#1,157 – Contravariance and Generic Delegate Types

As with generic interfaces, generic delegate types are contravariant if you mark input parameters as in.

In the example below, we’re unable to assign an instance of ReturnDelegate<Dog> to ReturnDelegate<Terrier>.  The delegate type is not contravariant.

        public delegate void ReportOnDelegate<T>(T param);

        public static void ReportOnDog(Dog d)
        {
            Console.WriteLine(d.Name);
        }

        static void Main(string[] args)
        {

            ReportOnDelegate<Dog> del = ReportOnDog;

            // Error: Cannot implicitly convert ReportOnDelegate<Dog>
            // to ReportOnDelegate<Terrier>
            ReportOnDelegate<Terrier> del2 = del;
        }

We can get the delegate type to behave contravariantly by marking its type parameter with the in keyword.

        public delegate void ReportOnDelegate<in T>(T param);

We can now assign an instance of ReportOnDelegate<Dog> to a variable of type ReportOnDelegate<Terrier>.

Advertisement

#1,156 – Covariance and Generic Delegate Types

As with generic interfaces, generic delegate types are covariant if you mark output parameters as out.

In the example below, we’re unable to assign an instance of ReturnDelegate<Terrier> to ReturnDelegate<Dog>.  The delegate type is not covariant.

        public delegate T ReturnDelegate<T>();

        public static Terrier GenerateTerrier()
        {
            return new Terrier("Bubba");
        }

        static void Main(string[] args)
        {
            ReturnDelegate<Terrier> del = GenerateTerrier;

            // Error: Cannot implicitly convert ReturnDelegate<Terrier>
            // to ReturnDelegate<Dog>
            ReturnDelegate<Dog> del2 = del;
        }

We can get the delegate type to behave covariantly by marking its type parameter with the out keyword.

        public delegate T ReturnDelegate<out T>();

We can now assign an instance of ReturnDelegate<Terrier> to a variable of type ReturnDelegate<Dog>.

#1,155 – Return Value Compatibility when Assigning to Delegate Types

When you assign a method to an instance of a delegate, input parameters in the delegate may be more specific than corresponding parameters in the method.

Similarly, the return type of the delegate can be less specific (ancestor of) the return type of the method.

In the example below, assume that Terrier derives from Dog.

        public delegate Dog ReturnDogDelegate();

        public static Terrier GenerateTerrier()
        {
            return new Terrier("Bubba");
        }

        static void Main(string[] args)
        {
            // Return type of method can be more
            // specific than return type of delegate type
            ReturnDogDelegate del = GenerateTerrier;

            // Invoke through delegate - Method 1
            Terrier t = (Terrier)del();

            // Invoke, Method 2
            Dog d = del();
        }

Note that if we invoke through the delegate, it returns an instance of the parent class.  To assign to the derived class, we need an explicit cast.

#1,154 – Input Parameter Compatibility when Assigning to Delegate Types

When you assign a method to an instance of a delegate type (or pass a method name to a method that takes a delegate), the types of the parameters in the method will normally exactly match the types in the declaration of the delegate type.

You can, however, assign a method when the type of an input parameter for a delegate is derived from the corresponding parameter type in the method.  This is shown in the second chunk of code, below.  When you invoke using the delegate, however, you’ll still need to pass an object whose type is the more derived type.

            // Delegate/Method = string/string
            AcceptString asd1 = StringMethod;
            asd1("s");  // ok
            // asd1(new object());    // Error

            // Delegate/Method = string/object
            //   Assignment works, but must invoke with string
            asd1 = ObjectMethod;     // ok
            //asd1(new object());    // Error
            asd1("s");               // works--string converted 

            // Delegate/Method = object/object
            AcceptObject asd2 = ObjectMethod;
            asd2("s");               // ok
            asd2(new object());      // ok

            // Delegate/Method = object/string
            //   Asignment does not work
            // AcceptObject asd3 = StringMethod;   // Error

#1,153 – Different Delegate Types Are Not Assignment Compatible

Different delegate types are not assignment compatible, even if they define the same method signature.

For example, suppose we have two delegate types with the same signature and a method that matches the signature.

        public delegate void DogDelegate(Dog d);
        public delegate void DogDel2(Dog d);

        public static void UseDog(Dog d) 
        {
            // do something with dog
        }

Notice below that we can’t assign an instance of DogDelegate to a variable of type DogDel2, even if we use a cast.

            DogDelegate d1 = UseDog;

            // Another instance of same delegate type
            // is assignment compatible
            DogDelegate d2 = d1;  // works

            // Assignment to a different delegate type
            // is NOT allowed
            DogDel2 d3 = d1;  // Error: Can't convert

            // Cast doesn't work either
            DogDel2 d4 = (DogDel2)d1;  // Error

