Partial Classes

Partial classes16 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# allows declaration of a partial class by prepending a contextual keyword, partial, immediately before class,17 as Listing 6.57 shows.

Listing 6.57: 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 6.58 places the Program.CommandLine class into a file separate from the core Program members.

Listing 6.58: Defining a Nested Class in a Separate Partial Class
// File: Program.cs
partial class Program
{
    static void Main(string[] args)
    {
        CommandLine commandLine = new(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.

Partial Methods

Extending the concept of partial classes is the concept of partial methods,18 which are allowed only within partial types. 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 (e.g., to accommodate an additional column in the database), 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 6.59 shows the partial method declaration and the implementation for the Person class.

Listing 6.59: Defining a Partial Method to Access Its Implementation
// File: Person.Designer.cs
public partial class Person
{
    #region Extensibility Method Definitions
    static partial void OnLastNameChanging(string value);
    static partial void OnFirstNameChanging(string value);
    #endregion Extensibility Method Definitions
 
    // ...
    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;
 
    public partial string GetName();
}
 
// File: Person.cs
partial class Person
{
    static partial void OnLastNameChanging(string value)
    {
        value = value ?? 
            throw new ArgumentNullException(nameof(value));
 
        if (value.Trim().Length == 0)
        {
            throw new ArgumentException(
                $"{nameof(LastName)} cannot be empty.",
                nameof(value));
        }
    }
 
    // ...
 
    public partial string GetName() => $"{FirstName} {LastName}";
}

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 6.59, 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.

Prior to C# 9.0, partial methods 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 non-implemented 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. Lastly, partial members cannot be decorated with an accessibility modifier (e.g., private or public). Rather, partial methods are implicitly private.

Starting with C# 9.0, these restrictions were all removed. Partial methods could return values, have out parameters, and even have access modifiers. In fact, distinguishing this unrestricted version of a partial method, C# required the access modifier, even for methods that were intended to be private. To remove the restrictions, C# 9.0 required that all partial methods with access modifiers also had an accompanying method pair with the implementation. As demonstrated by Listing 6.59, if you declared a partial member as public string GetName() with no implementation, you were also required to have a second partial method declaration with the same signature and with the implementation, such as public partial string GetName() => $"{FirstName} {LastName}". In this way, the C# compiler ensured that any return values (including returns via out parameters) were successful because, in fact, the method had an implementation.

In summary, partial methods allow generated code to call methods that have not necessarily been implemented. Furthermore, if no implementation is 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.

________________________________________

16. Introduced with C# 2.0.
17. Interfaces (Chapter 8) structs (Chapter 9) can also be partial.
18. Introduced with C# 3.0.
{{ snackbarMessage }}