AddThis Social Bookmark Button

Print

Making Sense of Partial Classes

by Nick Harrison
10/04/2004

In Whidbey, Microsoft has introduced a new feature called partial classes. With partial classes, we can spread the definition of a class over multiple files. Partial classes attempt to solve the problem of separation of designer code and implementation code. In Whidbey, partial classes are used to separate these ideas by having the designer code in one file, and the user code in another. At compile time, these files are merged together. But the usefulness of partial classes goes beyond designer support. We will explore these solutions and explore a couple of benefits we may reap in our own projects.

Using Partial Classes

The mechanics of partial classes are very simple. You simply use the new keyword partial. In VB, it will look like this:


Partial Public Class SampleClassProperties

In C#, it will look like this:


public partial class SampleClassProperties

When the various classes in the project are compiled, the separate files are combined and the various components of the class are merged together at compile time. Aside from there being more than one file involved, a partial class is identical to a regular class. The rules for method overloading still hold true. IntelliSense still works as expected. Variables declared in one file are known to IntelliSense in all files making up that class. If a class implements a particular interface, only one file needs to mention the interface being implemented, yet the implementation does not need to be confined to that single file.

This new feature could potentially lead to confusion. We will also explore some file-naming conventions that can help reduce the potential confusion introduced by this new feature.

Uses in Visual Studio

When you drag a control onto a Web Form or a Windows Form, code is added automatically to initialize the control and set the properties for this new control. In the past, this code was embedded in a region labeled "Windows Form Designer generated code" or "Web Form Designer generated code." We were supposed to ignore this section and not worry about it, but it was always there. Visual Studio uses partial classes to further shield us from these details and to protect this code from a developer messing with it.

For Window Forms, a separate file is created following a naming convention of "FormName.Designer.cs or FormName.Designer.vb. By default, this file is hidden in the Solution Explorer, but you can see it if you click on the Show All Files button in the Solution Explorer. There is not much to see in this file. It simply has the control initialization information for the controls that you have added to the Windows Form.

When it comes to Web Forms, the designers of Visual Studio take it a step further. The designer class is implied and never written to disk. All of the information that would go into the designer class is implied in the .aspx file.

The tactics taken by the Visual Studio designers provide several advantages, including:

  • Reduction in file contention in shared development environments. The forms designer and the developer are not both trying to change the same file.
  • Isolation of low-level details. You don't have to worry about the details of how the individual controls are instantiated and initialized.
  • Protection for generated code through changes. You are less likely to change the generated code, and any code that you add will not be in the designer file and is protected.

While these may not always be goals in your designs, there are still lessons to learn.

Practical Uses for Your Team

When using partial classes, there are some traps to avoid. The option to use partial classes should not distract from good object-oriented design. Spreading a class' definition over multiple files may lead to designs that would benefit from refactoring and taking advantage of inheritance, but this does not mean that partial classes do not have their place.

Spreading a class definition over multiple files can have some advantages, but it also means that there are potentially more places to write your code. This can lead to confusion. Obviously, this requires careful adherence to new file-naming guidelines. We are all familiar with guidelines that call for defining one class per file and naming the file the same as the class being defined. To be useful and not confusing, partial classes require new guidelines.

With partial classes, the name of each file becomes all the more important. The file names should include the name of the class being defined as well as an indication of which aspects of the class are being defined in that file. We can take our lead from the Visual Studio designers and follow naming conventions similar to Winform1.Designer.cs. Following this naming convention, we know that this file contains code generated by the designer for WinForm1. We know that all code generated by the designer for WinForm1, and only the code generated by the designer for WinForm1, should be included in this file. Similarly, following a naming convention producing file names such as BusinessLogicClass.Properties.vb tells us that this file should contain all of the properties for this BusinessLogicClass. By grouping the components of you classes into Properties, Event, Methods, etc., the structure of your class is easier to follow. Many people already use "regions" to help enforce such structure. Partial classes take this structure a step further. Plus, by separating the implementation of a class across multiple files, multiple people can work on a class at the same time, which can improve productivity. This can also simplify configuration management, as your applications go into production where one version is being developed while the current version is still being supported.

There are also advantages in separating a class implementation into separate files to isolate complex or rarely used aspects. For example, if your class implements multiple interfaces, it might make sense to provide these implementations in separate files. Oftentimes, our classes may implement interfaces necessary to interact with a specific framework that are not necessarily relevant to the core functionality of the class. For example, you may have a business logic class whose core functionality is to perform validations on data entered and to run calculations based on this entered data. Such a class might implement an ISaveable interface, indicating that the data entered can be saved. Saving data is not part of the core functionality; the validation and calculations are. By putting the details for the implementation of the ISaveable interface in a separate file, this extra functionality is isolated and need not distract from the core functionality. Additionally, such isolation may help in future refactoring. If in the future you decide to move these details to a base class, the implementation details are already isolated.

Such practices may benefit from naming conventions that lead to file names such as BusinessLogicClass.ISaveable.vb or BusinessLogicClass.IClonable.cs. These files could contain the implementation of the interface mentioned in the file name. These are conventions to help reduce confusion; they are not intended to be hard and fast rules, and may not always work. If your class implements multiple interfaces with common methods or properties, the choice of where to put these common components may not be obvious. The goal is not strict adherence to rules. The goal is to reduce confusion and ease maintenance.

Practical Uses for Code Generation

Many people believe that partial classes were introduced solely to ease some complications in automatically generating code. While there are definite advantages for code generation, this was not the only reason, although it is perhaps the most compelling reason for their use.

One problem with code generators is that if you have to make any code changes to the generated code, you lose the ability to regenerate or you lose the changes that you made if the code is regenerated. We have all seen examples of code added to the generated sections of a Windows Form or a Web Form being deleted or removed by the designer. This is why. Since the designer does not know about your changes, these changes are not protected if the code is regenerated. One way to protect added code is to use inheritance and create a generation inheritance level and a customization level. By deriving the customization level from the generation level, and limiting all code changes to this generation level, your code changes are protected and the generation level can safely be regenerated.

The problem is that there are many cases where such inheritance layers may lead to a needlessly complicated solution. These extra layers of inheritance can pollute the NameSpaces with needless classes. Partial classes can provide a simpler solution when inheritance is not required.

Imagine a scenario where you want to generate classes that will expose a read/write property for every column in selected database tables, as well as a Save method and a couple of constructors to handle initializing the properties. Such features can easily be generated and provide boilerplate functionality. If you want to keep this base functionality but make slight changes, partial classes can lead to a simple solution. You can generate all of the baseline features and then add, in a separate file, any extra features that are needed. Perhaps you might want to have the generated Save method call a private method defined in the customizations file. You would still be able to regenerate to take into account new generation standards (or new columns or new features) without losing any of your customizations.

Such practices might lead to naming conventions with file names similar to TableObject.Generation.cs or TableObject.Customization.cs.

Nick Harrison UNIX-programmer-turned-.NET-advocate currently working in Charlotte, North Carolina using .NET to solve interesting problems in the mortgage industry.


Return to the ONDotnet.com