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?
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.
+=
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.
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.
Its done using "operator overloading", you can do this in your own objects aswell.
public class MyObject
{
public string Property { get; set; }
public MyObject(string property)
{
this.Property = property;
}
public static MyObject operator +(MyObject a1, MyObject a2)
{
return new MyObject(a1.Property + a2.Property);
}
}
MyObject o1 = new MyObject("Hello");
MyObject o2 = new MyObject(" World!");
MyObject o3 = o1 + o2;
Result: o3.Property = "Hello World!"
So I assume the framwork performs somthing like this in the background
public static Action operator +(Action a1, Action a2)
{
return (Action)MulticastDelegate.Combine(a1,a2);
}