13

アップデート

ここからのさまざまな回答を組み合わせて、新しい質問に対する「決定的な」回答を作成しました。

元の質問

私のコードには、アプリケーションの存続期間全体にわたって存在するイベント パブリッシャーがあります (ここでは必要最小限に縮小されています)。

public class Publisher
{
    //ValueEventArgs<T> inherits from EventArgs
    public event EventHandler<ValueEventArgs<bool>> EnabledChanged; 
}

このパブリッシャーはあらゆる場所で使用できるため、すべてのサブスクライバーで処理コードを書き直さなくても済むように、この小さなヘルパー クラスを作成できたことに非常に満足しています。

public static class Linker
{
    public static void Link(Publisher publisher, Control subscriber)
    {
         publisher.EnabledChanged += (s, e) => subscriber.Enabled = e.Value;
    }

    //(Non-lambda version, if you're not comfortable with lambdas)
    public static void Link(Publisher publisher, Control subscriber)
    {
         publisher.EnabledChanged +=
             delegate(object sender, ValueEventArgs<bool> e)
             {
                  subscriber.Enabled = e.Value;
             };
    }
}

小さなマシンで使い始めるまでは問題なく動作していましたが、時折発生するようになりました。

System.ComponentModel.Win32Exception
Not enough storage is available to process this command

結局のところ、サブスクライバー コントロールが動的に作成、追加、およびフォームから削除されるコード内の場所が 1 つあります。ガベージ コレクションなどについての高度な理解 (つまり、昨日まで何もなかった) を考えると、ほとんどの場合、サブスクライバーもアプリケーションの存続期間中存続するため、後片付けをするとは思いもしませんでした。

私はしばらくの間、Dustin Campbell の WeakEventHandlerをいじりましたが、匿名のデリゲートでは機能しません(とにかく私にとってはそうではありません)。

とにかくこの問題はありますか?ショップ全体で定型コードをコピーして貼り付ける必要はありません。

(ああ、わざわざコントロールを作成したり破棄したりする理由を私に尋ねることは気にしないでください。それは私の設計上の決定ではありません...)

(PS: これは winforms アプリケーションですが、VS2008 と .Net 3.5 にアップグレードしました。Weak Event パターンの使用を検討する必要がありますか?)

(PPS:Rory からの良い答えですが、誰かが WeakEventHandler に相当するものを思いつくことができれば、明示的に UnLink/Dispose することを覚えておく必要がなくなります。それはクールです...)

編集問題のコントロールを「リサイクル」することで、この問題を回避したことを認めなければなりません。ただし、使用していた「キー」が明らかに一意ではないため、回避策が戻ってきて私を悩ませています(すすり泣く)。ここで他のリンクを発見しました(これを試してみました-少し弱すぎるようです-ターゲットがまだ生きていてもGCはデリゲートをクリアします。以下のs、oɔɯǝɹの回答と同じ問題です)、ここ(パブリッシャーを変更する必要があり、そうではありません匿名のデリゲートでは実際には機能しません) およびhere (Dustin Campbell による不完全として引用)。

私が探しているものは意味的に不可能かもしれないと思います.クロージャーは「私がいなくなった後でもぶらぶらする」ように設計されています.

私は別の回避策を見つけたので、神々からの声を待って、それを使い続けます.

4

4 に答える 4

5

私はこの質問が古くからあることを知っていますが、地獄-私はそれを見つけました.他の人もそうかもしれないと思います. 関連する問題を解決しようとしていますが、洞察があるかもしれません。

あなたは、Dustin Campbell の WeakEventHandler について言及しました。実際、設計上、匿名メソッドでは機能しません。私は、a) 99% のケースで、彼の元の解決策がより安全であるようなものが必要であり、b) 私がしなければならないいくつかのケースで (注: 持っている) to、「ラムダはとてもきれいで簡潔だから」ではなく)少し賢くなれば、それを機能させることが可能です。

あなたの例は、少しトリッキーになるとかなり簡潔な解決策になる可能性がある、まさに1回限りのケースのようです。


