2016-11-06 1 views
1

私はイベントアグリゲータに手をかけています。私は実際のアクションメソッドを格納するラッパークラスにアクションを格納しています。Delegate、アクションメソッドのクラスWeakReferenceとなっています。WeakReference to Actionターゲットは常に匿名のアクションのために生きています

イベントハンドラを呼び出すときは、最初にWeakReference.IsAliveがtrueであるかどうかをチェックします。そうでなければ、それを呼び出すか、そうでなければコレクションから削除します。これは、それが所属するオブジェクトがNULL /ガベージコレクションされていることを意味するからです。

ただし、匿名のアクションを作成してイベントアグリゲータに追加すると、親クラスをnullにして(ガベージコレクタを強制的に実行しても)常にアクティブになります。

匿名メソッドでIsAliveをFALSEに設定するにはどうすればよいですか? GC.Collect()と呼ばれ、私は新しいイベントを送信した後

void AddHandler<TEvent>(Action<TEvent> callback) { 
    InternalHandler handler = new InternalHandler(callback); 
    // Store the handler somewhere to use the callback later, if it still alive 
} 

class InternalHandler { 
    WeakReference _reference; 
    Delegate _method; 

    public InternalHandler(Delegate handler) { 
     _reference = new WeakReference(handler.Target); 

     Type messageType = handler.Method.GetParameters()[0].ParameterType; 
     Type delegateType = typeof(Action<,>).MakeGenericType(handler.Target.GetType(), messageType); 

     _method = Delegate.CreateDelegate(delegateType, handler.Method); 
    } 

    bool IsAlive => _reference != null && _reference.IsAlive; 

    bool Invoke(object data) { 
     if (!IsAlive) return false; 

     if (_reference.Target != null) _method.DynamicInvoke(_reference.Target, data); 

     return true; 
    } 
} 

とテストアプリケーション

TempObject t = new TempObject(); 

// Some time later 
t = null; 
GC.Collect(); 

class TempObject { 

    public TempObject() { 
     myHandler.AddHandler<SomeObject>(o => { 
      // Some code with a breakpoint 
     }); 
    } 
} 

で、tで匿名メソッドにブレークポイントをまだ呼ばれています!

WeakReferenceを匿名メソッドにするにはどうすればよいですか?

答えて

1

ベロウI 匿名のTempObjectハンドラでインスタンス変数を参照しない(したがって "this"を使用しない)と仮定すると、そのような前提では動作が観察されるためです。

理由を十分に理解するには、最も簡単な方法は、言及した匿名メソッドに対してC#コンパイラによって生成されたコードを検索することです。コードがあるために使用するクラスや変数の名前ではなく、判読できませんが、ここでは少し清書バージョンです:

class TempObject { 
    public TempObject(Handlers handlers) { 
     handlers.AddHandler<object>(GeneratedClass._staticAction ?? (GeneratedClass._staticAction = GeneratedClass._staticField.Handler)); 
    } 

    [CompilerGenerated] 
    [Serializable] 
    private sealed class GeneratedClass { 
     public static readonly TempObject.GeneratedClass _staticField; 
     public static Action<object> _staticAction; 

     static GeneratedClass() { 
      TempObject.GeneratedClass._staticField = new TempObject.GeneratedClass(); 
     } 

     public GeneratedClass() { 

     } 

     internal void Handler(object o) { 
      Console.WriteLine(o); 
     } 
    } 
} 

あなたはここに1 静的を持っている(ここではGeneratedClass命名)そのコンパイラ生成された新しいクラスを参照してくださいこのクラスのインスタンスへの参照を持つフィールドと、匿名ハンドラへの参照がキャッシュされる別の静的フィールドです。だから、あなたの匿名デリゲートは、実際にはGeneratedClassクラスのインスタンスのインスタンスメソッド(Handlerという名前)です。このインスタンスはstaticフィールドに格納されています。

この時点で、なぜこのような動作を観察するのかを理解しているはずです。あなたの参照がnullに設定されることはありませんstaticフィールドに、この場合の参照で

_reference = new WeakReference(handler.Target); 

そしてhandler.Targetので、ガベージコレクションになることはありません。

直感的にそれを理解する別の方法はないインスタンスメソッドに、あなたはあなたの匿名ハンドラでTempObjectインスタンスに関連したものを使用することはありません、ので、あなたの匿名メソッドは、基本的には(概念的には)静的メソッドへの参照であるということです。したがって、実際にはTempObjectインスタンスとの関係はなく、寿命はTempObjectsの寿命とは関係ありません。だから、基本的には同じです:

class TempObject { 
    public TempObject(Handlers handlers) { 
     handlers.AddHandler<object>(Handler); 
    } 

    private static void Handler(object arg) { 
     Console.WriteLine(arg); 
    } 
} 

そして、もちろん、それは実際には、静的メソッドのために、handler.Targetはnullになります(期待どおりメソッドがstaticメソッドのために動作しないことは驚くべきことではないですので、あなたのコードが失敗します私は考えます)。

は、今度は、このビットを変更してみましょう:あなたのハンドラ 「は、この」ので、それがTempObjectクラスのインスタンスに関連している参照を行い

class TempObject { 

    public TempObject(Handlers handlers) { 
     handlers.AddHandler<object>(o => { 
      Console.WriteLine(o + this.Name); 
     }); 
    } 

    public string Name { get; set; } 
} 

この時間。コンパイラはここでは示しませんが、このコードでは別のコードを生成しますが、この場合、TempObjectがガベージコレクトされたときにWeakReferenceが生きていないので、期待どおりに動作します。

+0

詳細な返答ありがとうございました:)私は、「この」という参照を追加すると、TempObjectがGCされたときにGC'edを取得することに気付きました。しかし、私は "this"を参照しないで、静的なGeneratedClassが作成されることを意味するいくつかの用途を持っています。 TempObjectをGCしたときにこの静的参照を削除する方法はありませんか?もし存在すれば、どうすれば '_reference.Target!= null'が静的であってもそれを確実にすることができますか?言い換えれば、 "this"を参照する必要なしに、この問題の回避策をどのように作成できますか? – GTHvidsten

+1

いいえ、削除する方法はありません。読み取り専用なので(たとえ何らかの形で反射を使用していたとしても)通常の静的メソッドを渡すのと同じ話です。ターゲットはnullで、あなたはそれで何もできません。 AddHandler(owner、(0)=> ....)明示的に "owner"オブジェクトを渡すことをお勧めします。あなたが弱い参照のためにそれを使用する - 使用する。これをオプションにすると、APIを悪用する可能性があるので、必要になると考えるかもしれません。 – Evk

+0

私は今理解していると思います。好奇心のために、これはすべての匿名メソッド、またはイベントハンドラが参照を保持しているメソッドのみに発生しますか?これが私のイベントハンドラに関係なく発生した場合、これはC#.Netのメモリリークのようなものではありませんか? – GTHvidsten

関連する問題