取消订阅通过ref关键字传递给委托方法的委托?

我有以下课程:

public class Terminal : IDisposable { readonly List _listeners; public Terminal(IEnumerable listeners) { _listeners = new List(listeners); } public void Subscribe(ref Action 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关键字传递的参数。 尝试将源参数添加到列表或将其分配给字段变量不允许它保持对实际委托的原始引用的引用; 所以我的问题是:

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

谢谢。

编辑:似乎没有使用包装器或reflection,没有解决给定的问题。 我的目的是使类尽可能地可移植,而不必将代理包装在辅助类中。 谢谢大家的贡献。

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

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

 class ActionWrapper { public Action Action; } 

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

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

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

 public class Terminal : IDisposable { List _listeners; List> _sources; public Terminal(IEnumerable listeners) { _listeners = new List(listeners); _sources = new List>(); } public void Subscribe(ref Action 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; } } 

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

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

编辑:

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

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

我建议为您的目的声明与事件的接口。 这将是非常灵活的方法。

 public interface IMessageSource { event Action OnMessage; } public class MessageSource : IMessageSource { public event Action OnMessage; public void Send(string m) { if (OnMessage!= null) OnMessage(m); } } public class Terminal : IDisposable { private IList sources = new List(); 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传递给…

这很简单,但有一些陷阱。 如果存储对源对象的引用,就像到目前为止提出的大多数示例一样, 该对象将不会被垃圾回收 。 避免这种情况的最佳方法是使用WeakReference,这将允许GC正常工作。

所以,你所要做的就是:

1)向类中添加源列表:

 private readonly List _sources = new List(); 

2)将源添加到列表中:

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

3)然后只需实施配置:

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

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

也许,不是试图存储对委托的引用,而是调用Subscribe使用其对委托对象的引用来为订阅和取消订阅创建操作。 它是一个额外的参数,但它仍然很简单。

 public void Subscribe(Action> addHandler,Action> removeHandler) { //Prevent error for possibly being null in closure Action 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 CallOccured; public void Program() { Subscribe(a => CallOccured += a, a => CallOccured -= a); CallOccured("Hello"); }