Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does using Delegate.Combine require a cast but using the + operator doesn't?

Tags:

c#

delegates

I couldn't find the + operator in Reflector under Delegate or MulticastDelegate.

I'm trying to figure out how this doesn't need a cast:

Action a = () => Console.WriteLine("A");
Action b = () => Console.WriteLine("B");

Action c = a + b;

But this does:

Action a = () => Console.WriteLine("A");
Action b = () => Console.WriteLine("B");

Action c = (Action)MulticastDelegate.Combine(a, b);

In the first sample is the cast just done under the covers?

like image 793
Ryan Peschel Avatar asked Nov 25 '12 09:11

Ryan Peschel


People also ask

Are all delegates multicast?

Delegates in . NET are multicast delegates. Regardless of whether you choose to attach zero or one or several handlers to them, they are still multicast delegates.

Which of the following operator is used to remove a delegate?

The "-" operator can be used to remove a component delegate from a composed delegate.

What is Action delegate in C#?

C# - Action Delegate Action is a delegate type defined in the System namespace. An Action type delegate is the same as Func delegate except that the Action delegate doesn't return a value. In other words, an Action delegate can be used with a method that has a void return type.


3 Answers

+= and -= is implemented at the language level (i.e. with compiler help) with known delegate types, so it doesn't need a cast, whereas Delegate.Combine is just an ordinary (non-generic) method with Delegate return type, so it needs a cast.

like image 167
user541686 Avatar answered Oct 13 '22 10:10

user541686


Taking the following example:

class Program
{
    static void Main(string[] args)
    {
        Test1();
        Test2();
    }

    public static void Test1()
    {
        Action a = () => Console.WriteLine("A");
        Action b = () => Console.WriteLine("B");

        Action c = a + b;
        c();
    }

    public static void Test2()
    {
        Action a = () => Console.WriteLine("A");
        Action b = () => Console.WriteLine("B");

        Action c = (Action)MulticastDelegate.Combine(a, b);
        c();
    }
}

Then looking at it with ILSpy:

internal class Program
{
    private static void Main(string[] args)
    {
        Program.Test1();
        Program.Test2();
    }
    public static void Test1()
    {
        Action a = delegate
        {
            Console.WriteLine("A");
        };
        Action b = delegate
        {
            Console.WriteLine("B");
        };
        Action c = (Action)Delegate.Combine(a, b);
        c();
    }
    public static void Test2()
    {
        Action a = delegate
        {
            Console.WriteLine("A");
        };
        Action b = delegate
        {
            Console.WriteLine("B");
        };
        Action c = (Action)Delegate.Combine(a, b);
        c();
    }
}

Seems that they do the exact same thing, just C# is providing some syntactic sugar in the first test.

like image 25
armen.shimoon Avatar answered Oct 13 '22 09:10

armen.shimoon


In the first sample is the cast just done under the covers?

Yes, you can say that!

The Combine method was written in .NET 1 where no generic C# existed. The formal return type of Combine therefore had to be Delegate:

public static Delegate Combine(Delegate a, Delegate b)
{
  ...
}

But the method still returns an Action when both a and b are Action. And yes, a and b are required to have the same runtime type.

Please don't write MulticastDelegate.Combine as Combine is a static method defined by the System.Delegate class. Therefore say Delegate.Combine, that's less confusing.

Detour:

The current version of C# and Combine has problems with contravariant delegate types. Consider the following:

Action<string> doSomethingToString; // will be assigned below

Action<ICloneable> a = cloneable => { Console.WriteLine("I'm cloning"); cloneable.Clone(); }
Action<IConvertible> b = convertible => { Console.WriteLine("I'm converting"); convertible.ToInt32(CultureInfo.InvariantCulture); }

Action<string> aStr = a; // OK by contravariance of Action<in T>, aStr and a reference same object
Action<string> bStr = b; // OK by contravariance of Action<in T>, bStr and b reference same object

doSomethingToString = aStr + bStr;  // throws exception
doSomethingToString("42");          // should first clone "42" then convert "42" to Int32

Now, suppose some future version of the framework introduced a generic Combine method:

public static TDel Combine<TDel>(TDel a, TDel b) where TDel : Delegate
{
  // use typeof(TDel) to figure out type of new "sum" delegate
}

and suppose C# was changed such that + was translated into a call to the new generic Combine<> method, then contravariance and delegate combination would be fixed! I guess they tell us they have higher priorities, but still.

like image 40
Jeppe Stig Nielsen Avatar answered Oct 13 '22 09:10

Jeppe Stig Nielsen