9

我有以下课程:

public class Terminal : IDisposable
{
    readonly List<IListener> _listeners;

    public Terminal(IEnumerable<IListener> listeners)
    {
        _listeners = new List<IListener>(listeners);
    }

    public void Subscribe(ref Action<string> source)
    {
        source += Broadcast;
        //Store the reference somehow?
    }

    void Broadcast(string message)
    {
        foreach (var listener in _listeners) listener.Listen(message);
    }

    public void Dispose()
    {
        //Unsubscribe from all the stored sources?
    }
}

我搜索了一段时间,似乎无法存储使用 ref 关键字传递的参数。尝试将源参数添加到列表或将其分配给字段变量不允许它保留对实际委托的原始引用的引用;所以我的问题是:

  • 有没有办法取消订阅所有来源而不再次传递它们的引用?
  • 如果不是,如何更改类以支持它,但仍然通过方法传递委托来维护订阅?
  • 不使用反射是否可以实现它?
  • 是否可以在不将委托/事件包装在一个类中然后将该类作为订阅参数传递的情况下实现它?

谢谢你。

编辑:似乎不使用包装器或反射,没有解决给定问题的方法。我的目的是使该类尽可能可移植,而不必将委托包装在辅助类中。感谢大家的贡献。

4

6 回答 6

2

编辑:好的,这是一个坏主意,所以回到基础:

我建议在 Action 上创建一个包装类:

class ActionWrapper
{
    public Action<string> Action;
}

并重组您的初始类以使用包装器:

private ActionWrapper localSource;

public void Subscribe(ActionWrapper source)
{
    source.Action += Broadcast;
    localSource = source;        
}

public void Dispose()
{
    localSource.Action -= Broadcast;
}

现在你应该得到想要的结果。

于 2011-12-14T20:05:05.680 回答
0
public class Terminal : IDisposable
{
  List<IListener> _listeners;
  List<Action<string>> _sources;

  public Terminal(IEnumerable<IListener> listeners)
  {
      _listeners = new List<IListener>(listeners);
      _sources = new List<Action<string>>();
  }

  public void Subscribe(ref Action<string> source)
  {
      _sources.Add( source );
      source += Broadcast;
  }

  void Broadcast(string message)
  {
      foreach (var listener in _listeners) listener.Listen(message);
  }

  public void Dispose()
  {
      foreach ( var s in _sources ) s -= Broadcast; 
  }
}
于 2011-12-14T20:06:23.077 回答
0

编辑:

是的,我的错误 - 委托是不可变类型,因此将方法添加到调用列表实际上会创建一个新的委托实例。

这导致您的问题的答案是否定的。要取消订阅委托,您需要Broadcast从委托的调用列表中删除您的方法。这意味着创建一个新委托并将其分配给原始字段或变量。但是一旦你用尽了Subscribe方法,你就无法访问原件。另外,该原始字段/变量的其他副本可以在调用列表中包含您的方法。而且您无法了解所有这些并改变那里的价值观。

我建议为您的目的声明与事件的接口。这将是一种相当灵活的方法。

public interface IMessageSource
{
    event Action<string> OnMessage;
}

public class MessageSource : IMessageSource
{
    public event Action<string> OnMessage;

    public void Send(string m)
    {
        if (OnMessage!= null) OnMessage(m);
    }
}

public class Terminal : IDisposable
{
    private IList<IMessageSource> sources = new List<IMessageSource>();

    public void Subscribe(IMessageSource source)
    {
        source.OnMessage += Broadcast;
        sources.Add(source);
    }


    void Broadcast(string message)
    {
        Console.WriteLine(message);
    }

    public void Dispose()
    {
        foreach (var s in sources) s.OnMessage -= Broadcast;
    }
}

原始答案

您是否有特殊原因将source委托传递为ref?例如,如果您想从方法返回不同的委托,则需要这个。

否则,委托是引用类型,因此您可以订阅它而无需将其传递为ref...

于 2011-12-14T20:20:41.343 回答
0

我建议订阅方法应该返回一个 SubscriptionHelper 类的实现,它实现了 IDisposable。一个简单的实现是让 SubscriptionHelper 保存对订阅列表的引用和订阅委托的副本;订阅列表本身将是一个 List<SubscriptionHelper>,并且 SubscriptionHelper 的 Dispose 方法会将自己从列表中删除。请注意,如果同一个委托被多次订阅,每个订阅将返回不同的 SubscriptionHelper;在 SubscriptionHelper 上调用 Dispose 将取消已返回它的订阅。

这种方法比普通 .net 模式使用的 Delegate.Combine/Delegate.Remove 方法要干净得多,如果尝试订阅和取消订阅多目标委托,其语义会变得非常奇怪。

于 2011-12-14T20:25:37.353 回答
0

它相当简单,但有一些陷阱。如果您存储对源对象的引用,正如迄今为止的大多数示例所建议的那样,该对象将不会被垃圾收集。避免这种情况的最好方法是使用 Wea​​kReference,这将使 GC 正常工作。

所以,你所要做的就是:

1)向类添加源列表:

private readonly List<WeakReference> _sources = new List<WeakReference>();

2)将源添加到列表中:

public void Subscribe(ref Action<string> source)
{
    source += Broadcast;
    //Store the reference 
    _sources.Add(new WeakReference(source));
}

3)然后执行处置:

public void Dispose()
{
    foreach (var r in _sources)
    {
        var source = (Action<string>) r.Target;
        if (source != null) 
        {
            source -= Broadcast;
            source = null;
        }
    }


    _sources.Clear();
}

也就是说,还有一个问题是为什么必须将 Action 作为 ref 传递。在当前代码中,没有理由这样做。无论如何,它不会影响问题或解决方案。

于 2011-12-14T21:25:15.910 回答
0

也许,与其尝试存储对委托的引用,不如让调用订阅的东西使用其对委托对象的引用来为订阅和取消订阅创建操作。它是一个附加参数,但它仍然很简单。

public void Subscribe(Action<Action<string>> addHandler,Action<Action<string>> removeHandler)
    {
        //Prevent error for possibly being null in closure
        Action<string> onEvent = delegate { };

        //Broadcast when the event occurs, unlisten after (you could store onEvent and remove handler yourself)
        onEvent = (s) => { Broadcast(s); removeHandler(onEvent); };
        addHandler(onEvent);
    }

还有一个订阅示例。

public event Action<string> CallOccured;

    public void Program()
    {
        Subscribe(a => CallOccured += a, a => CallOccured -= a);
        CallOccured("Hello");
    }
于 2012-01-10T18:38:21.700 回答