【问题标题】:I don't understand the difference between pure delegate and event fields我不明白纯委托和事件字段之间的区别
【发布时间】:2012-05-22 16:57:05
【问题描述】:

代表:我明白。但是当我转向活动时,很多事情我不太了解。我在 Network 上阅读了书籍、MSDN 和一些简单的示例,它们都有相同的结构。例如,这里是链接:Event Example

我举第一个例子,作者说这是关于 C# Event 最简单的例子。

这是他的代码:

public class Metronome
{
    public event TickHandler Tick;
    public EventArgs e = null;
    public delegate void TickHandler(Metronome m, EventArgs e);
    public void Start()
    {
        while (true)
        {
            System.Threading.Thread.Sleep(3000);
            if (Tick != null)
            {
                Tick(this, e);
            }
        }
    }
}

public class Listener
{
    public void Subscribe(Metronome m)
    {
        m.Tick += new Metronome.TickHandler(HeardIt);
    }
    private void HeardIt(Metronome m, EventArgs e)
    {
        System.Console.WriteLine("HEARD IT");
    }
}

class Test
{
    static void Main()
    {
        Metronome m = new Metronome();
        Listener l = new Listener();
        l.Subscribe(m);
        m.Start();
    }
}

您可以注意到线路:public event TickHandler Tick。当我更改为public TickHandler Tick 时,程序仍然运行相同。但是我理解新行,因为它只是一个纯粹的代表。

所以,我的问题是:event 关键字的真正目的是什么:public event TickHandler Tick。这很重要,因为所有的例子都是这样使用的,但我无法解释为什么。

谢谢:)

