为什么我不能将Action用作ThreadStart?

10

两者都是委托并具有相同的签名,但我不能将Action用作ThreadStart。

为什么?

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

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

但这似乎是有效的:
Thread t;

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

1
似乎从委托到Action有一个隐式转换, 但反过来就没有. 您可以尝试创建一个或者将t = new Thread(doIt);更改为t = new Thread(doIt.Invoke);, 可以获得相同的结果. - Spencer Ruport
8个回答

21

正如其他人所指出的那样,问题在于委托类型不是"结构性"的。也就是说,它们不具有基于它们的"结构"的等效性。

现在,这对于某些类型来说可能是件好事。如果你有...

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

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

显然,如果允许将MyRectangle的实例赋值给YourRectangle的变量,那就会犯错误,仅仅因为它们都由四个整数组成。这些整数的语义不同,因此类型并不相等。

理论上,委托也是如此。你可以有

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

"纯"函数是指没有副作用并且在给定相同输入时具有相同输出的函数。由于每个纯函数逻辑上都是一个函数,但不是每个函数都必然是纯函数,因此它们之间不应该有结构类型。

当然,在实践中,类型系统并不完全支持诸如“纯函数”之类的概念。而在实践中,将委托类型之间进行转换的绝大多数尝试都是完全安全的:例如从 Func<int,bool> 转换为 Predicate<int> 等等。

所以,回顾过去和展望未来有两件事情。回顾过去:如果我们必须重新设计一切,我认为委托在 CLI 中可能会被定义为结构类型。在设计全新的框架时,您并不总是知道哪些功能将会有用,至今非结构委托类型的使用也不像预期的那样有用。展望未来:我预计在 CLR 的未来版本中会看到更多的功能以实现更多的结构类型。例如,C# 4 中的“no pia”功能旨在使在不同程序集中定义的语义和结构相同的两种类型在逻辑上结构统一。


委托是否有可能成为结构性的,换句话说,这会破坏什么吗? - H H
当然,各种事情都可能出错。那将是一个巨大的破坏性变化。任何类型转换规则的更改都会导致破坏性变化。如果您使以前合法的内容非法,显然这是一种破坏性变化。如果您使以前非法的内容合法,则可能会在重载决策中引入歧义。 - Eric Lippert
通过在接受委托的所有API中添加接受适当的 Action / Func 变体的重载,可以轻松地前进很长的路程。尽管这是许多重载...... 作为额外的好处,预期的签名立即通过 IntelliSense 明显可见,无需任何其他 Visual Studio 调整。 - Roman Starkov
如果编译器能假定一个Action/Func类型并从那里继续,而不是发出错误CS1660(无法将lambda表达式转换为类型...),那就太好了。虽然这也可以通过辅助方法来解决。 - Roman Starkov
要不要添加一个可爱的Thread类构造函数重载,使用Action参数(而不是ThreadStart),这应该能满足很多人的需求。另外,怎么样添加一个作为扩展方法的构造函数呢?太疯狂了! - user734028

4

这个错误的基本形式是:

delegate void d1();
delegate void d2();
d1 a;
d2 b;
b = a;

Error Cannot implicitly convert type 'ConsoleDemo1.d1' to 'ConsoleDemo1.d2'

所以,您可以通过使用正确的委托类型来“解决”第一个示例:
//Action doIt;
ThreadStart doIt;
doIt = () => MyMethod("test");
Thread t = new Thread(doIt);

这不是我看到的。我看到一个委托到操作的隐式转换,但没有从操作到委托的隐式转换。 - Spencer Ruport
@Spencer,Action是一个特定的委托。 - H H

4
我相信这应该有效?
Action doIt;
doIt = () => MyMethod("test");
Thread t;

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

1
是的,这个可以工作。但是为什么?为什么我可以不用括号调用Invoke? 自从什么时候C#方法可以以VB风格被调用了:D? - Rookian
@Rookian:doIt.Invoke 会自动包装在 ThreadStart 委托中。它实际上并没有调用 Invoke。这与任何其他返回 void 并且没有参数的方法的行为相同。 - Zach Johnson
@Rookian:首先,我想象中没有从Action到委托的隐式转换,因为你要将其转换为携带更少数据的类型。无论如何,没有什么阻止你编写一个隐式转换,但是 .Net 设计者可能认为包含这个标准不是一个好主意。第二,如果您不包括圆括号,则不会调用函数,而是传递具有ThreadStart委托的方法组,该方法组具有隐式转换。 - Spencer Ruport

4
您注意到的行为是由于“Action”是一种类型,而您第二个有效示例中的“lambda”不是。
可以说它们是相同的,但是当使用“Action”时,您正在使用具体的框架类型,而编译器会推断lambda的签名。
编辑:要清楚:
Action是委托类型,不是 ThreadStart 委托,因此虽然可以将lambda表达式分配给两者,但不能同时使用它们来启动线程。
在第二个示例中,您为编译器提供了推断委托类型的机会,并且毫不意外地,它能够将lamda表达式分配给ThreadStart类型。

Lambda不应该是一种类型,它是可分配给Action/ThreadStart类型的值。Lambda与错误无关。 - H H
它绝对是这样的,Henk。在后一个示例中,lambda 被推断为 ThreadStart,而在前一个示例中,它被明确地类型化为 Action。后者与 Thread 类构造函数之一兼容,而前者则不兼容。 - Chris
@Henk,你是在同意我的观点还是在暗示我说了其他的话?我不确定... - Sky Sanders
不,我不同意你。就像我说的一样,lambda表达式没有参与,错误出现在 new Thread(doIt) 中。 - H H
@Henk,所以你在我的回答上留下评论,我必须推断你认为我说了不同的话。让我更清楚地表达。 - Sky Sanders

3

在CLR的视角下,具有相同签名的委托并不相同 - 它们是完全不同的类型。


2
我认为 C# 语言规范中第26.3.1节的以下措辞很重要:
类似于匿名方法表达式,lambda 表达式被归类为具有特殊转换规则的值。该值没有类型,但可以隐式转换为兼容的委托类型。具体来说,如果一个委托类型 D 与 lambda 表达式 L 兼容,则 L 可以与 D 兼容。具体规则如下:[列表已省略]
这防止了像这样的代码编译:
var doIt = () => MyMethod("test");    // CS0815

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

编译器不允许将一个委托类型转换为另一个,即使它们的签名是兼容的,这导致失败。强制执行:

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

这段代码可以顺利编译。


无关的额外特性:早期对第一台苹果Mac进行的可用性测试发现,在使用无衬线字体的对话框上,OK按钮的第一个版本存在问题。用户经常会误点取消按钮,有些甚至表现出明显的痛苦。在几次采访后,一位用户最终承认了问题:“当电脑叫我傻瓜时,我很讨厌!”

嗯,编译器称呼你为傻瓜也许并不是那么无关紧要 :)


2

由于线程启动是一个单独的委托类型,Action 无法转换为 ThreadStart

这种情况可以工作,因为编译器将您的 lambda 表达式视为 ThreadStart:

Thread t;

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

1

尝试:

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

或者

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

你正在尝试将一个类型为“Action”的参数传递给一个接受ThreadStart的构造函数。由于没有定义隐式转换,因此您需要手动调用ThreadStart构造函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接