“Sideways overriding” with partial methods
First note: this blog post is very much tongue in cheek. I'm not actually planning on using the idea. But it was too fun not to share.
As anyone following my activity on GitHub may be aware, I've been quite a lot of work on Protocol Buffers recently - in particular, a mostly-new port for proto3. I've recently been looking at JSON support, and thinking about how to implement "overriding" ToString() for a few well-known types. I generate partial classes, so that gives me a hook to provide extra functionality. Indeed, I'm planning on using this to provide conversion methods for Timestamp and Duration, for example. However, you can't really override anything in partial methods.
Refresher on partial methodsWhile partial classes were introduced in C# 2, partial methods were introduced in C# 3. The idea is that one source file (usually the generated one) can provide a partial method signature, and another source file (usually the manually-written one) can provide an implementation if it wants to. Any part of the source can call the method, and the call will be removed at compile-time if nothing provides an implementation. The fact that the method may not be there leads to some limitations:
- Partial methods are implicitly private, but you can't specify an access modifier explicitly
- Partial methods are always void - they can't return any values
- Partial methods cannot have out parameters
(Interestingly, a partial method implementation can be an async method - but with a return type of void, which is never a nice situation to be in.)
There's more in the spec, but the last two bullets are the important part.
So, suppose I want to override ToString() in the generated code, but provide a mechanism for that override to be "further overridden" effectively, in the manual code for the same class? How do I get the value from an "extra override"? How do I even detect whether or not it's there?
Side effects to the rescue!(Now there's a phrase you never thought you'd hear from me.)
I mentioned before that if a partial method is called but no implementation is provided, the call is removed. That includes all aspects of the call - including the evaluation of the arguments. So if evaluating the argument has a side-effect" we can spot that side effect.
Next, we have to work out how to get a value back from a method. We can't use the return value, and we can't use an out parameter. There are two options here: we could either pass a wrapper (e.g. an array with a single element) and allow the "extra override" to populate the wrapper" or we can use a ref parameter. The latter feels ever-so-slightly cleaner to me.
And so the ugly hack is born. The code generator can always generate code like this:
partial void ToStringOverride(bool ignored, ref string value);public override string ToString(){ string value = null; bool overridden = false; ToStringOverride(overridden = true, ref value); return overridden ? value : "Original";}
For any partial class where the ToStringOverride method isn't implemented, overridden will still be false, so we'll fall back to returning "Original". (I would hope that any decent JIT would remove the overridden and value local variables entirely at that point.) Otherwise, we'll return whatever the method has changed value to.
Here's a short but complete example:
using System;// Generated codepartial class UglyHack1{ partial void ToStringOverride(bool ignored, ref string value); public override string ToString() { string value = null; bool overridden = false; ToStringOverride(overridden = true, ref value); return overridden ? value : "Original"; }}// Generated codepartial class UglyHack2{ partial void ToStringOverride(bool ignored, ref string value); public override string ToString() { string value = null; bool overridden = false; ToStringOverride(overridden = true, ref value); return overridden ? value : "Original"; } }// Manual codepartial class UglyHack2{ partial void ToStringOverride(bool ignored, ref string value) { value = "Different!"; }}class Test{ static void Main() { var g1 = new UglyHack1(); var g2 = new UglyHack2(); Console.WriteLine(g1); Console.WriteLine(g2); }}
Horribly ugly, but it works"
Alternatives?Obviously this isn't really pleasant. Some alternatives:
- Derive from the generated class in order to override ToString again. Doesn't work with sealed classes, and will only work if clients create instances of the derived class.
- Introduce a new interface, and allow manual code to implement it on the partial class. The ToString method can then check this is IMyOtherToString or whatever, and call it appropriately. This introduces another virtual call for no great reason, and exposes the interface to the outside world, which we may not want to do.
- Don't override ToString in the generated code at all. Not good if you normally want to override it.
- Introduce an abstract base class which the generated class derives from. Override ToString() in that base class, possibly calling an abstract member which is then provided in the generated class - but allowing the manual code to override ToString() again.
Ugly hacks are fun. But it's much better to keep them where it belongs: in a blog post, not in production code.