12

My morbid curiosity has me wondering why the following fails:

// declared somewhere
public delegate int BinaryOperation(int a, int b);

// ... in a method body
Func<int, int, int> addThem = (x, y) => x + y;

BinaryOperation b1 = addThem; // doesn't compile, and casting doesn't compile
BinaryOperation b2 = (x, y) => x + y; // compiles!
nawfal
  • 70,104
  • 56
  • 326
  • 368
Hobbes
  • 370
  • 2
  • 7

2 Answers2

17

C# has very limited support for "structural" typing. In particular, you can't cast from one delegate-type to another simply because their declarations are similar.

From the language specification:

Delegate types in C# are name equivalent, not structurally equivalent. Specifically, two different delegate types that have the same parameter lists and return type are considered different delegate types.

Try one of:

// C# 2, 3, 4 (C# 1 doesn't come into it because of generics)
BinaryOperation b1 = new BinaryOperation(addThem);

// C# 3, 4
BinaryOperation b1 = (x, y) => addThem(x, y);
var b1 = new BinaryOperation(addThem);
Ani
  • 111,048
  • 26
  • 262
  • 307
  • Brilliant! Thank you! (... Need to wait 11 minutes to mark yours as the answer ...) – Hobbes Dec 17 '10 at 03:35
  • 12
    The reason for non-structural typing is because the delegate definition might have semantics. You shouldn't be able to assign a "Func" to a "SecureFunc", or a "PureFunc" or a "NoSideEffectsFunc" or whatever. In practice, no one actually does make delegate types that have semantics; if we had to do it all over again delegates would probably be more structurally typed. – Eric Lippert Dec 17 '10 at 07:19
  • @Eric Lippert: That's an interesting point. What is your view on why there are so many "duplicate" delegate-types with the same "structure" in the BCL, such as Action, ThreadStart, MethodInvoker? – Ani Dec 17 '10 at 07:26
  • 1
    @Eric Lippert: Also, why provide an implicit conversion from a method-group but not from a structurally compatible delegate-type? Assigning a "NoSideEffectsFunc" to a method-group that *does* have side-effects has the same issue doesn't it? This isn't something the language should be interfering with.. Or am I missing the point? – Ani Dec 17 '10 at 07:31
  • 2
    In that particular example one might further hypothesize that the compiler could identify side-effect-free methods and have special knowledge of the given delegate type, to warn if you do it wrong. But in general, at some point the developer has to take responsibility for using the tools correctly. – Eric Lippert Dec 17 '10 at 15:08
  • @Eric Lippert: Ah, I think I understand. The compiler could in theory reason about a *method group* (let's say it's marked with a [Pure] attribute), but not about an arbitrary delegate instance (of a different nominal type), since it's not always possible to tell what its target will be at run-time? – Ani Dec 18 '10 at 02:44
  • @EricLippert: I appreciate that it should certainly be possible to declare a delegate type which will not support typecasts to another. Would there be any particular problem with defining an "anonymous" for every possible signature, similar to what's done with tuples, and possibly allowing delegates tagged with a certain attribute to be considered interchangeable with the "anonymous" delegate for their signature? – supercat Apr 17 '12 at 17:44
  • 1
    @supercat: That would be a reasonable approach to structurally typed delegates; I don't see any technical problem per se. The question is, as always, whether or not creating such a system is in the top n of the CLR team's priority list. – Eric Lippert Apr 17 '12 at 18:06
  • @Eric: I personally define "delegates with semantic" quite often; it would be really disappointing if the delegates will suddenly become duck typed one day. – Vlad Mar 20 '13 at 23:11
  • 1
    @Vlad: You are the first person to tell me that! Can you give me some examples of delegate semantics that you'd like to encode? – Eric Lippert Mar 20 '13 at 23:57
8

Here's a similar question: why doesn't this compile?

// declared somewhere
struct Foo {
    public int x;
    public int y;
}

struct Bar {
    public int x;
    public int y;
}

// ... in a method body
Foo item = new Foo { x = 1, y = 2 };

Bar b1 = item; // doesn't compile, and casting doesn't compile
Bar b2 = new Bar { x = 1, y = 2 }; // compiles!

In this case it seems a little more natural for the cast to not work, but it's really the same reason.

configurator
  • 40,828
  • 14
  • 81
  • 115