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

时间:2011-12-14 19:58:10

标签: 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 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关键字传递的参数。尝试将源参数添加到列表或将其分配给字段变量不允许它保持对实际委托的原始引用的引用;所以我的问题是:

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

谢谢。

编辑:看来,如果不使用Wrapper或Reflection,则无法解决给定问题。我的目的是使类尽可能地可移植,而不必将代理包装在辅助类中。感谢大家的贡献。

6 个答案:

答案 0 :(得分:1)

修改:好的,这是一个坏主意,所以回归基础:

我建议在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;
}

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

答案 1 :(得分: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; 
  }
}

答案 2 :(得分: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传递...

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

这很简单,但有一些陷阱。如果存储对源对象的引用,就像到目前为止提出的大多数示例一样,该对象将不会被垃圾回收。避免这种情况的最佳方法是使用WeakReference,这将允许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)然后只需实施dispose:

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


    _sources.Clear();
}

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

答案 5 :(得分:0)

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

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");
    }