C# language feature:

  • Co- and Contra-variance for delegates

applies to

  • delegates

benefit

  • The method passed to a delegate may now have greater flexibility in its return type and parameters.

  • 1) Covariance permits assignment of a method to have a more derived RETURN TYPE than what is defined in the delegate. I.e. when a delegate returns an object, a derived type can be converted/substituted for a base/antecedent type
  • 2) Contravariance permits assignement of a method with parameter types that are less derived than in the delegate type. I.e. when invoking a delegate, a derived parameter can be converted/substituted as a base/antecedent parameter.

note

  • Substitution will always make sense. Keep in mind that it is an assignment of delegate signature and the return or parameter which can be sensibly replaced by an antecedent class type. Due to the difficulty/complexity of this topic, more comments and additional code are provided to clarify co and contra variance concept beyond a minimal how to use.

how to use CoVariance

  • Substitution of base to decendent/derived in RETURN TYPE.
  • 1) Create a base class, derived class from base.
  • 2) Define a delegate RETURNING base class.
  • 3) Create 2 methods, a) RETURNING base class and b) RETURNING derived class.
  • 4) The delegate can accept the derived method RETURNING derived class (delegate definition defined with BASE class).

how to use ContraVariance

  • Substitution of defined/derived to antecedent/base in PARAMETER TYPE.
  • 1) Create a base class, derived class from base.
  • 2) Define a delegate with DERIVED class as PARAMETER.
  • 3) Create 2 methods, a) with base class as PARAMETER and b) with derived class as PARAMETER.
  • 4) The delegate can accept the method with BASE class as PARAMETER (where delegate definition is defined with DERIVED class).

ContraVariance note

  • A PARAMETER object as defined in delegate signature (or more derived/descendent) is required when calling an ASSIGNED delegate where BASE/antecedent class is PARAMETER! This allows a sensible defined or antecedent class substitution.

example:

    // declare base class and derived classes
    class mybase
    {
    }
    class derived : mybase
    {
    }
    class derived2times : derived
    {
    }

    class CoVariance_Example
    {
        // declare delegates with varying returned typess
        public delegate mybase Delegate_Returns_Base_Type();  // accepts methods with base/defined and derivitive return types! 
        public delegate derived Delegate_Returns_Derived_Type();  // accepts methods with derived/defined and derivitive return types! 
        public delegate derived2times Delegate_Return_Derived2Times_Type();  // accepts methods with derived2times/defined and derivitive return types if any! 

        // declare methods/functions
        public static mybase MethodReturnsBase()
        {
            return new mybase();
        }
        public static derived MethodReturnsDerived()
        {
            return new derived();
        }
        public static derived2times MethodReturnsDerived2times()
        {
            return new derived2times();
        }

        void Use_of_CoVariance_Example()
        {   // assign some delegates
            // NOTICE- delegate type CAN accept signatures with returnr objects from base/defined and derivatives i.e.  descendants, this is co-variance!
            Delegate_Returns_Base_Type handler_accepts_base_return = MethodReturnsBase;
            Delegate_Returns_Base_Type handler_accepts_base_return2 = MethodReturnsDerived;
            Delegate_Returns_Base_Type handler_accepts_base_return3 = MethodReturnsDerived2times;

            // Delegate_Returns_Derived_Type handler_accepts_derived_return = MethodReturnsBase;  // illegal can not accept less derived type than Delegate_Returns_Derived_Type
            Delegate_Returns_Derived_Type handler_accepts_derived_return2 = MethodReturnsDerived;
            Delegate_Returns_Derived_Type handler_accepts_derived_return3 = MethodReturnsDerived2times;

            //Delegate_Return_Derived2Times_Type handler_accepts_derived2times_return = MethodReturnsBase;  // illegal can not accept less derived type than Delegate_Return_Derived2Times_Type
            //Delegate_Return_Derived2Times_Type handler_accepts_derived2times_return2 = MethodReturnsDerived; //
            Delegate_Return_Derived2Times_Type handler_accepts_derived2times_return3 = MethodReturnsDerived2times;
        }
    }

    class ContraVariance_Example
    {
        // declare delegates with varying parameter types
        public delegate mybase Delegate_Accepts_Base_Parameter(mybase c);  // very limited accepts methods with base parameters defined! 
        public delegate mybase Delegate_Accepts_Derived_Parameter(derived c);  // accepts methods with (derived or base) parameters defined  
        public delegate mybase Delegate_Accepts_Derived2Times_Parameter(derived2times c);  // accepts methods with (derived2times or derived or base) parameters defined  

        static event Delegate_Accepts_Derived_Parameter onEventDerived;  // declare an event

        // declare methods/functions
        public static mybase MethodAcceptsBase(mybase b)
        {
            return b;
        }
        public static mybase MethodAcceptsDerived(derived d)
        {
            return d as mybase;
        }
        public static mybase MethodAcceptsDerived2times(derived2times d2)
        {
            return d2 as mybase;
        }
        void Use_of_ContraVariance_Example()
        {   // assign some delegates
            Delegate_Accepts_Base_Parameter handler_accepts_base_parameter = MethodAcceptsBase;  // legal
            // NOTICE- delegate type can NOT accept signatures with derived parameter objects (i.e. descendents like below!) 
            // Delegate_Accepts_Base_Parameter handler_accepts_base_parameter2 = MethodAcceptsDerived;  // illegal cannot accept more derived parameter signature than Delegate_Accepts_Base_Parameter
            // Delegate_Accepts_Base_Parameter handler_accepts_base_parameter3 = MethodAcceptsDerived2times;  // illegal cannot accept more derived parameter signature than Delegate_Accepts_Base_Parameter

            // ***
            // NOTICE- delegate type CAN accept signatures with parameter objects from defined to base i.e.  ancestors, this is contra-variance!
            Delegate_Accepts_Derived_Parameter handler_accepts_derived_parameter = MethodAcceptsBase;  //legal
            Delegate_Accepts_Derived_Parameter handler_accepts_derived_parameter2 = MethodAcceptsDerived;  //legal
            //Delegate_Accepts_Derived_Parameter handler_accepts_derived_parameter3 = MethodAcceptsDerived2times;  // illegal cannot accept more derived parameter signature than Delegate_Accepts_Derived_Parameter

            // All legal  by contravariance!  that is, DELEGATE accepts antecedent signature methods
            Delegate_Accepts_Derived2Times_Parameter handler_accepts_derived2times_parameter = MethodAcceptsBase; 
            Delegate_Accepts_Derived2Times_Parameter handler_accepts_derived2times_parameter2 = MethodAcceptsDerived;
            Delegate_Accepts_Derived2Times_Parameter handler_accepts_derived2times_parameter3 = MethodAcceptsDerived2times;

            // On to events...
            onEventDerived += new Delegate_Accepts_Derived_Parameter(handler_accepts_base_parameter); //legal 
            onEventDerived += new Delegate_Accepts_Derived_Parameter(handler_accepts_derived_parameter); //legal
            //onEventDerived += new Delegate_Accepts_Derived_Parameter(handler_accepts_derived2times_parameter);  // illegal signature must be less derived! 
            
            //onEventDerived.Invoke(new mybase()); //illegal as parameter can not be up derived!
            onEventDerived.Invoke(new derived());
            onEventDerived.Invoke(new derived2times());

            // In conclusion,  in explaining contra-variance of event handlers, the event handlers  
            // are able to assign/use contra-variance to call a delegate with an base/antecedent signature
            // also note, a more "derived signature" would not make sense as that implies a derived/descendent
            // object that the handler does not "understand" or is capable of producing.
        }
    }