难以理解代表世界中的术语

时间:2019-05-31 06:12:33

标签: c# oop delegates

我是C#的新手,我对使用的术语(例如“侦听器”,“呼叫者”,“回调”,“ EventHandler”)感到非常困惑,假设我们在下面有以下代码:

public class Car 
{
    public delegate void CarEngineHandler();
    public CarEngineHandler listOfHandlers;
    public void Accelerate(int delta)
    {
       if (delta > 80) 
          listOfHandlers();    // not checking null for simplicity 
    }
}

class Program
{
    static void Main(string[] args)
    {
         Car myCar = new Car(); 
         myCar.listOfHandlers = new Car.CarEngineHandler(CallWhenExploded);
         myCar.Accelerate(120);
    }

    static void CallWhenExploded() 
    {
       Console.WriteLine("Sorry, this car is dead..."); 
    }
}

在这种情况下:

  1. 谁是主叫方?

  2. 谁是听众?听什么?

  3. 谁是回调函数?

  4. 谁是EventHandler?

我的答案是:

  1. 来电者是myCar
  2. 不确定
  3. 回调是CallWhenExploded()静态函数
  4. EventHandler是listOfHandlers

我正确吗?我真的不知道谁是听众,听什么。

2 个答案:

答案 0 :(得分:3)

希望您理解string类型的变量就像一个存储字符串的存储桶,您可以单独查看它们,将它们作为集合传递给其他对象。这是一件很容易可视化的事情,该变量是一个存储数据的容器,因为我们一直在现实世界中存储数据;盒子/文件中的纸片等

代表就像 methods 的变量一样-这是一个很难理解的概念,我们可以像对待数据一样传递方法,并且可以让计算机运行方法..但这就是委托。声明委托时,就像声明自己的类时一样:

delegate void SomeMethod(); //specify a delegate

class Person{               //specify a class
  string FirstName;
  string LastName;
}

就像创建一个类一样,您也赋予了自己创建该类的实例的能力,当您声明一个委托时,就赋予了自己创建该委托的实例的能力。您的课程实例引用了数据,例如此处的人的名字和姓氏。委托实例引用方法而不是引用数据。您可以创建一个引用“ John”和“ Smith”的类实例。您可以创建一个引用Method1()的委托实例。您可以创建另一个引用“ Bill”“ Jones”的类实例。您可以创建另一个引用Method2()的委托实例。

您可以将任何方法与委托实例相关联,只要该方法具有与委托相同的签名即可。上面的委托将接受对任何不带参数且返回void的方法的引用。如果您具有一种或多种此类方法,则可以将它们附加到委托的实例,将委托实例像变量一样传递,并且将委托传递给的对象可以调用方法而无需知道方法名称是什么或有关它的任何内容。

我们为什么要这样做?委托的主要用途之一是事件处理。 Microsoft创建了能够执行操作的UI控件-您可以单击按钮。您希望按钮被单击时可以执行某些操作,但是世界上每个人都希望按钮被单击时可以执行不同的操作,因此Microsoft不能为此编写代码。但是,他们可以说将创建一个带有委托的按钮(一个像变量一样传递的方法),并且该按钮承诺在单击该方法时会调用该方法。作为委托,您可以为按钮提供执行所需功能的代码。您的方法必须具有一定的签名(参数),因为该按钮希望向您的方法传递一些可能对您有所帮助的数据。可能是发生时钟时鼠标的位置或被点击的次数。 UI组件具有数百种不同类型的事件,它们都在委托上运行

委托的另一个很好的例子是异步编程的旧样式,当我们有Begin / End方法对时,调用Begin通常会启动一个新线程来做某事,而我们必须提供一个方法(我们的方法)来就是说,当Microsoft的Begin方法完成某项操作后,它将调用我们的方法以告知我们已完成。通常情况下,我们的代码将调用End,传递一些已经给我们方法的引用,然后得到结果。例如,将请求发送到服务器会花费一些时间,因此,我们将开始它,执行其他一些操作,并在响应准备就绪时,将由被调用的代理通知我们,并将处理结果

因此,习惯一下委托只是一种方式或引用方法的概念,您将其传递给您,传递给它的内容可以运行它并向其提供数据,而又不知道它的作用。在术语中,它也称为回调-当外来方法完成其工作时将调用的东西,尽管这是一个相当宽松的术语-它可能用于指代完成工作的方法,或者指向您方法的委托实例,即传入的东西。实际上,它们等效取决于会话上下文

委托本身更像是一个集合/数组-您可以将多个方法附加到其上,并且在调用该委托时它们将全部运行,尽管不会以任何定义的顺序进行。在您的示例中,您仅为其分配了一个,但可能为多个:

