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

Advertisement

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

#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,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,022 – How to Use Return Values from All Delegate Instances

If a delegate instance has an invocation list with multiple methods and you invoke the delegate using the standard functional notation, you’ll get back the return value only for the last method in the invocation list.

If you want to consume the return value for each method in a delegate’s invocation list, you can use GetInvocationList to explicitly iterate through the invocation list.

            ReturnIntDelegate del1 = Method1; // Returns 12
            del1 += Method2;  // Returns 99

            // Invoke all at once, get return value
            // only from last
            Console.WriteLine("Invoke normally");
            int val = del1();
            Console.WriteLine(val);

            // Invoke one at a time
            Console.WriteLine("Invoke one at a time");
            foreach (ReturnIntDelegate del in del1.GetInvocationList())
            {
                int ret = del.Invoke();
                Console.WriteLine(ret);
            }

1022-001