5

使用 - C# (.Net Framework 4.5, Visual Studio 2012)

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

据我了解,我必须为使用委托做几件事:

  • 创建一些实体以使用它(需要创建一些委托)
  • 声明委托类型
  • 创建一些我调用委托的方法
  • 在使用实体的所需方法的主类调用委托中(从第一点开始)

我在下面显示的所有描述

了解委托

问题-我是否正确理解了所有内容,或者我错了-请澄清。

还有另一个关于DELEGATE 的问题 - 在哪里更好地使用 DELEGATE 放置代码 - 在控制台 C# 应用程序中,我可以在使用过的命名空间的任何地方创建它 - 如下所示。

放置dalagete

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

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

编辑

namespace SimpleCSharpApp
{
   delegate void myDelagate ();
}
4

3 回答 3

9

嗬嗬..你有什么东西搞砸了。直到我在“代表”声明下看到带有红色下划线的 VS 屏幕截图,我才完全理解您要说明的问题。

首先,暂时忘记这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, i.e. 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” - 文本一样,“delegate”包含有关“可调用的东西”的信息,或者,使用一些术语,“可调用的东西”。

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

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

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

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

另外,请注意这里使用的创建委托的完整语法是new Action(...). 就像创建任何对象一样 - new + typename + 构造参数。另一个迹象表明“代表”只是一个对象。

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

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

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

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

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".

请注意在创建委托期间如何将类名替换为对象变量。语法被保留:就像调用一样,但没有括号。但是,“方法”与“功能”有什么大惊小怪的呢?

委托不仅可以存储要调用的“什么方法”,还可以存储调用它们的对象。

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<string, int>)它们可以返回值(Func<int>)或两者都(Func<string,int>)。然而,不断地谈论Func<int,float,string,int,int,bool,decimal>是有点……晦涩难懂。

好的。让我们最终进入所有这些喋喋不休的话题。抱歉,如果您知道所有这些,但我想澄清一下。

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

假设您想创建自己的委托类型。一种不会被称为Func<int,int,int,bool,bool,Zonk,string>“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”运算符,因此您将无法完全实现自己的自定义命名委托类型。您将永远被困在 Actions 和 Funcs 中。

public void delegate xxx现在回想一下我叫你暂时忘记下的那个红色下划线。

public bool delegate MyStudentFilteringDelegate( Student stud );

此行不创建任何委托。这一行定义了委托的类型。它与自定义名称完全相同Func<Student,bool>。*)

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

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.Xcand ::global.Xd,第二对命名为WTF.Xcand WTF.Xd,最后一对命名为WTF.Whatever.Xcand WTF.Whatever.Xd。就像普通班一样。

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

编辑:*) 实际上,从历史上看,情况完全不同。delegate关键字和编译器技巧比Action 和 Func 类更古老。在 .Net 2.0 中,Action/Func 不存在。创建/使用委托的唯一方法是定义您自己的新委托类型(或在系统命名空间深处的某个地方找到/猜测一些合适的委托类型)。请记住,每个新的委托类型都是一个新类。不能转换为任何其他类,甚至没有类似的外观。它是如此令人沮丧且难以维护,以至于在 .Net 3.5 中,他们最终在框架中包含了“通用通用委托类型”。从那时起,Action/Func 被越来越多地使用,因为即使它们更难阅读,它们也是……通用的。System.Func<Student,bool>可以在“任何地方”传递,并且您不会遇到另一个问题:'bool delegate StudentFilter() from one library does not matchbool delegate StudentSelector()`。

于 2013-11-13T21:31:40.287 回答
1

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*/};

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

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

于 2013-11-13T21:02:20.877 回答
1

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

例如,如果我有一个将 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);

...或使用Func<TResult>Action

   Action<object, CustomEventArgs> // similar declaration to the SpecialEventDelegate above

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

   Func<bool> // a delegate that return true/false

Func<TResult>有关和的更多信息,请参阅链接的 MSDN 文章Action。为了完整性和对较新声明方法的了解,我只包括了对这些的引用。Func<TResult>并且Actiondelegate.

委托的第二个条件是对方法或类成员的引用。我可能有一个私有方法作为特定需求的处理程序。假设 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对象作为 an传递,IFileHandler并且仅通过属性公开委托,FileHandler而不公开整个对象。请注意,委托引用的方法是私有的。这是委托(以及 Func、Action)的特殊优势。

于 2013-11-13T21:43:30.640 回答