Click here to Skip to main content
11,710,943 members (88,018 online)
Click here to Skip to main content

Introducing C# 2.0 Partial Types

, 3 Oct 2005 CPOL 38.3K 15
Rate this:
Please Sign up or sign in to vote.
A general discussion on C# 2.0 partial types.

Introduction

Examine the example below:

//definition for BinaryTree.h
class BinaryTree
{
public:
      BinaryTree(void);
      ~BinaryTree(void);
      int seed;
      char* name;
      void Load(char* name);
      char* Find(); 
};

In C++, class definitions can be separated from the implementations. The above snippet is taken from a file BinaryTree.h which contains all the definitions associated with the BinaryTree class but no implementation code. Continuing with the example, the implementation file BinaryTree.cpp would be defined as follows:

#include ".\binarytree.h"

BinaryTree::BinaryTree(void)
{
}

BinaryTree::~BinaryTree(void)
{
}

void BinaryTree::Load(char* str){
      this->name = str;
}

char* BinaryTree::Find(){
      return this->name;
}

As you can see from the sample, the first line of BinaryTree.cpp explicitly declares the inclusion of the class definition found in binarytree.h. Consequently, this completely separate file can use members defined in the latter as though they were defined there. The sample is, of course, nonsensical, but the concept nevertheless prevails; the ability to create types across multiple files.

It is important to note that the idea of having multiple public types in one file was so revolting to the devout object oriented programmer at one time that entire languages, Java for instance, excluded it. However, you will find that while it is good programming practice to maintain all source code for a type in a single file, sometimes a type becomes large enough that this is an impractical constraint.

Partials

C# 2.0 introduces the concept of partial type definitions for classes, structs, and interfaces, as a means to break types in a program into multiple pieces to be stored in different source files for easier maintenance and development. This approach is particularly useful when the need arises to use nested types or provide interface implementation.

Unlike C/C++, where a #include keyword was used to explicitly bind the contents of a header file to the implementation file, C# 2.0 partial type links are inferred through the type names given. Also, these are no interface definitions necessary in C# 2.0. A sample of a partial type is given below:.

//Binarytree_header.cs
public partial class BinaryTree
{
    int seed;
    string name;
    public BinaryTree()
    {
    }
}

The above example attempts to recreate the BinaryTree sample we saw earlier in the chapter, using C#; notice that no include statements are needed, nor are any function prototypes defined. Further implementation of this class may now be defined in other files; for instance, a BinaryTree_Load.cs file may be created which produces more BinaryTree functionality.

//BinaryTree_load.cs: Load functionality
public partial class BinaryTree
{
    public void Load(string name)
    {
          this.name = name;
    }
}

We can also create another file BinaryTree_find.cs which would have all the functionality associated with finding an element in the tree.

//BinaryTree_find.cs: find functionality
public partial class BinaryTree
{
    public string Find()
    {
          return name;
    }
}

As you can see from the above samples, the new type modifier, partial, is used when defining a type in multiple parts. It indicates that additional parts may exist elsewhere. I stress the word may because each partial type declaration is autonomous. The example below illustrates this by creating a partial type declaration that includes no other parts:

public partial class BinaryTree
{
    int x;
}

The code above will compile successfully. Adding another part to the type will not change this successful compilation.

public partial class BinaryTree
{
    int y;
}

Finally, a constructor can be added to instantiate the two variables defined in both parts of the type. A sample showing all three parts is highlighted below:

public partial class BinaryTree
{
    int x;
}

public partial class BinaryTree
{
    int y;
}

public partial class BinaryTree
{
    public BinaryTree()
    {
          this.x = 100;
          this.y = 100;
    }
}

Each partial type declaration must include the partial modifier, and must be declared within the same namespace as the other parts.

Compiling the code above will produce the following type definition:

public partial class BinaryTree
{
    int x;
    int y;
    public BinaryTree()
    {
      this.x = 100;
      this.y = 100;
    }
}

If we compiled the BinaryTree sample from the first section, it would produce a type definition similar to the one depicted below:

public class BinaryTree
{
    int seed;
    string name;
    public BinaryTree()
    {
    }

