views:

278

answers:

7

Both are delegates and have the same signature, but I can not use Action as ThreadStart.

Why?

Action doIt;
doIt = () => MyMethod("test");
Thread t;

t = new Thread(doIt);
t.Start();

but this seams to work:

Thread t;

t = new Thread(() => MyMethod("test"));
t.Start();
A: 

Try:

t = new Thread(new ThreadStart(doIt));

or

t = new Thread( ()=>MyMethod("test"));

You're trying to pass an argument of type "Action" to a constructor that takes a ThreadStart. There isn't an implicit conversion defined, so you need to manually invoke the ThreadStart constructor.

Alan
+3  A: 

Delegates with the same signature are not the same in the eyes of the CLR - they are entirely different types.

Zach Johnson
+1  A: 

Because thread start is a separate delegate type and Action cannot be converted to ThreadStart.

This case works because here your lambda is treated by compiler as ThreadStart:

Thread t;

t = new Thread(() => MyMethod("test"));
t.Start();
Andrew Bezzub
+4  A: 

The behaviour your are noticing is because Action is a type and the 'lambda' in your second, working example, is not.

Arguably, they are the same, but you are using a concrete framework type when using Action, whereas the compiler infers the signature of the lambda.

EDIT: to be clear:

Action is a delegate type, which is not a ThreadStart delegate, so while you can assign the lambda expression to both, you cannot use both to start a thread.

In the second example you are giving the compiler the opportunity to infer the type of delegate and, with no great surprise, it is able to assign the lamda expression to the type ThreadStart.

Sky Sanders
The lambda shouldn't be a type, it is a value assignable to the Action/ThreadStart types. The lambda is not involved in the error.
Henk Holterman
It absolutely is, Henk. In the latter example, the lambda is inferred to be a ThreadStart, while in the former, it is explicitly typed as an Action. The latter is compatible with one of the Thread class constructors, while the former is not.
Chris
@Henk, ? are you agreeing with me or implying that I stated otherwise? not sure....
Sky Sanders
No, I was not agreeing with you. As i said, the lambda is not involved, the error is in `new Thread(doIt)`.
Henk Holterman
@Henk, So, in that you left the comment on my answer I must infer that you think I said otherwise. Let me be more clear.
Sky Sanders
+2  A: 

I believe this should work?

Action doIt;
doIt = () => MyMethod("test");
Thread t;

t = new Thread(doIt.Invoke);
t.Start();
Spencer Ruport
yes this works. BUT! WHY? And why can I call Invoke with out brackets?Since when C# methods can be invoked in the VB style :D?
Rookian
@Rookian: `doIt.Invoke` is automatically wrapped in a `ThreadStart` delegate. It is not actually invoking `Invoke`. This is the same behavior you see with any other method that returns void and has no parameters.
Zach Johnson
@Rookian: First, I'd imagine there's no implicit cast from Action to a delegate because you'd be casting to a type that carries less data. There's nothing stopping you from writing an implicit cast anyway but the .Net designers must not have felt like it was a good idea to include this standard. Second you're not invoking the function when you don't include the parenthesis, you're passing the method group which has an implicit cast to the ThreadStart delegate.
Spencer Ruport
+10  A: 

As others have noted, the problem is that delegate types are not "structural". That is, they do not have equivalence based on their "structure".

Now, this is arguably a good thing for some types. If you have

struct MyRectangle { int x; int y; int width; int height; ... }

and

struct YourRectangle { int x1; int y1; int x2; int y2; ... } 

obviously it would be a mistake to allow instances of MyRectangle to be assigned to variables of YourRectangle, just because they both consisted of four ints. The semantics of the ints are different and therefore the types are not equivalent.

The same is, in theory, true of delegates. You could have

delegate int Pure(string x);
delegate int Func(string x);

where a "pure" function is one with no side effects and the same output given the same input. Since every Pure is logically a Func, but every Func is not necessarily a Pure, there shouldn't be structural typing between them.

In practice of course the type system does not support notions like "pure function" very well. And in practice, the vast majority of attempts to convert between delegate types are perfectly safe: converting from a Func<int, bool> to a Predicate<int> and so on.

So, two things, one looking backwards and one looking forwards. Backwards: if we had to do it all over again, I think delegates would probably be structurally typed in the CLI. You don't always know what features are going to be useful when you design a brand new framework, and non-structural delegate types have thus far turned out to be not as useful as perhaps anticipated. Forwards: I expect to see more features in future versions of the CLR that enable more structural typing. The "no pia" feature in C# 4, for example, is about making two types that are semantically and structurally the same, but defined in different assemblies, logically unify structurally.

Eric Lippert
Any chance delegates could become structural, i.o.w. would that break anything?
Henk Holterman
Sure, all kinds of things could break. That would be a massive breaking change. *Any* change to the type conversion rules causes a breaking change. If you make what used to be legal illegal, obviously that is a breaking change. If you make something that used to be illegal legal, then you might introduce an ambiguity in overload resolution.
Eric Lippert
+2  A: 

I think the following verbiage in section 26.3.1 of the C# Language Specification is important:

Similar to an anonymous-method-expression, a lambda-expression is classified as a value with special conversion rules. The value does not have a type but can be implicitly converted to a compatible delegate type. Specifically, a delegate type D is compatible with a lambda-expression L provided:
[List elided]

Which prevents code like this from compiling:

var doIt = () => MyMethod("test");    // CS0815

Which leads to:

Action<object> doIt = (o) => MyMethod("test");
t = new Thread((ParameterizedThreadStart)doIt);  // CS0030

Defeated by the compiler disallowing conversions from one delegate type to another, even if their signatures are compatible. Forcing:

ParameterizedThreadStart doIt = (o) => MyMethod("test");
t = new Thread(doIt);

Which compiles without trouble.


Irrelevant bonus feature: early usability tests on the first Apple Mac discovered a problem with the first version of the OK button on the dialogs that used a sans-serif font. Users were often clicking Cancel instead, with some visible anguish. After several interviews, one user finally admitted the problem: "I hate it when a computer calls me a Dolt!"

Well, a compiler calling you a dolt perhaps isn't that irrelevant :)

Hans Passant