C# partial methods to the limelight!
C# 2.0 and Visual Studio 2005 introduced partial classes to the C# language. In the same rush partial methods appeared, but they have been far less heard of. What’s with those?
Background: Partial classes
Partial classes are a big part of the background for partial methods, so a short recap is in order. Skip to the next heading if you know all this already!
Partial class is a class defined in multiple segments. For example, in the following code snippet, MyClass contains both Method1 and Method2, although the two definitions could be split across files (but not across assemblies).
public partial class MyClass { public void Method1() { } } public partial class MyClass { public void Method2() { } }
There are two scenarios where partial classes are particularly useful. The first, and definitely more used of them, is the ability to hide generated code into a separate file (see my expression of relief back in 2005). The technique is very useful in designers, domain-specific code generators and all similar source code producers.
The second one involves splitting a huge class of your own code into several files, making it easier to find the right spot to edit. This is a bit more controversial: If your class is big enough to benefit from splitting, it’s probably too big. There are certain exceptions though. For example, if a class is only used to define a large set of closely-related constants such as error codes, it might be reasonable to split the definitions. While the resulting class would have ridiculously many members, it might still only have one responsibility, making it acceptable from a design perspective.
A partial method, then?
Direct analogy would suggest that partial methods would then have their code content combined from several sources, but that is actually not the case. Instead, partial methods are an extensibility approach for partial classes. They are not useful in scenario two described above, but generated code might benefit from such a construct.
Namely, it is often necessary to inject your own functionality into the generated code. The typical example might involve injecting validation logic into a class generated by an Object-Relational Mapping tool such as LINQ to SQL. There would be several possible approaches to this, the most obvious ones being inheritance and events.
However, inheritance by itself is at odds with this: Prior to to the advent of partial classes, your own code often was a subclass of the inherited code, and such code injections were performed through use of abstract or virtual methods. Partial classes specifically allow to simplify the situation and get rid of inheritance, so override isn’t a possibility here. On the other hand, using events as a customization mechanism involves considerable overhead both design-time (implementing the correct EventArgs containers, delegate types et cetera) and run-time.
Thus comes the definition: Partial method is a method that has its definition specified in one segment of a partial class and its implementation possibly defined in another.
public partial class MyClass // First segment, generated code { public void Method1() { MyExtension(); } partial void MyExtension(); } public partial class MyClass // Second segment, hand-written { public void Method2() { } partial void MyExtension() { Console.WriteLine("Hello world"); } }
In this case, it is relevant to notice that the second definition might not even exist – of course, the generated code need not always be customized, but the generator cannot know that in beforehand. Code in the first segment would still compile, however. If a body definition for a partial method does not exist, all the references to such methods are removed at compilation time. Therefore, were the second segment missing, the Method1 definition would be considered empty for all practical purposes.
In order for such behavior to be valid, there are a few important constraints for partial methods:
- First, they are implicitly private, and as such, only act as an extensibility point within the class.
- Second, they must return void and cannot have out parameters, since the removal of the method call might then result in a undefined local variable at the call site.
- Third, it is an error to create a delegate to a partial method that hasn’t been defined. In practice a code generator cannot trust on the delegate being creatable, but the handwritten code part can.
Here’s the authoritative reference on MSDN – it also lists the more esoteric constrains related to partial methods.
Which extensibility approach to use?
Naturally, this question only arises when creating your own code generator or customizing the templates of one. Most don’t do that, but in case you happen to, here are some thoughts on the matter:
Given that partial methods are always private, they cannot be used as a public extensibility mechanisms. Therefore partial methods are constrained to be a very local approach to extensibility. They are a good one at that, given that they are totally invisible to the outer world.
If you need visibility down the inheritance tree, use abstract and virtual methods. If you want to provide notifications and/or request input from any users of your objects, use events. Both these methods are great, but carry one important side effect: It is very hard to make sure the extending code (event handler or an overriding method) cannot hurt you, for example by throwing surprising exceptions and so on. Although the partial method body could do the same, the risks are substantially less severe, as the attacking code cannot be injected from outside your own class.
So use a partial method if that suffices – it’s cleanest and safest for most simple extension purposes. If you need more visibility or better modularity (such as a overridable but callable default implementation as provided by a virtual method), you’ll need to look further. Unfortunately, that also complicates your extensibility story, so you might consider simplifying it first.
July 6, 2009
· Jouni Heikniemi · No Comments
Tags: C#, class design, code generation · Posted in: .NET
Leave a Reply