SomeMethod delegs = null;
delegs += new SomeMethod(MyMethod1);
delegs += new SomeMethod(MyMethod2);

调用该委托将导致MyMethod1和MyMethod2都可能以2,1顺序运行。


事件也都是委托,但它们是一种稍微特殊的情况,在上面单击了按钮单击处理程序。当使用event关键字指定委托时,只能在声明委托变量的类内部调用委托。 UI控件使用它来确保只有他们才能运行委托;您无法进入按钮并自己强制运行其时钟处理程序,因为处理单击的委托列表被声明为事件。如果要以编程方式导致按钮触发其事件处理程序列表,通常必须对其进行子类化,以便您的代码可操作地置于按钮代码“内部”

侦听器作为短语通常与事件相关联,但是在某种程度上与回调和事件处理程序同义-当您将侦听器添加到事件中时,就是说您正在编写用于处理事件发生的代码,您的代码(作为委托/事件处理程序)在事件发生时起作用。它本身并不“监听”;当引发事件的事件调用与该事件相关联的委托/事件处理程序列表时,它只是四处徘徊并开始起作用。

顺便说一句,因为委托实例代表一个指向方法的变量,即它们指向一个动作,所以实例的命名应为动词而不是名词。您调用了自己的listOfHandlers,但对您的理解在事件/动作发生后调用委托实例会更有用。您正在应用的用例是revrevving之一,因此也许您应该将其命名为CarEngineOverRevving。当温度超过极限时,您可能会遇到另一个问题。如果这两个委托都具有相同的签名(您希望将通过没有参数的方法来处理这些事件的人员),则可以将同一委托用于不同的事件:

public void delegate CarEngineProblemHandler();
public CarEngineProblemHandler CarEngineOverRevving = null;
public CarEngineProblemHandler CarEngineOverheating = null;

委托是您声明要调用的方法的外观的方式。命名问题列表就是您要声明的其他人可以提供的处理方法。我可以决定不给您一个处理过度的处理程序,因为我永远也不会这样做,但是过热还是有可能发生,所以我一定会给您一个处理程序/侦听器/委托/回调来调用它

答案 1 :(得分:0)

当您了解代表使用的“广播者/订户”模式时,您应该能够回答这些问题。 “广播者”持有委托实例,并决定何时在其“调用列表”中调用目标方法。这些目标方法是“订户”,它们可以通过在广播公司的代表字段上调用“-=”或“ + =”来决定何时停止或开始“监听”。

public class Car 
{
    // This is the 'delegate type'
    // ‘Delegate type’ is what is responsible for defining the method such as its signature 
    // which include return type and parameters
    // In this ‘Delegate type’ named:'CarEngineHandler', the return type is 'void' and there are no parameters
    public delegate void CarEngineHandler();

    //this is the ‘Delegate instance’
    //‘Delegate instance’ is responsible for holding the reference to the actual methods 
    //that adheres to the delegate type. 
    //Delegate instance can be used to then ‘invoke’ that method whenever needed. 
    public CarEngineHandler listOfHandlers;


    public void Accelerate(int delta)
    {
       if (delta > 80) 
            //this is where you use the 'Delegate instance' to ‘invoke’ the methods 
            // You can also you use: listOfHandlers.Invoke(); 
            // which is same as calling 'listOfHandlers();'
            // When delegate instance is invoked, all its actions in its invocation list are executed in order
            //In this example it will first dispaly: "Sorry, this car is dead..."
            // then display: "Calling, emergency..."
          listOfHandlers();    // not checking null for simplicity 
    }
}

class Program
{
    static void Main(string[] args)
    {
            Car myCar = new Car(); 
            //This is where you add to the 'delegate instance', the 'target method's' that matches delegate signature
            //A 'delegate instance' can contain more than one action. This list of actions is called the “invocation list”.
            //You can also add multiple methods to invocation list like this: myCar.listOfHandlers += CallWhenExploded;  
            myCar.listOfHandlers = new Car.CarEngineHandler(CallWhenExploded);
            // For example you can add another target method like this:
            myCar.listOfHandlers += CallEmergencyService; 

            // you get details about the 'target methods' attached to the “invocation list” like this:
            var listOfTargets = myCar.listOfHandlers.GetInvocationList();
            Console.WriteLine(listOfTargets[0].Method.Name); 


            myCar.Accelerate(120);
    }

    static void CallWhenExploded() 
    {
       Console.WriteLine("Sorry, this car is dead..."); 
    }
    static void CallEmergencyService() 
    {
       Console.WriteLine("Calling, emergency..."); 
    }
}