Organizing Your Interface Implementations






Organizing Your Interface Implementations

Problem

You have a class that implements an interface with many methods. These methods support only the interface functionality and don't necessarily relate well to the other code in your class. You would like to keep the interface implementation code separate from the main class code.

Solution

Use partial classes to separate the interface implementation code into a separate file. For example, you have a class called TriValue that takes three decimal values and performs some operations on them, like getting the average, the sum, and the product. This code is currently in a file called TriValue.cs, which contains:

	public partial class TriValue
	{
	    decimal first;
	    decimal second;
	    decimal third;

	    public TriValue(decimal val1, decimal val2,decimal val3)
	    {
	        this.first = val1;
	        this.second = val2;
	        this.third = val3;
	    }

	    public TypeCode GetTypeCode()
	    {
	        return TypeCode.Object;
	    }

	    public decimal GetAverage()
	    {
	        return (GetSum() / 3);
	    }
	    public decimal GetSum()
	    {
	        return first + second + third;
	    }

	    public decimal GetProduct()
	    {
	        return first * second * third;
	    }
	}

Now you want to add support for the IConvertible interface to the triValue class so that it can be converted to other data types. We could just add all 16 method implementations to the class definition in TriValue.cs and hide the code using a #region statement. Instead, you can now use the partial keyword on the triValue class and store the IConvertible implementation code in a separate file. Once a class begins to be defined in multiple files, it is important to have a naming convention for those files, so that it is easy to find implementation code and for other developers to understand where to put new code when it is added to this class. We will use the [BaseClass].[Interface].cs naming convention here. This will give you a new file called TriValue.IConvertible.cs that contains the IConvertible interface implementation code, shown in Figure.

Using partial classes to organize your interface implementations

/// Partial class that implements IConvertible 
public partial class TriValue : IConvertible 
{
    bool IConvertible.ToBoolean(IFormatProvider provider)
    {
        if (GetAverage() > 0)
            return true;
        else
            return false;
    }

    byte IConvertible.ToByte(IFormatProvider provider)
    {
        return Convert.ToByte(GetAverage());
    }

    char IConvertible.ToChar(IFormatProvider provider)
    {
        decimal val = GetAverage();
        if (val > char.MaxValue)
            val = char.MaxValue;
        if (val < char.MinValue)
            val = char.MinValue;
        return Convert.ToChar((ulong)val);
    }

    DateTime IConvertible.ToDateTime(IFormatProvider provider)
    {
        return Convert.ToDateTime(GetAverage());
    }

    decimal IConvertible.ToDecimal(IFormatProvider provider)
    {
        return GetAverage();
    }

    double IConvertible.ToDouble(IFormatProvider provider)
    {
        return Convert.ToDouble(GetAverage());
    }

    short IConvertible.ToInt16(IFormatProvider provider)
    {
        return Convert.ToInt16(GetAverage());
    }

    int IConvertible.ToInt32(IFormatProvider provider)
    {
        return Convert.ToInt32(GetAverage());
    }

    long IConvertible.ToInt64(IFormatProvider provider)
    {
        return Convert.ToInt64(GetAverage());
    }

    sbyte IConvertible.ToSByte(IFormatProvider provider)
    {
        return Convert.ToSByte(GetAverage());
    }

    float IConvertible.ToSingle(IFormatProvider provider)
    {
        return Convert.ToSingle(GetAverage());
    }

    string IConvertible.ToString(IFormatProvider provider)
    {
        return string.Format("({0},{1},{2})",
            first.ToString(),second.ToString(),third.ToString());
    }

    object IConvertible.ToType(Type conversionType, IFormatProvider provider) 
    {        
        return Convert.ChangeType(GetAverage(), conversionType);
    }

    ushort IConvertible.ToUInt16(IFormatProvider provider)
    {
        return Convert.ToUInt16(GetAverage());
    }

    uint IConvertible.ToUInt32(IFormatProvider provider)
    {
        return Convert.ToUInt32(GetAverage());
    }

    ulong IConvertible.ToUInt64(IFormatProvider provider) 
    {
        return Convert.ToUInt64(GetAverage()); 
    } 
}

Now you have the interface implemented and your original class definition is still straightforward. For classes that implement many interfaces, this approach will allow for a more tightly organized implementation.

Discussion

It should be noted that there is no Microsoft intermediate language (MSIL) indicator that these are partial classes if you look at your class in Ildasm or Reflector. It will look just like a normal class by the time it gets to MSIL. Intellisense handles the merge as well. Since partial types are a language trick, they cannot span assemblies, as the class needs to be resolved by the compiler. Partial types can be declared in the same file as well as in separate files, but still must be in the same namespace so the compiler can resolve it before generating the MSIL.

You can use the partial type support for classes, nested classes, structures, and interfaces, but you cannot have a partial enum definition. Partial types can declare support for different interfaces per partial type. However, single inheritance is still in force and must be the same or omitted from the secondary partial type. You can see that in the Solution the partial triValue class definition in TriValue.cs you created does not specify the inheritance from IConvertible, only the one in TriValue.IConvertible.cs does.

The previous TriValue class can be exercised with the following code:

	class Program 
	{ 
	    static void Main(string[] args) 
	    { 
	        TriValue tv = new TriValue(3, 4, 5);
	        Console.WriteLine("Average: {0}",tv.GetAverage());
	        Console.WriteLine("Sum: {0}", tv.GetSum());
	        Console.WriteLine("Product: {0}", tv.GetProduct()); 
	        Console.WriteLine("Boolean: {0}", Convert.ToBoolean(tv)); 
	        Console.WriteLine("Byte: {0}", Convert.ToByte(tv));
	        Console.WriteLine("Char: {0}", Convert.ToChar(tv)); 
	        Console.WriteLine("Decimal: {0}", Convert.ToDecimal(tv)); 
	        Console.WriteLine("Double: {0}", Convert.ToDouble(tv)); 
	        Console.WriteLine("Int16: {0}", Convert.ToInt16(tv)); 
	        Console.WriteLine("Int32: {0}", Convert.ToInt32(tv)); 
	        Console.WriteLine("Int64: {0}", Convert.ToInt64(tv)); 
	        Console.WriteLine("SByte: {0}", Convert.ToSByte(tv)); 
	        Console.WriteLine("Single: {0}", Convert.ToSingle(tv)); 
	        Console.WriteLine("String: {0}", Convert.ToString(tv)); 
	        Console.WriteLine("Type: {0}", Convert.GetTypeCode(tv)); 
	        Console.WriteLine("UInt16: {0}", Convert.ToUInt16(tv)); 
	        Console.WriteLine("UInt32: {0}", Convert.ToUInt32(tv)); 
	        Console.WriteLine("UInt64: {0}", Convert.ToUInt64(tv));
	    }
	}

The preceding code produces the following output:

	Average: 4
	Sum: 12
	Product: 60
	Boolean: True
	Byte: 4
	Char: _
	Decimal: 4
	Double: 4
	Int16: 4
	Int32: 4
	Int64: 4
	SByte: 4
	Single: 4
	String: (3,4,5)
	Type: Object
	UInt16: 4
	UInt32: 4
	UInt64: 4

See Also

See the "Partial Class Definitions" and "partial Keyword" topics in the MSDN documentation.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows