如何以正确的方式使用委托/了解委托

使用 – C#(。Net Framework 4.5,Visual Studio 2012)

我试着理解像代表这样的主题,目前我有几点,必须为我澄清。 我在互联网上发现了很多不同的信息来描述如何使用它,但是对我来说理解这个主题有点复杂。

据我所知,我必须做一些使用委托的事情:

  • 创建一些实体用于它(需要创建一些委托)
  • 声明委托类型
  • 创建一些我调用委托的方法
  • 在主类调用委托与使用实体的必需方法(从第一点)

所有描述如下所示

理解分流

问题 – 我是否正确理解了所有或者我错了 – 请澄清一下。

另外一个关于DELEGATE的问题 – 在哪里更好地将代码与DELEGATE放在一起 – 在Console C#应用程序中我可以在任何使用过的命名空间的地方创建它 – 我可以在下面看到。

放置dalagete

但也许有一些建议/要求不仅为控制台应用程序而且为WinForms,WPF等放置委托。

这个主题对我来说是新的,我花了一天时间来理解它,但仍然有点(或更多)与此混淆,最后创建这篇文章以获得更好和清晰的理解。 认为这是非常强大的东西。

编辑

namespace SimpleCSharpApp { delegate void myDelagate (); } 

Ho-ho ..你有些搞砸了。 在我看到VS的屏幕截图,并在“委托”声明下用红色下划线之前,我并没有完全理解你想要说明的问题。

首先, public void delegate zczcxxzc忘掉public void delegate zczcxxzc行。 这有点特别。 首先,让我们看一些代表的标准种类*)。

最基本的两个是:

  • System.Action
  • System.Func

两者都是通用的,并且第一次看到它们的签名,它们看起来可能过于复杂。 但是,它们非常简单。

首先,让我们限制裸,无参数, System.Action

 private static void myFunction1() { Console.WriteLine("Hello!"); } private static void myFunction2() { Console.WriteLine("Bye!"); } private static void myFunction0() { return; } ... // in some function, ie Main() Action myDelegate = null; myDelegate = new Action( myFunction1 ); myDelegate(); // writes "Hello!" myDelegate = new Action( myFunction2 ); myDelegate(); // writes "Bye!" myDelegate = new Action( myFunction3 ); myDelegate(); // does "nothing" 

就像“int”持有一个数字,“string” – 文本,“委托”持有关于“可调用的东西”的信息,或者,使用一些术语,“可以调用的东西”。

首先,我创建了一个记住“myFunction1”类型的“Action”类型的委托 。 然后我调用/调用该委托 – 它导致被记住的函数被调用。

然后,我创建一个类型为“Action” 的委托 ,记住“myFunction2”。 然后我调用/调用该委托 – 它导致被记住的函数被调用。

最后,我创建了一个类型为“Action” 的委托 ,它记住了“myFunction3”。 然后我调用/调用该委托 – 它导致被记住的函数被调用,没有任何反应 – 但只是因为目标函数什么也没做。

请注意,我故意说“创建了一个代表”。 每次执行new Action ,都会创建一个新委托。 “委托”只是一个对象,如String“foo”或float [] {1.2,4.5}。

另请注意,此处使用的创建委托的完整语法是new Action(...) 。 就像创建任何对象一样 – 新的+ typename +构造参数。 另一个标志,“代表”只是一个对象。

另外需要注意的是我没有写new Action( myFunction1() ) 。 我不想调用该方法并获取其结果并将该结果提供给Action的构造函数。 我写了new Action( myFunction1 ) 。 我把函数本身给了构造函数。

那么,什么是“行动”呢? System.Action是一个类。 像String,或Socket或WebClient。 这里没什么特别的。 所以,我们有一个“类”,它的对象可以记住应该被调用的函数。 凉。

因此,有些人将代表与“函数指针”进行比较。 但这并不完全正确。 函数指针可以记住要调用的函数 。 代表们可以记住要调用的方法 。 记得区别吗? 在上面的例子中,我故意在每个myFunctionstatic 。 这些可以称为无对象/无目标。 你需要他们的名字,你可以从任何地方打电话给他们。 要打电话给他们,一个简单的哑指针就足够了。

现在,代表们可以做得更多。 他们可以研究方法 。 但是需要针对对象调用方法。

 class GuineaPig { public static void Squeak() { Console.WriteLine("Ieek!"); } public void Well() { Console.WriteLine("actually"); } public void IDontKnow() { Console.WriteLine("what they do"); } } GuineaPig.Squeak(); // says 'ieek' Action myDelegate = null; myDelegate = new Action( GuineaPig.Squeak ); myDelegate(); // writes "ieek" // GuineaPig.Well(); // cannot do! // myDelegate = new Action( GuineaPig.Well ); // cannot do! 

好吧,在其他类中创建一个静态函数的委托很容易 – 只需要确切地说什么是函数来自什么类。 再次就像打电话一样,但没有括号。

但是,如果您尝试取消注释对非静态方法的引用,它将无法编译。 看看GuineaPig.Well – 这很明显。 它不是静态的,需要针对OBJECT而不是CLASS进行调用。 出于同样的原因,无法创建委托。 我们来解决这个问题:

 class GuineaPig { public void Well() { Console.WriteLine("actually"); } public void IDontKnow() { Console.WriteLine("what they do"); } } GuineaPig myPiggie = new GuineaPig(); myPiggie.Well(); // ok! writes "actually" Action myDelegate = null; myDelegate = new Action( myPiggie.Well ); // ok! myDelegate(); // ok! writes "actually". 

请注意在创建委托期间如何将classname替换为objectvariable。 语法被保留:就像调用一样,但没有parens。 但是,关于“方法”与“function”的所有大惊小怪……

代理不仅可以存储要调用的“方法”,还可以存储调用它们的对象

 class GuineaPig { public string Name; public void Well() { Console.WriteLine("I'm " + Name); } } GuineaPig myPiggie1 = new GuineaPig { Name = "Bubba" }; GuineaPig myPiggie2 = new GuineaPig { Name = "Lassie" }; Action myDelegate = null; myDelegate = new Action( myPiggie1.Well ); myDelegate(); // -> Bubba myDelegate = new Action( myPiggie2.Well ); myDelegate(); // -> Lassie myPiggie1 = myPiggie2 = null; myDelegate(); // -> Lassie 

现在,这是普通函数指针无法做到的事情。 (尽管你可以使用非常智能的函数指针..但是,让我们离开它)。

注意如何在“pig#2”上调用“Well”的事实存储在委托对象中。 “myPiggie2”变量无关紧要。 我可以使它无效。 代表记住了目标和方法。

System.Action只是其中之一。 它是最简单的,没有参数,没有返回..但是它们中有很多,它们可以获取参数( Action )它们可以返回值( Func )或两者( Func ) 。 然而,不断说Func有些……模糊不清。

好。 让我们终于明白这一切的唠叨。 对不起,如果你知道所有这些,但我想清楚。

“代表”就是要记住“目标”和“方法”。 实际上,如果您在调试器中检查委托(或检查Intellisense在“点”之后说的内容),您将看到两个属性,Target和Method。 他们正是他们的名字所代表的。

让我们假设您想要创建自己的委托类型。 一种不会被称为Func ,而是“MyStudentFilteringDelegate”。

现在,重点是,在C#中你不能轻易地获取函数的& (地址) ,也不能重载operator() 。 这导致您无法编写自己的委托类。

你不能只写:

 class MyStudentFilteringDelegate { public object Target; public somethingstrange* MethodPointer; // other code } 

因为,即使你真的设法遵循这个想法,最后在某个地方你会发现:

 MyDelegate dd = new MyDelegate ( ... ); dd(); // is just impossible!!! 

至少,在目前的C#版本4.5或5中。

你不能重载“call”/“invoke”操作符,因此你将无法完全实现自己的,自定义命名的委托类型。 你永远都会被动作和Funcs困住。

现在回想起public void delegate xxx下的红色下划线我请你暂时忘记。

 public bool delegate MyStudentFilteringDelegate( Student stud ); 

此行不会创建任何委托 。 该行定义了委托类型 。 它与Func完全相同,带有自定义名称。 *)

实际上,编译器将行转换为:

 public class MyStudentFilteringDelegate : some_framework_Delegate_class { // object Target {get;} - inherited from base class // MethodInfo Method {get;} - inherited from base class, too } 

所以它是一个 ,所以你现在可以创建一个委托对象:

 var dd = new MyStudentFilteringDelegate ( ... ); // like normal class! dd(); // ok!; 

由于类是特殊的,编译器生成的,它可以破坏规则。 它的’call’/’invoke’运算符被重载,所以你可以“调用”委托,因为它是一个方法。

请注意,尽管有奇怪的表示法:

 public bool delegate MyStudentFilteringDelegate( Student stud ); 

MyStudentFilteringDelegate是一个 ,就像Action或Func或String或WebClient一样。 delegate关键字只是编译器的标记,以便知道它应该应用于该行的转换以生成适当的“委托类型”(类)。

现在,要真正回答你的另一个问题:

放置委托类型声明的地方真的无关紧要。 你可以在任何你喜欢的地方写public void delegate XYZ(...) 。 就像你可以在任何地方放置一个类声明一样。

您可以将类声明放在默认(无命名空间)范围,某个命名空间或类中。 因此,由于委托类型只是一个类,您还可以在默认(无命名空间)范围,某个命名空间或类内部处理新的委托类型:

 public class Xc {} public void delegate Xd(); namespace WTF { public class Xc {} public void delegate Xd(); class Whatever { public class Xc {} public void delegate Xd(); } } 

请注意,我完全故意将它们命名为相同。 那不是错误。 首先命名为::global.Xc::global.Xd ,第二对命名为WTF.XcWTF.Xd ,最后一对命名为WTF.Whatever.XcWTF.Whatever.Xd 。 就像普通的clases一样。

要确定放置这些声明的位置,请使用与您用于类的规则相同的规则。 IE浏览器。 如果将文本处理类放在命名空间MyApp.Text.Parsing ,那么与该文本处理相关的所有委托类型也应该位于该命名空间中。 但是,即便如此,这纯粹是化妆和组织的。 将它们放置/定义在适合您的任何范围内。

编辑:*)实际上,从历史上看,它是完全相反的。 delegate关键字和编译器技巧比Action和Func类 。 在.Net 2.0中,Action / Func不存在。 创建/使用委托的唯一方法是定义自己的新委托类型(或在系统的名称空间深处找到/猜测一些合适的委托)。 请记住,每个新的委托类型都是一个新类。 不能转换为任何其他类,甚至不能同样看起来。 令人沮丧的是令人沮丧且难以保持,在.Net 3.5中,他们最终在框架中包含了“通用通用委托类型”。 从那时起,Action / Func越来越常被使用,因为即使它们更难阅读,它们也是普遍存在的。 System.Func可以“随处”传递,并且你没有问题, from one library does not match ‘bool delegate StudentFilter() from one library does not match的bool委托StudentSelector()` from one library does not match

c#中的委托有点像C ++中函数指针的替代品。 它们有很多用途。 您可以使用它们:

  1. 为事件创建内联实现。
  2. 将回调传递给函数。
  3. 创建一个可以在循环中调用的“函数”数组。

根据我的经验,第一次使用是最常见的。

 Button.Click += delegate(object sender, EventArgs args) => {/*you do something here*/}; 

可以通过lamba表达式简化:

 Button.Click += (sender, args) => {/*you do something here*/}; 

您为按钮单击提供了一些行为,而不必为其创建单独的方法。

关于问题的第二部分,我通常将代理声明放在单独的文件中。

委托声明可以放在具有公共和内部可访问性的任何源文件中。 我个人将它们放在最适用的类源的顶部。

例如,如果我有一个专门的事件委托,它将CustomEventArgs作为参数,我将委托声明放在该文件的顶部:

 namespace MyNamespace { public delegate void SpecialEventDelegate(object sender, CustomEventArgs e); public class CustomEventArgs : EventArgs { // implementation details } } 

另一个选择是将代理人放在一个单独的源文件中……我没有看到这样做,并且会受到编码指南的约束。

代表一般有两个资格:

  • 方法或类成员的声明
  • 对方法或类成员的引用

第一个资格是代表的声明:

  public delegate void SpecialEventDelegate(object sender, CustomEventArgs e); 

…或使用FuncAction

  Action // similar declaration to the SpecialEventDelegate above 

由于事件处理程序(声明的委托)通常没有返回类型( void ),因此不会使用Func 。 Func委托需要返回类型:

  Func // a delegate that return true/false 

有关FuncAction更多信息,请参阅链接的MSDN文章。 我只提供了对这些内容的引用,以便对新的声明方法进行完整性和洞察。 FuncActiondelegate专用包装器。

委托的第二个资格是对方法或类成员的引用。 我可能有一个私有方法充当特定需求的处理程序。 假设FileIO对象需要针对不同类型文件的特定文件处理程序 – 即.XML,.TXT,.CSV:

 namespace MyNamespace { public delegate Stream OpenFile(FileInfo FileSpec); } 

现在,任何对象都可以根据文件类型实现自己的OpenFile定义,但必须返回Stream对象。

 class XMLHandler : IFileHandler { private OpenFile xmlFileReader; // implementation of interface public OpenFile FileHandler { get { return xmlFileReader; } } public XMLHandler(){ xmlFileReader = MyXmlFileReader; // references the private method in this class } private Stream MyXmlFileReader(FileInfo XmlFileSpec) { // implementation specific to this class } } interface IFileHandler { OpenFile FileHandler { get; } } 

通过使用委托声明和接口,我们可以将XMLHandler对象作为IFileHandler传递,并且只通过FileHandler属性公开委托,而不暴露整个对象。 请注意,委托引用的方法是私有的。 这是委托(和Func,Action)的特殊好处。