【问题标题】:Multicast delegate weird behavior in C#?C# 中的多播委托奇怪行为?
【发布时间】:2012-12-06 16:46:09
【问题描述】:

我有一个简单的事件:

public class ClassA
{
    public event Func<string, int> Ev;
    public int Do(string l)
    {
        return Ev(l);
    }
}

还有两种方法:

  static int Display(string k)
        {
            return k.Length;
        }

  static int Display_2(string k)
        {
            return k.Length*10;
        }

我正在注册这个活动:

 ClassA a = new ClassA();
 a.Ev += Display;
 a.Ev += Display_2;

现在,我正在执行:

   Console.WriteLine(a.Do("aaa")); 

输出:

什么???

  • 他在调用列表中有 2 个方法!它确实运行了它们,但为什么它只显示last注册的结果?

  • "3" 的结果到哪里去了? (第一次调用)? (虽然display+display_2 都被执行了......我没想到console.write 会遍历结果。但也没想到他会决定显示哪个。

编辑:

【问题讨论】:

  • 这里没有代表数组。
  • 这是某种集合。您可以将其作为数组获取,但您的代码不能。只是提示选择更好的标题。
  • 它被称为“多播委托”,而不是“委托数组”。
  • @RoyiNamir 它怎么能全部归还?它不会是正确的类型来返回所有这些,它需要某种集合才能做到这一点,然后它就不会匹配它自己的签名。
  • @RoyiNamir 是的,你没想到它会做到这一点,我明白了。我问的是你确实期望它做什么,而不是你没有期望它做什么。

标签: c# .net delegates


【解决方案1】:

这里涉及三个方面:

  1. 事件的实现
  2. 委托组合的行为
  3. 调用其调用列表有多个条目的委托的行为

对于第 1 点,您有一个 field-like 事件。 C# 4 规范的第 10.8.1 节给出了一个示例,并指出:

Button 类的声明之外,Click 成员只能用于+=-= 运算符的左侧,如

b.Click += new EventHandler(...);

将委托附加到Click 事件的调用列表

(强调我的)。规范还明确指出,类字段事件会创建一个委托字段,该字段用于在类中进行调用。

更一般地说(第 2 点),C# 4 规范的第 7.8.4 节通过 ++= 讨论了委托组合:

委托组合。每个委托类型都隐式提供以下预定义运算符,其中D 是委托类型:

D operator +(D x, D y)

当两个操作数都属于某个委托类型D 时,二进制+ 操作符执行委托组合。 [...跳过xy为空的位...]否则,操作的结果是一个新的委托,当被调用时,调用第一个操作数,然后调用第二个操作数。

(再次强调我的。)

最后,第 3 点 - 事件调用和返回值。 C# 规范第 15.4 节指出:

如果委托调用包含输出参数或返回值,则它们的最终值将来自列表中最后一个委托的调用。

更一般地说,它取决于事件的实现。如果您使用使用“正常”委托组合/删除步骤的事件实现,则一切都会得到保证。如果你开始编写一个做疯狂事情的​​自定义实现,那就是另一回事了。

【讨论】:

    【解决方案2】:

    作为一般规则,事件返回值是没有意义的。

    如果您想从事件处理程序获取信息,事件处理程序更改输入参数或调用触发事件的任何对象的另一个方法来传达其他信息会更有意义。

    通常这甚至不会出现,因为事件在逻辑上将信息传递事件处理程序,并且不需要从事件处理程序获取信息.老实说,这是代码异味的标志。一个事件不应该关心是否有人订阅了它,他们可能是谁,他们可能在做什么,或者是否有任何订阅者。依赖它们的返回值只会造成过于紧密的耦合。

    【讨论】:

    • 如何回答我的问题:为什么只显示一个值?他为什么要决定哪些是可见的?
    • @RoyiNamir According to this post 它总是会返回列表中最后一个委托的值,但我的意思是你不应该首先依赖它。它不会出现在 C# 语言规范中,而是一个实现细节。
    • 它在(C# 或 CLR)规范中。但事实上,永远不要依赖它。
    • @Servy 知道调用列表正在执行还是不同的顺序(对我来说)非常重要。世界上并非所有人都是一样的
    【解决方案3】:

    调用多播非空委托返回已执行的 last 处理程序的值。

    您几乎无法控制谁是第一个或最后一个。这是一个糟糕的系统。

    这就是大多数事件和委托返回void的原因。

    【讨论】:

    • 你几乎无法控制谁是第一个或最后一个 ?它们的附加方式 - 这将是订单号?
    • 起初是的。但是在您删除并添加了一些处理程序之后,它变得更难预测,甚至可能是实现定义的。
    • 不确定是否可以。我相信你甚至不应该尝试。如果你真的需要,你可以创建一个管理你自己的代表列表。
    • @Servy - 每个事件都使用多播委托。
    • 使用类似字段的事件,将按订阅顺序调用委托 - 这一切都已明确定义。对于更一般的事件,当然不是这种情况 - 不能保证事件将提供明智的 add/remove 实现...但我会很惊讶看到一个没有。当然,调用不是你可以用 event 做的事情的一部分(在 C# 中)......
    【解决方案4】:

    事件只是按照它们附加的顺序进行迭代。因为您使用的是返回值,所以您获得的值是最后一次调用的值。

    不过,这并不是一个真正的正常事件模式。您可能想要更多类似的东西:

    public class MyEventArgs : EventArgs
    {
        public MyEventArgs()
        {
            Results = new List<int>();
        }
        public string InputString{get;set;}
        public List<int> Results{get;set;}
    }
    public event EventHandler<MyEventArgs> Ev
    public int Do(string l)
    {
        MyEventArgs e = new MyEventArgs();
        e.InputString = l;
        if(Ev != null) Ev(this, e);
        return e.Results.Sum();
    }
    

    然后

    static int Display(object sender, MyEventArgs e)
            {
                return e.Results.Add(k.Length);
            }
    
      static int Display_2(object sender, MyEventArgs e)
            {
                return e.Results.Add(k.Length*10);
            }
    

    【讨论】:

    • 这不是 Henk 所说的。
    • @RoyiNamir 怎么样?我认为两者之间没有矛盾。
    • 他说调用顺序可能与附加的不同
    猜你喜欢
    • 2013-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-30
    • 1970-01-01
    • 2017-06-14
    • 2011-09-11
    相关资源
    最近更新 更多