1153-001

#1,152 – The Action Delegate Type

The .NET Framework includes the Action predefined delegate type that represents the signature of a method that accepts zero or more parameters and does not have a return value (i.e. returns void).

Overloads of Action are :

  • delegate void Action()
  • delegate void Action(T p1)
  • delegate void Action(T1 p1, T2 p2)
  • etc…

You might define your own delegate type:

        public delegate void Merger<T1,T2>(T1 thing1, T2 thing2);

        static void MergeIntAndString<T1,T2>(Merger<T1,T2> myMerger, T1 n, T2 s)
        {
            myMerger(n, s);
        }

        public static void ConcatIntString(int n, string s)
        {
            Console.WriteLine("{0}{1}", n, s);
        }

        static void Main(string[] args)
        {
            MergeIntAndString(ConcatIntString, 42, "Adams");
        }

However, you could instead use the Action delegate type, avoiding the definition of your own custom delegate type.

        static void MergeIntAndString<T1,T2>(Action<T1,T2> myMerger, T1 n, T2 s)
        {
            myMerger(n, s);
        }

#1,151 – The Func Delegate Type

The .NET Framework includes the Func predefined delegate type, which represents the signature of a method that returns some type and accepts zero or more parameters.

Overloads of Func are :

  • delegate TResult Func<out TResult>()
  • delegate TResult Func<in T, out TResult>(T p1)
  • delegate TResult Func<in T1, in T2, out TResutl>(T1 p1, T2 p2)
  • etc…

You might define your own delegate type:

        public delegate T Merger<T>(T thing1, T thing2);

        static void MergeSomething<T>(Merger<T> someMerger, T thing1, T thing2)
        {
            T result = someMerger(thing1, thing2);
        }

However, you could instead use the Func delegate type, avoiding the definition of your own custom delegate type.

        static void MergeSomething2<T>(Func<T, T, T> someMerger, T thing1, T thing2)
        {
            T result = someMerger(thing1, thing2);
        }

Func<T,T,T> indicates that we’ll pass in a function that takes two parameters of a given type and returns a value of the same type.

#1,150 – Generic Delegate Type as Parameter

You can use a generic delegate type as a parameter to a method.

Suppose that we have the following type:

        public delegate T Merger<T>(T thing1, T thing2);

We can now create a method that takes a constructed version of this type as a parameter.  We could then call the method as shown in the Main method.

        static void MergeInts(Merger<int> intMerger, int n1, int n2)
        {
            int result = intMerger(n1, n2);
        }

        public static int Add(int n1, int n2)
        {
            return n1 + n2;
        }

        static void Main(string[] args)
        {
            MergeInts(Add, 5, 6);
        }

We could also define a generic method that has a generic delegate parameter.

        static void MergeSomething<T>(Merger<T> someMerger, T thing1, T thing2)
        {
            T result = someMerger(thing1, thing2);
        }

We can then call this method as follows:

        public static Dog Breed(Dog mama, Dog papa)
        {
                return new Dog(mama.Name + papa.Name);
        }

        static void Main(string[] args)
        {
            MergeSomething(Add, 5, 6);

            Dog dog1 = new Dog("Lassie");
            Dog dog2 = new Dog("Spuds");
            MergeSomething(Breed, dog1, dog2);
        }

#1,149 – Generic Delegate Types

A delegate type can be generic.  Consider the following delegate type:

        public delegate T Merger<T>(T thing1, T thing2);

This delegate type defines a pattern for methods that take two parameters of a particular type and returns the same type.  Below are a couple examples of methods whose signature matches this delegate type.  (Methods are only static so that we can call them from another static method).

        public static int Add(int n1, int n2)
        {
            return n1 + n2;
        }

        public static Dog Breed(Dog mama, Dog papa)
        {
                return new Dog(mama.Name + papa.Name);
        }

We can now declare Merger<T> instances that refer to these methods as follows:

            Merger<int> adder = Add;
            Merger<Dog> breeder = Breed;

#1,128 – Disable a Compile-Time Warning for Entire Project

You can use #pragma warning to selectively disable certain warnings that occur when compiling your code.  When #pragma warning disable is encountered, the specified warning will be disabled for the current file, from the point where the pragma appears until the end of the file, or until a #pragma warning restore is encountered for the same warning.

If you want to disable a particular warning for all files compiled in a project, rather than just within a single file, you can add the warning number to the Suppress warnings field on the Build tab in the project properties window.  This will suppress the specified warning for the entire project.

1128-001

1128-002