65.9K
CodeProject is changing. Read more.
Home

Operator Overloading with Generics - Using Inheritance to Allow Use of C# Operators (C# 2005)

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.70/5 (9 votes)

Oct 1, 2006

CPOL

2 min read

viewsIcon

40900

downloadIcon

170

An article on how to use C# operators on the parameter types in your generic code (normally the compiler does not allow this). This solution just uses inheritance, so is fairly easy to maintain.

Introduction

This is an article about operator overloading with generics using inheritance to allow the use of C# operators (C# 2005). Normally, the C# compiler does not allow us to use C# operators on a type parameter. For example, say you have some generic code, and you are doing some processing, such as adding the types, then it would be nice to use the '+' operator.

    class CMyGenericClass<T>
    {
        public T TestUsingOperatorsOnT(T _one, T _two)
        {
            T tTemp = (_one + _two);
            return tTemp;
        }
    }

Compiling this code results in the compiler error:

    Operator '+' cannot be applied to operands of type 'T' and 'T'    

Now, there may be good reasons why C# 2005 does not allow operations to be used on generic parameters. The most likely reason is that operator code is not truly 'generic'. However, there may be cases where it would be nice to use an operator...

Background

This article was inspired by the book "Pro C#2005 and the .NET 2.0 Platform", by Andrew Troelsen (an excellent book...), where I learned about the compiler restriction (that generic code could not use operators on the parameter type).

This seemed a bit restrictive to me, so I figured there must be a way around it. This is a fairly simple way around it.

The Code

We can circumvent this restriction, by creating an abstract type, which has the desired operators. The actual implementation of the operator will be an abstract method, which our type 'T' must provide:

    abstract class CMyOperations
    {
        ...
        public abstract CMyOperations AddMyOperations(CMyOperations _ITwo);

//the operator+, which we want to be able to use in our generic code in CMyGenericClass
        public static CMyOperations operator +(CMyOperations _IOne, CMyOperations _ITwo) 
        {
//use the implementation provided by the deriving class
            return _IOne.AddMyOperations(_ITwo); 
        }
        ...
    }    

The implementation for these operators will be provided by our type 'T', here CMyComplexNumber:

    class CMyComplexNumber : CMyOperations
    {
        public override CMyOperations AddMyOperations(CMyOperations _ITwo)
        {
            //... implement the addition here ...
        }
    };    

In our generic class, we can force the type 'T' to provide the desired operations, by using the 'where' keyword:

    class CMyGenericClass<T> where T : CMyOperations
    {
        ...
    }   

The 'where' keyword here means that the type 'T' must be derived from CMyOperations. Because CMyOperations is abstract, 'T' must provide the implementation for the required operations. To sum it up by example:

     /*
     * class CMyOperations
     * 
     * This class provides the operators that we would like our parameter type 
     * 'T' to provide.
     * This is an abstract class - the actual implementation 
     * will be provided by the parameter type 'T'.
     * The parameter type 'T' will derive from this class.
     */
    abstract class CMyOperations
    {
//an abstract method, which implements the operator+, and will be provided by type 'T'
        public abstract CMyOperations AddMyOperations(CMyOperations _ITwo); 

//the operator+, which we want to able to use in our generic code in CMyGenericClass
        public static CMyOperations operator +(CMyOperations _IOne, CMyOperations _ITwo) 
        {
//use the implementation provided by the deriving class
            return _IOne.AddMyOperations(_ITwo); 
        }
    }

    //Now, we can create types for our generic class to use:
    class CMyComplexNumber : CMyOperations
    {
        private int iIValue = 0;
        private int iJValue = 0;

        public CMyComplexNumber(int _iIValue, int _iJValue)
        {
            iIValue = _iIValue;
            iJValue = _iJValue;
        }

        //Implementing the operator+ from CMyOperations.
        //This must return the containing type (CMyComplexNumber), 
        //so that the cast in CMyGenericClass<T> is successful.
        public override CMyOperations AddMyOperations(CMyOperations _ITwo)  
        {
            if(_ITwo is CMyComplexNumber)
            {
                CMyComplexNumber numberNew = new CMyComplexNumber(0, 0);
                CMyComplexNumber number2 = _ITwo as CMyComplexNumber;

                numberNew.iIValue = iIValue + number2.iIValue;
                numberNew.iJValue = iJValue + number2.iJValue;

                return numberNew;
            }
            else
            {
                System.Diagnostics.Debug.Assert(false, 
                    "CMyComplexNumber.CMyOperations.AddMyOperations() 
                    was invoked for an unsupported parameter type.");
            }

            return null;
        }

        public override string  ToString()
        {
              return String.Format("{0}i {1}j", iIValue, iJValue);
        }
    }

We can now write generic code, which uses C# operators on the type parameter 'T':

//the parameter type 'T' must inherit from CMyOperations, 
//and so must provide the operators.  
//This is an indirect way of specifying operator constraints on T 
    class CMyGenericClass<T> where T : CMyOperations 
    {
        public T TestUsingOperatorsOnT(T _one, T _two)
        {
//We need to cast from CMyOperations to T.  
//This is safe enough, provided that T implements its 
//AddMyOperations() method to return an object of its own type.
//Normally, this use of operator overloading would not be allowed by the compiler
            T tTemp = (T)(_one + _two); 

            return tTemp;
        }
    };

Points of Interest

  1. There were a few other ways I tried to allow generic code to use operators - thought about using interfaces - however, we cannot have operators inside interfaces.
  2. Using the 'where' keyword is an indirect way of specifying operator constraints on the type 'T'.

History

  • Submitted on 1st October, 2006