C# 是否通过ref关键字从委托取消订阅到订阅方法?

C# 是否通过ref关键字从委托取消订阅到订阅方法?,c#,parameters,delegates,ref,unsubscribe,C#,Parameters,Delegates,Ref,Unsubscribe,我有以下课程: public class Terminal : IDisposable { readonly List<IListener> _listeners; public Terminal(IEnumerable<IListener> listeners) { _listeners = new List<IListener>(listeners); } public void Subscrib

我有以下课程:

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?
    }
}
公共类终端:IDisposable
{
只读列表监听器;
公共终端(IEnumerable侦听器)
{
_侦听器=新列表(侦听器);
}
公共无效订阅(参考操作源)
{
源+=广播;
//以某种方式存储引用?
}
无效广播(字符串消息)
{
foreach(var listener in_listeners)listener.Listen(message);
}
公共空间处置()
{
//取消订阅所有存储的源?
}
}
我搜索了一段时间,似乎无法存储使用ref关键字传递的参数。试图将源参数添加到列表或将其分配给字段变量,不允许它保留对实际委托的原始引用的引用;因此,我的问题是:

  • 有没有一种方法可以在不再次传递参考资料的情况下取消所有来源的订阅
  • 如果不是,如何更改类以支持它,但仍然通过方法传递委托来维护订阅
  • 不使用反射就可以实现吗
  • 是否可以在不将委托/事件包装到类中,然后将该类作为订阅的参数传递的情况下实现它
多谢各位


编辑:如果不使用包装器或反射,似乎就无法解决给定的问题。我的意图是使类尽可能具有可移植性,而不必将委托封装在助手类中。谢谢大家的贡献。

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

我建议在操作上创建包装器类:

class ActionWrapper
{
    public Action<string> Action;
}
现在您应该可以得到所需的结果了。

公共类终端:IDisposable
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; 
  }
}
{ 列出听众; 列出来源; 公共终端(IEnumerable侦听器) { _侦听器=新列表(侦听器); _来源=新列表(); } 公共无效订阅(参考操作源) { _来源。添加(来源); 源+=广播; } 无效广播(字符串消息) { foreach(var listener in_listeners)listener.Listen(message); } 公共空间处置() { foreach(var s in_sources)s-=广播; } }
编辑:

是的,我的坏委托是不可变的类型,所以将方法添加到调用列表实际上会创建一个新的委托实例

这导致对你的问题的回答是否定的。要取消订阅代理,您需要从代理的调用列表中删除
广播
方法。这意味着创建一个新委托并将其分配给原始字段或变量。但一旦退出
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;
    }
}
公共接口IMessageSource
{
事件对消息的作用;
}
公共类消息源:IMessageSource
{
关于信息的公共事件行动;
公共无效发送(字符串m)
{
如果(OnMessage!=null)OnMessage(m);
}
}
公共类终端:IDisposable
{
私有IList源=新列表();
公共无效订阅(IMessageSource源)
{
source.OnMessage+=广播;
来源。添加(来源);
}
无效广播(字符串消息)
{
控制台写入线(消息);
}
公共空间处置()
{
foreach(源中的var s)s.OnMessage-=广播;
}
}
原始答案

您将
source
委托作为
ref
传递是否有特殊原因?例如,如果希望从方法返回不同的委托,则需要使用此方法


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

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


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

这相当简单,但也有一些缺陷。如果存储对源对象的引用,正如目前大多数示例所建议的那样,对象将不会被垃圾收集。避免这种情况的最好方法是使用WeakReference,这将允许GC正常工作

所以,你所要做的就是:

1) 将源列表添加到类:

private readonly List<WeakReference> _sources = new List<WeakReference>();
private readonly List_sources=new List();
2) 将源添加到列表中:

public void Subscribe(ref Action<string> source)
{
    source += Broadcast;
    //Store the reference 
    _sources.Add(new WeakReference(source));
}
public void Subscribe(参考操作源)
{
源+=广播;
//存储引用
_添加(新的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();
}
public void Dispose()
{
foreach(来源中的var r)
{
变量源=(操作)r.目标;
如果(源!=null)
{
源-=广播;
source=null;
}
}
_资料来源:Clear()
public void Dispose()
{
    foreach (var r in _sources)
    {
        var source = (Action<string>) r.Target;
        if (source != null) 
        {
            source -= Broadcast;
            source = null;
        }
    }


    _sources.Clear();
}
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");
    }