    public void Load(string name)
    {
          this.name = name;
    }

    public string Find()
    {
          return name;
    }
}

It is important to note that partial types do not allow already compiled types to be extended. This is because all parts of a partial type must be compiled together such that the parts can be merged at compile-time to produce an actual type; as discussed in the example above. The partial modifier, if it appears, must appear immediately before either class, struct, or interface keywords, but is not itself a keyword.

Nested Partials

Examine the three types defined in the sample below:

public struct DataSource
{
    public string connectionString;
}

public class DataAccessLayer
{
    DataSourceCollection sources;
    public DataAccessLayer()
    {
        sources = new DataSourceCollection();
    }

    public DataSourceCollection DataSources
    {
        get
        {
            return sources;
        }
    }

    public class DataSourceCollection
    {
        Hashtable items;
        public DataSourceCollection()
        {
            items = new Hashtable();
        }

        public DataSource this[string dataSourceName]
        {
            get
            {
                return (DataSource )items[dataSourceName];
            }
        }
    }
}

The snippet defines a struct DataSource of which the type DataSourceCollection is an aggregate through the items Hashtable. This type DataSourceCollection also happens to be a nested type of the DataAccessLayer type. Using partial types in this scenario, the nested type DataSourceCollection may also be declared in multiple parts by using the partial modifier. Hence DataAccessLayer may also be defined as follows:

public class DataAccessLayer
{
    DataSourceCollection sources;
    public DataAccessLayer()
    {
        sources = new DataSourceCollection();
    }

    public DataSourceCollection DataSources
    {
      get
      {
        return sources;
      }
    }

    public partial class DataSourceCollection
    {
      Hashtable items;
      public DataSourceCollection()
      {
        items = new Hashtable();
      }
    }

    public partial class DataSourceCollection
    {
      public DataSource this[string dataSourceName]
      {
        get
        {
            return (DataSource)items[dataSourceName];
        }
      }
    }
}

This would not make too much sense though; since the type DataSourceCollection is only qualified through its container DataAccessLayer, there is no way to specify a part outside of the DataSourceCollection type definition. A more optimal approach is to define either the containing type as partial with the contained type as not.

public partial class DataAccessLayer
{
    DataSourceCollection sources;
    public DataAccessLayer()
    {
        sources = new DataSourceCollection();
    }

    public DataSourceCollection DataSources
    {
      get
      {
        return sources;
      }
    }     
    
}

public partial class DataAccessLayer
{
    public class DataSourceCollection
    {
      Hashtable items;
      public DataSourceCollection()
      {
        items = new Hashtable();
      }

      public DataSource this[string dataSourceName]
      {
        get
        {
            return (DataSource)items[dataSourceName];
        }
      }
    }
}

Or to define both containing and contained parts as partial:

public partial class DataAccessLayer
{
    DataSourceCollection sources;
    public DataAccessLayer()
    {
        sources = new DataSourceCollection();
    }

    public DataSourceCollection DataSources
    {
      get
      {
        return sources;
      }
    }
}

public partial class DataAccessLayer
{
    public partial class DataSourceCollection
    {
      Hashtable items;
      public DataSourceCollection()
      {
          items = new Hashtable();
      }
    }
}

public partial class DataAccessLayer
{    public partial class DataSourceCollection
    {
      public DataSource this[string dataSourceName]
      {
        get
        {
              return (DataSource)items[dataSourceName];
        }
      }
    }
}

The first example shows an easy solution to defining nested types. With the power of partial classes, nested types can be completely isolated to their own files. The DataAccessLayer is defined as partial so that the nested type DataSourceCollection can be defined in a separate part. The second example is probably more of a telling of the dangers of partial types when used for no good reason. Here, we see that both the containing type DataAccessLayer and the contained type DataSourceCollection are defined as partial. Hence, the definitions can be broken into multiple parts. Please practice caution when using partial types, particularly with regard to nested types. You will find that in a complex program, it will be almost impossible to determine what the full definition of any one type is once it is defined as partial.

Partial Type Attributes

