#1,158 – Events vs. Delegates, part I

You’ll typically define an event rather than using a public delegate.  Although either method will work, events provide some benefits over using delegates directly.

Below, we define a delegate type and a Barked event of that type in a Dog class:

    public delegate void DogDelegate(string dogName);

    public class Dog
    {
        public string Name { get; set; }

        public Dog(string name)
        {
            Name = name;
        }

        public event DogDelegate Barked = delegate { };

        public void Bark()
        {
            Console.WriteLine("Woof");
            Barked(Name);
        }
    }

We can now handle the event as follows:

        static void Main(string[] args)
        {
            Dog d = new Dog("Bob");
            d.Barked += d_Barked;
            d.Bark();

            Console.ReadLine();
        }

        static void d_Barked(string dogName)
        {
            Console.WriteLine(dogName + " just barked");
        }

1158-001
This code would all work the same if we simply removed the event keyword, making the delegate available to the client code.  (See part II for more information).

#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>.

#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);
        }