Often in a complex code base, there are additional bits of work you need to do to help with debugging. The easy but hard-to-maintain way to do this is with #if:
obj.Wow();
#if DEBUG
// only want the overhead of checking this when debugging...
Very("abc");
#endif
Such();
This gets the job done, but can lead to a whole heap of problems:
- general ugliness – especially when you have lots of different #if pieces all over the file
- hard to track what uses each method (many code tools won’t look inside inactive conditional compilation blocks)
- problems with automated tools, such as using directive removal or code organization tools, which get very confused
We can do better!
Partial classes were added to C# a long time ago – with one of their main aims being to help with extension points for generated code. You can then split a logical class into multiple files, as long as you use the partial
modifier in each file. The contents of the file get merged by the compiler. So what about partial methods? These are like regular method, but with the odd property that they completely evaporate if you aren’t doing anything useful; as in – the compiler completely ignores all mention of them. You declare a partial method like so:
partial void OnSomethingAwesome(int someArg);
(noting that you cannot specify an access modifier, and that they follow the same rules as [Conditional(…)]
methods: you cannot specify a return value or access modifier, and the parameters cannot be declared out
(although they can be ref
) – again, this is because the compiler might be removing all trace of them, and we can’t do anything that would upset “definite assignment”). Then elsewhere in the code you can use that method like normal:
Write("tralala");
OnSomethingAwesome(123);
return true;
That looks convincing right? Except: until we write the body of OnSomethingAwesome
, it does not exist. At all. For example, in another file somewhere we could add:
partial class TheSameClass // and the same namespace!
{
partial void OnSomethingAwesome(int number)
{
Trace(number);
}
}
And only now does our code do anything useful. It is also important to note that just like [Conditional(…)]
methods, the evaluation of the arguments and target are also removed, so you need to be a little careful… there is a huge difference between:
OnSomethingAwesome(SomethingCritical());
and
var value = SomethingCritical();
OnSomethingAwesome(value);
In reality, this is rarely an actual issue.
Applying this to debugging operations
Hopefully, it should be obvious that we can use this to move all of our debugging operations out of the main code file – perhaps into TheSameClass.debugging.cs
file (or whatever you want) – which can then legitimately have a single #if
conditional region. So to take our earlier example:
obj.Wow();
OnVery("abc");
Such();
with (elsewhere):
partial void OnVery(string much);
How about interfaces?
EDIT:It turns out I was completely wrong here; partial interface
works fine - my mistake. I'm leaving this here as a mark of my public shame, but: ignore me. You can use interfaces much like the above.
There is no such thing as a partial interface
, but what you can do is declare a separate debug-only interface, and then extend the type in a partial class
:
partial class TheSameClass : IMagicDebugger
{
void IMagicDebugger.So() { /* ... */ }
}
Real world example
Here’s something from some real code, where during debugging and testing I need to keep additional counters, that are not needed in the production code:
#if DEBUG
partial class ResultBox
{
internal static long allocations;
public static long GetAllocationCount()
{
return Interlocked.Read(ref allocations);
}
static partial void OnAllocated()
{
Interlocked.Increment(ref allocations);
}
}
#endif
The intent should be pretty obvious from the context, but note that here everything to do with this debug-only feature is now neatly packaged together.
Enjoy.