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

編集:OK、それは悪い考えだったので、基本に戻ります:

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

かなり単純ですが、いくつかの落とし穴があります。ソースオブジェクトへの参照を保存する場合、これまでの例のほとんどが提案しているように、オブジェクトはガベージコレクションされません。これを回避する最善の方法は、GCが正しく機能できるようにするWeakReferenceを使用することです。

だから、あなたがしなければならないのはこれだけです:

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

そうは言っても、なぜアクションを参照として渡さなければならないのかという問題もあります。現在のコードでは、その理由はありません。とにかく、それは問題や解決策に影響を与えません。

于 2011-12-14T21:25:15.910 に答える
0

サブスクリプション メソッドは、IDisposable を実装する SubscriptionHelper クラスの実装を返す必要があることをお勧めします。簡単な実装は、SubscriptionHelper がサブスクリプション リストへの参照とサブスクリプション デリゲートのコピーを保持することです。サブスクリプション リスト自体は List<SubscriptionHelper> になり、SubscriptionHelper の Dispose メソッドはそれ自体をリストから削除します。同じデリゲートが複数回サブスクライブされる場合、各サブスクリプションは異なる SubscriptionHelper を返すことに注意してください。SubscriptionHelper で Dispose を呼び出すと、返されたサブスクリプションがキャンセルされます。

このようなアプローチは、通常の .net パターンで使用される Delegate.Combine/Delegate.Remove メソッドよりもはるかにクリーンです。マルチターゲット デリゲートをサブスクライブおよびサブスクライブ解除しようとすると、セマンティクスが非常に奇妙になる可能性があります。

于 2011-12-14T20:25:37.353 に答える
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

おそらく、デリゲートへの参照を格納しようとする代わりに、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");
    }
于 2012-01-10T18:38:21.700 に答える
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 に答える