【问题标题】:How to create a callback variable in C# (true singlecast delegate)如何在 C# 中创建回调变量(真正的单播委托)
【发布时间】:2012-09-06 12:50:51
【问题描述】:

如何在 C# 中创建一个真正的单播委托。即一个委托实例,它可以在其调用列表中引用一个(并且只有一个)方法,因此可以用作回调(对于单个订阅者)而不是事件(可能有很多订阅者)。

Framework 有 System.Delegate 和 System.MulticastDelegate 类,这给人一种错误的印象,即 System.Delegate 是单播,而 System.Multicast 委托添加了多播功能。但是 System.Delegate http://msdn.microsoft.com/en-us/library/system.delegate.aspx 的 MSDN 文档表明 System.Delegate 实际上是多播...

“委托的调用列表是一组有序的委托,其中列表的每个元素恰好调用委托所代表的方法之一。”

...虽然 System.MulticastDelegate 的文档并没有真正解释它提供了哪些额外的行为。

这方面的官方文档相当混乱,但有一点很清楚,最终用户不能从 System.Delegate 或 System.MulticastDelegate 派生。那么框架是否支持创建真正的单播委托,该委托可以用作变量来存储对单个回调的引用?

@dtb。如果我可以使用单播委托,则无需运行时检查。当然,应用程序逻辑仍然可能以其他方式失败,例如分配了错误的处理程序,但至少如果我使用单播委托,那么存在多个处理程序的问题我只期望一个是一个简单的问题不可能存在,因此需要检查的东西更少,单元测试更简单,设计更优雅。此外,如果具有返回值的方法的委托在其调用列表中有多个处理程序,则返回给调用者的是列表中最后一个处理程序返回的值,而不是第一个处理程序。

【问题讨论】:

  • 您可以在运行时检查调用列表是否包含多个目标。但是为什么要打扰呢?如果委托有返回值,则仅使用 (iirc) 第一个目标返回的值。如果调用者传递了具有多个目标的实例,则这是调用者的问题,而不是被调用者的问题。

标签: c# .net callback


【解决方案1】:

如果您真的需要一个带有事件的“单播”委托,为什么不简单地为该事件实现自己的添加/删除方法呢?

Delegate 的“问题”是MulticastDelegate 是派生类,因此如果有人将MulticastDelegate 对象分配给Delegate 变量,那么您将始终拥有MulticastDelegate

例如,我们可以将默认的event 实现简化为:

private ChangedEventHandler _changed;
public event ChangedEventHandler Changed
{
   add
   {
      _changed += value;
   }
   remove
   {
      _changed -= value;
   }
}

现在让我们将事件实现更改为:

private ChangedEventHandler _changed;
public event ChangedEventHandler Changed
{
   add
   {
      _changed = value; // Do NOT combine delegates
   }
   remove
   {
      _changed -= value;
   }
}

现在您拥有的是(几乎)单播委托,由于事件语法,用户不能直接分配多播委托,并且只存储最后分配的委托。如果您的用户真的是恶意的,他们可以创建一个MulticastDelegate,然后将该委托添加到您的事件处理程序。如果您确实需要防止这种情况,您可以将此检查添加到 add 方法中:

if (value.GetInvocationList().Length > 1)
    throw new ArgumentException("MulticastDelegates are not allowed here.");

【讨论】:

  • 这很好,但是有没有办法实现运行时强制而不是编译时强制?
  • @Neutrino 如果某些事情不能在编译时完成,那么你确定它不会在运行时完成!这意味着:您在引发事件时不需要任何额外检查,因为您确定它不能是 MulticastDelegate(并且“if value.GetInvocationList()...”是运行时检查)。
  • 哎呀,我的意思是“但有没有办法实现编译时强制而不是运行时强制?”
  • @Neutrino 与自定义事件添加/删除你确定它不会发生(但用户会“+=”即使实际上它只是“=”......令人困惑)。我认为最好的解决方案是 DanBryant 提出的:它明确了你想要什么。
【解决方案2】:

这比使用委托要尴尬一些,但一种方法是使用您想要的方法传递回调接口。由于该方法只能有一个实现,因此只能注册一个回调。

public interface IDoSomething
{
    void DoSomething();
}

public sealed class MyClass
{
    private IDoSomething _doer;

    //We use a Set method rather than a property to prevent other classes from accessing the callback
    //Another common (and generally better) pattern is to pass the instance into the constructor
    public void SetSomethingDoer(IDoSomething doer)
    {
        _doer = doer;
    }

    //Other code can now access _doer to call back the method
}

这具有允许您将多个回调方法组合在一起的附带好处,这在您试图保证单个回调处理程序的情况下通常是有意义的。

【讨论】:

  • +1 我同意,在这种情况下,如果没有充分的理由使用委托(也许是兼容性?),那么最好使用接口(即使它使代码更加冗长)。
  • 但是SetSomethingDoer 不能依赖DoSomething 不会调用多个方法。我不明白这里的重点。
  • @dtb,这给您带来的唯一好处是您知道只有一种方法可以直接调用。当您有一个函数时,这更适用,因为多播可能会调用多个函数,并且如果有多个委托,则返回值可能会丢失。使用这种模式,只有一个返回值。如果接口实现调用其他方法,那就是实现细节。除此之外,这主要是语义上的差异(很明显,目的是只允许一个处理程序。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-23
  • 2018-12-31
  • 1970-01-01
  • 2011-01-12
  • 2012-01-18
  • 2011-01-16
相关资源
最近更新 更多