public static class Linker {
    public static void Link(Publisher publisher, Control subscriber) {
        // anonymous method references the subscriber only through weak 
        // references,so its existance doesn't interfere with garbage collection
        var subscriber_weak_ref = new WeakReference(subscriber);

        // this instance variable will stay in memory as long as the  anonymous
        // method holds a reference to it we declare and initialize  it to 
        // reserve the memory (also,  compiler complains about uninitialized
        // variable otherwise)
        EventHandler<ValueEventArgs<bool>> handler = null;

        // when the handler is created it will grab references to the  local 
        // variables used within, keeping them in memory after the function 
        // scope ends
        handler = delegate(object sender, ValueEventArgs<bool> e) {
            var subscriber_strong_ref = subscriber_weak_ref.Target as Control;

            if (subscriber_strong_ref != null) 
                subscriber_strong_ref.Enabled = e.Value;
            else {
                // unsubscribing the delegate from within itself is risky, but
                // because only one instance exists and nobody else has a
                // reference to it we can do this
                ((Publisher)sender).EnabledChanged -= handler;

                // by assigning the original instance variable pointer to null
                // we make sure that nothing else references the anonymous
                // method and it can be collected. After this, the weak
                //  reference and the handler pointer itselfwill be eligible for
                // collection as well.
                handler = null; 
            }
        };

        publisher.EnabledChanged += handler;
    }
}

WPF の Weak Event パターンはオーバーヘッドが大きいと噂されているため、この特定の状況では使用しません。さらに、WinForm アプリでコア WPF ライブラリを参照することも少し重いようです。

于 2009-09-19T03:17:05.033 に答える
4

匿名デリゲートへの参照を保持し、コントロールがフォームから削除されたときにそれを削除すると、コントロールと匿名デリゲートの両方をガベージ コレクションできるようになります。

だから、このようなもの:

public static class Linker
{

    //(Non-lambda version, I'm not comfortable with lambdas:)
    public static EventHandler<ValueEventArgs<bool>> Link(Publisher publisher, Control subscriber)
    {
         EventHandler<ValueEventArgs<bool>> handler = delegate(object sender, ValueEventArgs<bool> e)
             {
                  subscriber.Enabled = e.Value;
             };
         publisher.EnabledChanged += handler;
         return handler;
    }

    public static void UnLink(Publisher publisher, EventHandler<ValueEventArgs<bool>> handler)
    {
        publisher.EnabledChanged -= handler;
    }

}

デリゲートを削除する例については、「C# の匿名メソッドのサブスクライブ解除」を参照してください。

于 2008-12-16T12:26:15.797 に答える
1

WeakReference に基づいて最近作成したサンプル コード:

// strongly typed weak reference
public class WeakReference<T> : WeakReference
    where T : class
{
    public WeakReference(T target)
        : base(target)
    { }

    public WeakReference(T target, bool trackResurrection)
        : base(target, trackResurrection)
    { }

    public new T Target
    {
        get { return base.Target as T; }
        set { base.Target = value; }
    }
}

// weak referenced generic event handler
public class WeakEventHandler<TEventArgs> : WeakReference<EventHandler<TEventArgs>>
    where TEventArgs : EventArgs
{
    public WeakEventHandler(EventHandler<TEventArgs> target)
        : base(target)
    { }

    protected void Invoke(object sender, TEventArgs e)
    {
        if (Target != null)
        {
            Target(sender, e);
        }
    }

    public static implicit operator EventHandler<TEventArgs>(WeakEventHandler<TEventArgs> weakEventHandler)
    {
        if (weakEventHandler != null)
        {
            if (weakEventHandler.IsAlive)
            {
                return weakEventHandler.Invoke;
            }
        }

        return null;
    }
}

// weak reference common event handler
public class WeakEventHandler : WeakReference<EventHandler>
{
    public WeakEventHandler(EventHandler target)
        : base(target)
    { }

    protected void Invoke(object sender, EventArgs e)
    {
        if (Target != null)
        {
            Target(sender, e);
        }
    }