【问题讨论】:

    标签: c# events delegates


    【解决方案1】:

    代表和事件是相关的概念,但它们是一回事。 “代表”一词往往有两种含义(经常被掩盖):

    • 一个委托类型,类似于单一方法接口。 (存在显着差异,但这是一个合理的起点。)
    • 该类型的实例,通常通过方法组创建,这样当“调用”委托时,就会调用该方法。

    事件不是那些。它是类型中的一种成员 - 一对添加/删除方法,接受委托来订阅或取消订阅事件。当您使用foo.SomeEvent += handler;foo.SomeEvent -= handler; 时,将使用add 和remove 方法。

    这与属性实际上是一对 get/set 方法(或可能只是两者中的一个)非常相似。

    当你像这样声明一个类字段事件时:

    public event TickHandler Tick;
    

    编译器将成员添加到您的类中,有点如下:

    private TickHandler tick;
    
    public event TickHandler
    {
        add { tick += value; }
        remove { tick -= value; }
    }
    

    它比这要复杂一些,但这是基本思想 - 它是事件的简单实现,就像自动实现的属性一样。从类内部,您可以访问支持字段,而在类外部,您将始终只使用事件。

    我个人认为很遗憾,类字段事件的声明看起来非常一个委托类型的字段 - 这会导致在某些情况下发现一些误导性 (IMO) 语句在答案中,好像event 关键字“修改”了一个字段声明——实际上这意味着你声明的是完全不同的东西。我认为如果类似字段的事件看起来更像是自动实现的属性会更清楚,例如

    // Not real C#, but I wish it were...
    public event TickHandler Tick { add; remove; }
    

    我有一个whole article 进行了更详细的说明,您可能会发现它很有用。

    【讨论】:

      【解决方案2】:

      event 关键字基本上限制了对delegate 的操作。 您不能再使用= 运算符手动分配它。

      您只能从您的活动中逐一添加(使用+=)或删除(使用-=)代表。这样做是为了防止某些订阅者“覆盖”其他订阅。

      因此,您不能这样做:m.Tick = new Metronome.TickHandler(HeardIt)

      【讨论】:

      • 请注意,这些限制仅在声明 event 的类型之外。在里面,你仍然可以做任何你想做的事情,这样你就可以做Tick(this, e)之类的事情。您可以将其视为透明地创建 private TickHandler 以及通过 += 和 -= 访问的公共添加/删除方法。
      • 我认为说它“限制操作”是一种误导——它从根本上声明了一种不同类型的成员(一个事件)。我认为这有助于在心理上将事件和代表区分开来。
      【解决方案3】:

      "event" 是修饰符。有什么好处?

      1. 您可以在接口中使用事件
      2. 只有声明它的类才能调用事件
      3. 事件公开了一个 addremove 访问器,您可以覆盖它们并执行自定义操作
      4. 事件将您限制为指定方法SomeMethod(object source, EventArgs args) 的特定签名,该签名为您提供有关事件的其他信息。

      【讨论】:

      • 我不会说它是一个修饰符。它是一个关键字,但它的存在是为了介绍一种不同类型的成员 - 一个事件。那时,它是一个事件声明,而不是一个字段声明。类似字段的事件声明确实引入了一个字段,但这是一个实现细节。
      【解决方案4】:

      您是对的 - 添加 event 关键字似乎几乎是多余的。但是,作为事件的字段与键入到纯委托的字段之间存在一个关键区别。使用 event 关键字意味着包含对象外部的对象可以订阅委托,但不能调用它。当您删除 event 关键字时,外部对象可以订阅并调用委托(可见性允许。)

      【讨论】:

        【解决方案5】:

        当您向程序添加监听器时,您添加的是事件,而不是委托

        查看您的代码 m.Tick +=

        您会看到您正在请求属性(类型事件)的那部分,并且您正在向它添加一个带有 += 的侦听器。现在您只能向该 Tick 属性添加一个 TickHandler 类型,如果您覆盖它,您必须自己创建与 TickHandler 相同的格式。

        很像添加到字符串或整数时。

        string stringTest = string.Empty;
        stringTest += "this works";
        stringTest += 4; //this doesn't though
        int intTest = 0;
        intTest += 1; //works because the type is the same
        intTest += "This doesn't work";
        Metronome m = new Metronome();
        Metronome.TickHandler myTicker = new Metronome.TickHandler(function);
        m.Tick += myTicker; //works because it is the right type
        m.Tick += 4; //doesn't work... wrong type
        m.Tick += "This doesnt work either"; //string type is not TickHandler type
        

        这样可以清除一些吗?

        【讨论】:

        • 你只是告诉我 Delegate 是类型安全的。但是不要告诉我委托和事件之间的区别。你能告诉我更多吗?
        【解决方案6】:

        据我所知,事件基本上是一个多播委托,但对于基本操作具有不同的访问规则,可以在定义它们的类内部或外部对委托和事件执行。

        操作是:

        使用 = 运算符赋值

        使用 += 和 -= 运算符添加/删除

        使用 () 运算符调用

                      Operation         | delegate   | event
                      ------------------+------------+--------
        Inside class  += / -=           | valid      | valid
                      ------------------+------------+--------
        Inside class  =                 | valid      | valid
                      ------------------+------------+--------
        Inside class  ()                | valid      | valid
                      ------------------+------------+--------
        Outside class  += / -=          | valid      | valid
                      ------------------+------------+--------
        Outside class  =                | valid      | not valid
                      ------------------+------------+--------
        Outside class  ()               | valid      | not valid
        

        这为您提供了始终良好的 OOP 风格的封装。 :-)

        【讨论】:

        • 不,事件不是多播委托,正如属性是字段一样。
        • @JonSkeet:MSDN (msdn.microsoft.com/en-us/library/8627sbea(v=vs.100).aspx) 说:事件是一种特殊的多播委托,只能从声明它们的类或结构(发布者类)中调用。对于深入 c# 的人来说,这很肤浅!
        • MSDN 在这里完全是错误。 (这不是第一次了。)一个事件不是一种委托。它是一种允许订阅或删除代表的成员类型。 (事件的实现取决于类。)你认为字符串属性和字符串一样吗?这是一个类似的情况。很遗憾 MSDN 在这里是错误的,但没有必要传播它...... C# 规范(C# 4 规范的第 10.8 节)开始时相当准确 - 我建议你阅读。
        • @JonSkeet:啊,高高在上。字符串字段和字符串属性之间的比较和这种情况,嗯,不太适用,但随你的便!
        • 除了误导(和模糊),IMO - 确实不掌握事件和代表之间的区别,但似乎很少有人愿意尝试。所以当我纠正你不准确的时候,为什么不花点时间多学一点呢?这就像“默认情况下通过引用传递对象”的神话与通过值传递引用的现实之间的区别——解释更正确的模型需要更长的时间,但之后一切都变得更有意义。
        【解决方案7】:

        我认为使用委托和事件的主要区别在于事件只能由服务器引发(即类的作者)

        如果您现在删除event 关键字,您可以在Listener 中提高m.Tick(sender,e),否则不行。

        public class Listener
        {
          public void Subscribe(Metronome m)
          {
            m.Tick += new Metronome.TickHandler(HeardIt);
          }
        
          private void RaisTick(object sender, EventArgs e)
          {
              m.Tick(sender,e);
          }
          private void HeardIt(Metronome m, EventArgs e)
          {
             System.Console.WriteLine("HEARD IT");
          }
        

        }

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-07-23
          • 1970-01-01
          • 1970-01-01
          • 2018-05-18
          • 2010-10-08
          • 2017-03-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多