Attributes on partial types are combined in an unspecified order so that they appear to all be defined on the given type. In the preceding section, we defined a type DataAccessLayer as follows:

public partial class DataAccessLayer
{
    DataSourceCollection sources;
    public DataAccessLayer()
    {
        sources = new DataSourceCollection();
    }

    public DataSourceCollection DataSources
    {
      get
      {
        return sources;
      }
    }
}

public partial class DataAccessLayer
{
    public class DataSourceCollection
    {
      Hashtable items;
      public DataSourceCollection()
      {
        items = new Hashtable();
      }

      public DataSource this[string dataSourceName]
      {
        get
        {
            return (DataSource)items[dataSourceName];
        }
      }
    }
}

Attributes can be added to each part of the type definition as follows:

//Dal.cs
[Docking]
public partial class DataAccessLayer
{
    DataSourceCollection sources;
    public DataAccessLayer()
    {
        sources = new DataSourceCollection();
    }

    public DataSourceCollection DataSources
    {
      get
      {
            return sources;
      }
    }
}

//dal.datasourcecollection.cs
[Serializable]
public partial class DataAccessLayer
{
    public class DataSourceCollection
    {
      Hashtable items;
      public DataSourceCollection()
      {
        items = new Hashtable();
      }
      public DataSource this[string dataSourceName]
      {
        get
        {
            return (DataSource)items[dataSourceName];
        }
      }
    }
}

In the above example, a different attribute has been added to each part of the definition of DataAccessLayer. To the part defined in dal.cs, the attribute DockingAttribute has been attached. To the part defined in dal.datasourcecollection.cs, SerializableAttribute has been attached. This activity is equivalent to specifying both attributes on the type as follows:

[Serializable, Docking ]

Inheritance and Interface Implementation

Now that we are done having all this fun, it's time to lay down some rules regarding what you can and can't do with partial types. To help with the discussion, it is important that we visualize what the end result of a partial type is. The previous section defined a partial type DataAccessLayer as follows:

//Dal.cs
[Docking]
public partial class DataAccessLayer
{
    DataSourceCollection sources;
    public DataAccessLayer()
    {
        sources = new DataSourceCollection();
    }

    public DataSourceCollection DataSources
    {
      get
      {
            return sources;
      }
    }
}

//dal.datasourcecollection.cs
[Serializable]
public partial class DataAccessLayer
{
    public class DataSourceCollection
    {
      Hashtable items;
      public DataSourceCollection()
      {
        items = new Hashtable();
      }
      public DataSource this[string dataSourceName]
      {
        get
        {
            return (DataSource)items[dataSourceName];
        }
      }
   }
}

This definition, once compiled, will be equivalent to the listing below:

[Docking, Serializable] 
public class DataAccessLayer
{
    DataSourceCollection sources;
    public DataAccessLayer()
    {
        sources = new DataSourceCollection();
    }

    public DataSourceCollection DataSources
    {
      get
      {
            return sources;
      }
    }

    public class DataSourceCollection
    {
      Hashtable items;
      public DataSourceCollection()
      {
        items = new Hashtable();
      }
      public DataSource this[string dataSourceName]
      {
        get
        {
            return (DataSource)items[dataSourceName];
        }
      }
    }           
}

The lesson here is that a partial class, no matter how fragmented, is still one functional unit. Hence, any accessibility specifications applied to a partial type must agree with all parts of the type that also include the accessibility specification. The concept is illustrated with the sample below:

[Docking]
public partial class DataAccessLayer : GenericDAL 
{
    DataSourceCollection sources;
    public DataAccessLayer()
    {
        sources = new DataSourceCollection();
    }

    public DataSourceCollection DataSources
    {
      get
      {
        return sources;
      }
    }
}

internal partial class DataAccessLayer : GenericDAL 
{
    public class DataSourceCollection
    {
      Hashtable items;
      public DataSourceCollection()
      {
        items = new Hashtable();
      }
      public DataSource this[string dataSourceName]
      {
        get
        {
            return (DataSource)items[dataSourceName];
        }
      }
   }
}

