快速提问。
在this documentation page的第二个示例(第二个代码块,具有称为CompareDinosByLength的方法)中,Sort方法被这样调用:
dinosaurs.Sort(CompareDinosByLength);
为什么如我阅读Delegate文档所认为的那样,Sort方法不需要显式声明的委托?在找到该示例之前,我试图这样做:
delegate int CompareDinosDel(string first, string second);
CompareDinosDel newDel = CompareDinosByLength;
dinosaurs.Sort(newDel);
但是我不断收到与委托/委托方法不当的比较器有关的错误。
都不都可以吗?
最佳答案
dinosaurs.Sort(CompareDinosByLength);
CompareDinosDel newDel = CompareDinosByLength;
dinosaurs.Sort(newDel);
都不都可以吗?
不,因为您要将两个截然不同的东西传递给这两个函数调用。
这里的关键是要认识到,在两种情况下,您实际传递给方法的都是委托。在第一种情况下,即使您没有明确要求编译器隐式地为您创建正确类型的委托。在第二种情况下,您将创建自己的委托,但类型错误,因此尝试将失败。
从.NET 2.0开始,C#编译器允许您在许多情况下显式跳过创建委托。如果在需要委托的上下文中使用方法名称,并且编译器可以验证方法签名和委托签名匹配,则它将隐式使用该方法构造委托实例。也就是说,不要这样做(“旧”方式)
this.SubmitButton.Click += new System.EventHandler(this.SubmitButton_Click);
您现在可以执行以下操作:
this.SubmitButton.Click += this.SubmitButton_Click;
我认为Visual Studio本身仍会生成较旧的语法,因为它仍然可以工作,并且因为开发人员花很少的时间来搞弄它实在是徒劳无益,这是不值得的。但是,如果您在自己的代码中使用它,则大多数流行的代码分析工具都会标记多余的委托创建。
在拥有方法的任何地方(技术上称为“方法组”,因为一个方法名称可以引用多个重载),都可以使用相同的技术,然后将其分配给委托类型的变量。将方法作为参数传递给另一个方法与分配操作的类型相同:您将调用站点处的实际参数“分配”给方法主体中的形式参数,因此编译器执行相同的操作。换句话说,以下两个方法调用执行的操作完全相同:
dinosaurs.Sort(CompareDinosByLength);
dinosaurs.Sort(new Comparison<string>(CompareDinosByLength));
另一方面,您未成功地创建代表的尝试做了一些不同的事情:
dinosaurs.Sort(new CompareDinosDel(CompareDinosByLength));
这次,您告诉编译器您到底想要哪种委托,但这不是该方法期望的那种委托。通常,编译器不会尝试去猜测您告诉它要做什么。如果您要求它执行看上去“腥”的操作,则会产生错误(在这种情况下,类型不匹配错误)。
此行为类似于如果您尝试执行以下操作:
public class A
{
public int x;
}
public class B
{
public int x;
}
public void Foo(A a) { }
public void Bar()
{
B b = new B();
this.Foo(b);
}
在这种情况下,即使
A
和B
是两个不同的类型,即使它们的“类型签名”完全相同。在A
上运行的任何代码行在B
上也同样适用,但是,我们不能互换使用它们。委托是与其他任何类型一样的类型,并且C#的类型安全规则要求我们在需要的地方使用正确的委托类型,而仅使用足够接近的类型就无法摆脱困境。这是一件好事的原因是因为委托类型可能具有更多含义,而这仅仅是它的技术组件所隐含的含义。像任何其他数据类型一样,当我们为应用程序创建委托时,我们通常会对这些类型应用某种语义。例如,我们期望如果有
ThreadStart
委托,它将与在新线程启动时运行的方法相关联。委托的签名就和您获取的一样简单(没有参数,没有返回值),但是委托背后的含义非常重要。因此,我们通常希望编译器告诉我们是否尝试在错误的位置使用错误的委托类型。通常,这很可能表明我们即将做一些可能会编译甚至运行的事情,但可能会做错事情。您从程序中就永远不需要这东西。
尽管所有这些都是正确的,但通常也确实是您确实不想将任何语义含义分配给委托人,否则,含义是由应用程序的其他部分分配的。有时,您确实确实只是想传递必须在以后运行的任意代码。这在函数式程序或异步程序中非常常见,在这些程序中,您会得到诸如延续,回调或用户提供的谓词之类的东西(例如,查看各种LINQ方法)。为此,.NET 3.5及更高版本在
Action
和Func
系列中提供了非常有用的一组完全通用的委托。