两者都是委托并具有相同的签名,但我不能将Action用作ThreadStart。
为什么?
Action doIt;
doIt = () => MyMethod("test");
Thread t;
t = new Thread(doIt);
t.Start();
但这似乎是有效的:
Thread t;
t = new Thread(() => MyMethod("test"));
t.Start();
两者都是委托并具有相同的签名,但我不能将Action用作ThreadStart。
为什么?
Action doIt;
doIt = () => MyMethod("test");
Thread t;
t = new Thread(doIt);
t.Start();
Thread t;
t = new Thread(() => MyMethod("test"));
t.Start();
正如其他人所指出的那样,问题在于委托类型不是"结构性"的。也就是说,它们不具有基于它们的"结构"的等效性。
现在,这对于某些类型来说可能是件好事。如果你有...
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”功能旨在使在不同程序集中定义的语义和结构相同的两种类型在逻辑上结构统一。
Action
/ Func
变体的重载,可以轻松地前进很长的路程。尽管这是许多重载...... 作为额外的好处,预期的签名立即通过 IntelliSense 明显可见,无需任何其他 Visual Studio 调整。 - Roman StarkovAction
/Func
类型并从那里继续,而不是发出错误CS1660(无法将lambda表达式转换为类型...),那就太好了。虽然这也可以通过辅助方法来解决。 - Roman Starkov这个错误的基本形式是:
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);
Action doIt;
doIt = () => MyMethod("test");
Thread t;
t = new Thread(doIt.Invoke);
t.Start();
doIt.Invoke
会自动包装在 ThreadStart
委托中。它实际上并没有调用 Invoke
。这与任何其他返回 void 并且没有参数的方法的行为相同。 - Zach Johnson不是
ThreadStart 委托,因此虽然可以将lambda表达式分配给两者,但不能同时使用它们来启动线程。new Thread(doIt)
中。 - H H在CLR的视角下,具有相同签名的委托并不相同 - 它们是完全不同的类型。
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按钮的第一个版本存在问题。用户经常会误点取消按钮,有些甚至表现出明显的痛苦。在几次采访后,一位用户最终承认了问题:“当电脑叫我傻瓜时,我很讨厌!”
嗯,编译器称呼你为傻瓜也许并不是那么无关紧要 :)
由于线程启动是一个单独的委托类型,Action
无法转换为 ThreadStart
。
这种情况可以工作,因为编译器将您的 lambda 表达式视为 ThreadStart:
Thread t;
t = new Thread(() => MyMethod("test"));
t.Start();
尝试:
t = new Thread(new ThreadStart(doIt));
或者
t = new Thread( ()=>MyMethod("test"));
你正在尝试将一个类型为“Action”的参数传递给一个接受ThreadStart的构造函数。由于没有定义隐式转换,因此您需要手动调用ThreadStart构造函数。
t = new Thread(doIt);
更改为t = new Thread(doIt.Invoke);
, 可以获得相同的结果. - Spencer Ruport