Notice that in this definition of DataAccessLayer, the two parts inherit from a base type GenericDAL. Also notice that while one part defines the class as public, the other uses the internal modifier. Attempting to compile this listing will fail with the following message:

Error 1: Partial declarations of 'PartialTypes2.DataAccessLayer' 
         have conflicting accessibility modifiers

The reason is evident from our first demonstration in this section. Although DataAccessLayer is fragmented, it is still a type, and a type cannot be both internal and public at the same time.

The next issue is that of base classes, and is not as easy to ascertain. Examine the listing below:

[Docking]
public partial class DataAccessLayer : GenericDAL 
{
    DataSourceCollection sources;
    public DataAccessLayer()
    {
        sources = new DataSourceCollection();
    }

    public DataSourceCollection DataSources
    {
      get
      {
        return sources;
      }
    }
}

      
partial class DataAccessLayer : DataSource
{
    public class DataSourceCollection
    {
      Hashtable items;
      public DataSourceCollection()
      {
        items = new Hashtable();
      }
      public DataSource this[string dataSourceName]
      {
        get
        {
            return (DataSource)items[dataSourceName];
        }
      }
    }
}

Although the modifier incompatibility has been removed from the type, a problem persists during compilation of the above source. The difference here is that the second part of DataAccessLayer is now attempting to inherit from a different base type DataSource, than the first part which inherits from GenericDAL. This might not seem intuitive because it contradicts the combining behavior of partial type attributes; however, returning to our initial demonstration on the singularity of partial types, DataAccessLayer is a type; consequently, it can only inherit from one base class. The formal rule dictates that when a partial class declaration includes a base class specification, it must agree with all other parts that include a base class specification. Attempting to compile the listing above would produce the following error message:

Error 1: Partial declarations of 'PartialTypes2.DataAccessLayer' must not specify different base classes

Now in the world of interfaces, where multiple inheritance is the game, the compiling behavior returns. Examine the example below.

public partial class DataAccessLayer : GenericDAL  
{
    DataSourceCollection sources;
    public DataAccessLayer()
    {
        sources = new DataSourceCollection();
    }
}     
      
partial class DataAccessLayer : IDataSourceManager  
{
    public DataSourceCollection DataSources
    {
      get
      {
        return sources;
      }
    }

    public class DataSourceCollection
    {
      Hashtable items;
      public DataSourceCollection()
      {
        items = new Hashtable();
      }
      public DataSource this[string dataSourceName]
      {
        get
        {
            return (DataSource)items[dataSourceName];
        }
      }
    }
}

Similar to the example in the last section, the second part of DataAccessLayer defines a different base than the first. Unlike the sample in the previous section, however, IDataSourceManager is an interface and not a class. The formal rule states that the set of base interfaces for a type declared in multiple parts is the union of the base interfaces specified on each part. Furthermore, a particular base interface may only be named once on each part, but it is permitted for multiple parts to name the same base interface(s).

This means that the first part in the listing above could have been defined to include the IDataSourceManager interface as well. The listing below illustrates.

public partial class DataAccessLayer : GenericDAL, IDataSourceManager
{
    DataSourceCollection sources;
    public DataAccessLayer()
    {
        sources = new DataSourceCollection();
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Edward Moemeka
United States United States
Hi I'm Edward Moemeka,
For more interesting articles about stuff check out my blog at http://moemeka.blogspot.com
To correspond, email me at edward.moemeka@synertry.com
To support my company, thus help me feed my family, check out our awesome online preview at www.synertry.com. Remember, its in alpha Wink | ;-)

You may also be interested in...

Comments and Discussions

 
QuestionAdding Partial class files to a form. Pin
Wolfram Steinke27-May-08 14:44
memberWolfram Steinke27-May-08 14:44 
GeneralI Don't Get It Pin
John Simmons / outlaw programmer18-Oct-06 9:21
memberJohn Simmons / outlaw programmer18-Oct-06 9:21 
GeneralNice one! Pin
Ali I.24-Nov-05 18:49
memberAli I.24-Nov-05 18:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150819.1 | Last Updated 3 Oct 2005
Article Copyright 2005 by Edward Moemeka
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid