11

I have defined the following delegate types. One returns a string, and one an object:

delegate object delobject();
delegate string delstring();

Now consider the following code:

delstring a = () => "foo";
delobject b = a; //Does not compile!

Why is the assignment not valid ?

I do not understand. A method which returns a string should be safely considered as a method which returns an object (since a string is an object).


In C# 4.0, the following example works. Instead of using delegates, I use the Func<TResult> generic type:

Func<string> a = () => "foo";
Func<object> b = a; //Perfectly legal, thanks to covariance in generics

Also: if I rewrote it that way, it works:

delobject b = () => a();

But this is not the same thing as what I wanted initially. Now I have created a new method which calls another one.

It is not just an assignment, as shown in this example:

delint a = () => 5;
delobject b = a; //Does not work, but this is OK, since "int" is a value type.
delobject b = () => a(); //This will box the integer to an object.
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
tigrou
  • 4,236
  • 5
  • 33
  • 59
  • How would it work if the delegates accepted parameters? I'm guessing for consistency the language designers decided the rules should be the same when there are no parameters. – JohnLBevan Feb 28 '15 at 15:03
  • @JohnLBevan The same way? `Func` can implicitly be assigned to a `Func`. –  Feb 28 '15 at 15:05
  • "Instead of using delegates" isn't entirely accurate, since `Func` is a generic **delegate** type. – Ben Voigt Feb 28 '15 at 22:53

3 Answers3

11

Delegate types are still concrete object types. When you write

delegate object delobject();
delegate string delstring();

then there is no "is a" relation between the two delegate types. You've simply created two distinct types.

It's just like

class A { public int i; };
class B { public int i; };

There is no implicit or explicit conversion between A and B, even though there isn't any possible A that wouldn't make equal sense as a B, or vice versa.

Co- and contravariance in Func means that the authors of that concrete delegate type have decided that Func<string> may be treated as Func<object>, but that's something the author of the delegate type gets to decide, just like it's the author of my class B that gets to decide whether it would perhaps make more sense to just derive from A.

Something you can do is add one more level of indirection without creating an additional method:

delstring a = () => "foo";
delobject b = new delobject(a); // or equivalently, a.Invoke
  • 1
    If it's a single cast delegate and you have sufficient permissions, you can also use `(delobject)Delegate.CreateDelegate(typeof(delobject), a.Target, a.Method)`, avoiding the indirection. – CodesInChaos Feb 28 '15 at 16:42
  • @CodesInChaos You're right, but whether it's worth the effort doesn't just depend on whether you have sufficient permissions, but also on how often the delegate will be called, doesn't it? What you have is a more expensive way of creating a delegate that will be cheaper to call. –  Feb 28 '15 at 16:47
  • @CodesInChaos: FWIW, the counterpart to "multicast" delegate is "unicast", not "single cast" – Ben Voigt Feb 28 '15 at 22:52
5

Type arguments for the Func are marked in and out, so you can assign these types one to another like you do. This is what variance in generics means.

Your delobject and delstring are different types and they are not variant, so you cannot assign one to another. But to the delobject you can assign a handler that returns derived type, this is what variance in delegates means.

aush
  • 2,108
  • 1
  • 14
  • 24
3

Delegates (MSDN):

In the context of method overloading, the signature of a method does not include the return value. But in the context of delegates, the signature does include the return value. In other words, a method must have the same return type as the delegate.

Since the return type does not exactly match (i.e. object != string), the assignment is invalid.

So you could have

delstring a = () => "foo"; // compiles
delobject b = a;           // does not compile
delobject c = () => "foo"; // compiles

As you pointed out, Func works covariantly using out, whereas the delegates don't.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
  • 1
    That documentation is misleading, a method doesn't need to have the same return type as the delegate. You can convert a method returning `string` to `delobject`. Just try `static class Program { delegate object D(); static string f() { return "Hello, world!"; } static void Main() { D f = Program.f; Console.WriteLine(f()); } }` for a simple example. –  Feb 28 '15 at 15:12
  • @hvd you're right, the documentation is a little misleading. My `c` example compiles because `object` is [assignable from](https://msdn.microsoft.com/en-us/library/system.type.isassignablefrom) `string` (or, to be fair, anything else) - your example works in the same way. – Wai Ha Lee Feb 28 '15 at 15:16
  • The requirements are a bit stricter than "assignable from", but that does get close. (It wouldn't work for any type where the implicit conversion performs some operation, not even if that operation is simply boxing.) [Using Variance in Delegates](https://msdn.microsoft.com/en-gb/library/ms173174.aspx) gives an example too. –  Feb 28 '15 at 15:19