Partial Classes
Begin 2.0
Another language feature added in C# 2.0 is partial classes. Partial classes are portions of a class that the compiler can combine to form a complete class. Although you could define two or more partial classes within the same file, the general purpose of a partial class is to allow the splitting of a class definition across multiple files. Primarily this is useful for tools that are generating or modifying code. With partial classes, the tools can work on a file separate from the one the developer is manually coding.
Defining a Partial Class
C# 2.0 (and later) allows declaration of a partial class by prepending a contextual keyword, partial, immediately before class, as Listing 5.46 shows.
LISTING 5.46: Defining a Partial Class
// File: Program1.cs
partial class Program
{
}
__________________________________________________________________________________
// File: Program2.cs
partial class Program
{
}
In this case, each portion of Program is placed into a separate file, as identified by the comment.
Besides their use with code generators, another common use of partial classes is to place any nested classes into their own files. This is in accordance with the coding convention that places each class definition within its own file. For example, Listing 5.47 places the Program.CommandLine class into a file separate from the core Program members.
LISTING 5.47: Defining a Nested Class in a Separate Partial Class
// File: Program.cs
partial class Program
{
static void Main(string[] args)
{
CommandLine commandLine = new CommandLine(args);
switch (commandLine.Action)
{
// ...
}
}
}
__________________________________________________________________________________
// File: Program+CommandLine.cs
partial class Program
{
// Define a nested class for processing the command line.
private class CommandLine
{
// ...
}
}
Partial classes do not allow for extending compiled classes, or classes in other assemblies. They are simply a means of splitting a class implementation across multiple files within the same assembly.
End 2.0
Partial Methods
Begin 3.0
Beginning with C# 3.0, the language designers added the concept of partial methods, extending the partial class concept of C# 2.0. Partial methods are allowed only within partial classes, and like partial classes, their primary purpose is to accommodate code generation.
Consider a code generation tool that generates the Person.Designer.cs file for the Person class based on a Person table within a database. This tool examines the table and creates properties for each column in the table. The problem, however, is that frequently the tool cannot generate any validation logic that may be required because this logic is based on business rules that are not embedded into the database table definition. To overcome this difficulty, the developer of the Person class needs to add the validation logic. It is undesirable to modify Person.Designer.cs directly, because if the file is regenerated (to accommodate an additional column in the database, for example), the changes would be lost. Instead, the structure of the code for Person needs to be separated out so that the generated code appears in one file and the custom code (with business rules) is placed into a separate file unaffected by any regeneration. As we saw in the preceding section, partial classes are well suited for the task of splitting a class across multiple files, but they are not always sufficient. In many cases, we also need partial methods
Partial methods allow for a declaration of a method without requiring an implementation. However, when the optional implementation is included, it can be located in one of the sister partial class definitions, likely in a separate file. Listing 5.48 shows the partial method declaration and the implementation for the Person class.
LISTING 5.48: Defining a Nested Class in a Separate Partial Class
// File: Person.Designer.cs
public partial class Person
{
#region Extensibility Method Definitions
partial void OnLastNameChanging(string value);
partial void OnFirstNameChanging(string value);
#endregion
// ...
public System.Guid PersonId
{
// ...
}
private System.Guid _PersonId;
// ...
public string LastName
{
get
{
return _LastName;
}
set
{
if ((_LastName != value))
{
OnLastNameChanging(value);
_LastName = value;
}
}
}
private string _LastName;
// ...
public string FirstName
{
get
{
return _FirstName;
}
set
{
if ((_FirstName != value))
{
OnFirstNameChanging(value);
_FirstName = value;
}
}
}
private string _FirstName;
}
__________________________________________________________________________________
// File: Person.cs
partial class Person
{
partial void OnLastNameChanging(string value)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
if(value.Trim().Length == 0)
{
throw new ArgumentException(
"LastName cannot be empty.",
"value");
}
}
}
In the listing of Person.Designer.cs are declarations for the OnLastNameChanging() and OnFirstNameChanging() methods. Furthermore, the properties for the last and first names make calls to their corresponding changing methods. Even though the declarations of the changing methods contain no implementation, this code will successfully compile. The key is that the method declarations are prefixed with the contextual keyword partial in addition to the class that contains such methods.
In Listing 5.48, only the OnLastNameChanging() method is implemented. In this case, the implementation checks the suggested new LastName value and throws an exception if it is not valid. Notice that the signatures for OnLastNameChanging() between the two locations match.
Any partial method must return void. If the method didn’t return void and the implementation was not provided, what would the expected return be from a call to a nonimplemented method? To avoid any invalid assumptions about the return, the C# designers decided to prohibit methods with returns other than void. Similarly, out parameters are not allowed on partial methods. If a return value is required, ref parameters may be used.
In summary, partial methods allow generated code to call methods that have not necessarily been implemented. Furthermore, if there is no implementation provided for a partial method, no trace of the partial method appears in the CIL. This helps keep code size small while keeping flexibility high.
End 3.0