    public static implicit operator EventHandler(WeakEventHandler weakEventHandler)
    {
        if (weakEventHandler != null)
        {
            if (weakEventHandler.IsAlive)
            {
                return weakEventHandler.Invoke;
            }
        }

        return null;
    }
}

// observable class, fires events
public class Observable
{
    public Observable() { Console.WriteLine("new Observable()"); }
    ~Observable() { Console.WriteLine("~Observable()"); }

    public event EventHandler OnChange;

    protected virtual void DoOnChange()
    {
        EventHandler handler = OnChange;

        if (handler != null)
        {
            Console.WriteLine("DoOnChange()");
            handler(this, EventArgs.Empty);
        }
    }

    public void Change()
    {
        DoOnChange();
    }
}

// observer, event listener
public class Observer
{
    public Observer() { Console.WriteLine("new Observer()"); }
    ~Observer() { Console.WriteLine("~Observer()"); }

    public void OnChange(object sender, EventArgs e)
    {
        Console.WriteLine("-> Observer.OnChange({0}, {1})", sender, e);
    }
}

// sample usage and test code
public static class Program
{
    static void Main()
    {
        Observable subject = new Observable();
        Observer watcher = new Observer();

        Console.WriteLine("subscribe new WeakEventHandler()\n");
        subject.OnChange += new WeakEventHandler(watcher.OnChange);
        subject.Change();

        Console.WriteLine("\nObserver = null, GC");
        watcher = null;
        GC.Collect(0, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();

        subject.Change();

        if (Debugger.IsAttached)
        {
            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);
        }
    }
}

次の出力が生成されます。

new Observable()
new Observer()
subscribe new WeakEventHandler()

DoOnChange()
-> Observer.OnChange(ConsoleApplication4.Observable, System.EventArgs)

Observer = null, GC
~Observer()
DoOnChange()
~Observable()
Press any key to continue . . .

(登録解除 (-=) は機能しないことに注意してください)

于 2009-06-05T13:45:20.800 に答える
0

Egor の回答をさらに発展させて、アタッチするイベントを事前決定する必要のないバージョンを試してみたかったのです。

私はそれを汎用イベントハンドラーでのみ機能させることができました.「標準」イベントハンドラー(例: FormClosingEventHandler )の場合、型の制約を設定できないためwhere T : delegate(名前がPonyで終わらない限り)、少し注意が必要です。

private static void SetAnyGenericHandler<S, T>(
     Action<EventHandler<T>> add,     //to add event listener to publisher
     Action<EventHandler<T>> remove,  //to remove event listener from publisher
     S subscriber,                    //ref to subscriber (to pass to consume)
     Action<S, T> consume)            //called when event is raised*
         where T : EventArgs 
         where S : class
{
    var subscriber_weak_ref = new WeakReference(subscriber);
    EventHandler<T> handler = null;
    handler = delegate(object sender, T e)
    {
        var subscriber_strong_ref = subscriber_weak_ref.Target as S;
        if(subscriber_strong_ref != null)
        {
            Console.WriteLine("New event received by subscriber");
            consume(subscriber_strong_ref, e);
        }
        else
        {
            remove(handler);
            handler = null;
        }
    };
    add(handler);
}

(*EventHandler<T> consumeここで試してみましたが、ラムダの消費で s を Subscriber にキャストする必要があるため、呼び出しコードが醜くなります。)

上記の例から取った呼び出しコードの例:

SetAnyGenericHandler(
    h => publisher.EnabledChanged += h, 
    h => publisher.EnabledChanged -= h, 
    subscriber, 
    (Subscriber s, ValueEventArgs<bool> e) => s.Enabled = e.Value);

または、必要に応じて

SetAnyGenericHandler<Subscriber, ValueEventArgs<bool>>(
    h => publisher.EnabledChanged += h, 
    h => publisher.EnabledChanged -= h, 
    subscriber, 
    (s, e) => s.Enabled = e.Value);

Event を 1 つのパラメーターとして渡すことができればよいのですが、プロパティから get/set にアクセスできる以上に、イベントから add/remove にアクセスすることはできません (厄介なリフレクションを行わずに、私は思います)。 )。

于 2009-11-06T12:26:03.830 に答える