3

使い捨てのUIコントロールであるクラスがあります。モルデルオブジェクトの変更をサブスクライブして、コンテンツを再描画します。

一方、状況によっては、同じモデルオブジェクトの特別な変更により、このコントロールを含むビューに、このコントロール(コントロール)を削除して破棄するように指示されます。

その結果、サブスクリプションの順序に応じてモデルが変更されます(最初にコントロールが破棄され、その後メソッドが呼び出されます) 。これは最終的には。になりObjectDisposedExceptionます。

質問:コントロールはイベントコールバックを安全に無視するように設計する必要がありますか、それとも他のレイヤーからのこの種の呼び出しを防ぐようにする必要がありますか?

単語よりも多くのコードを見たい人のために、私は非常に単純化された例を用意しました:

//############################################
class View
{
    private Control m_Control;

    public View(Logic logic, Model model)
    {
        m_Control = new Control(model);
        logic.Changed += LogicChanged;
    }

    private void LogicChanged(object sender, EventArgs e)
    {
        m_Control.Dispose();
        m_Control = null;
    }
}

//############################################
class Control : IDisposable
{
    private readonly Model m_Model;

    public Control(Model model)
    {
        m_Model = model;
        m_Model.Changed += ModelOnChanged;
    }

    public bool IsDisposed { get; private set; }

    public void Dispose()
    {
        m_Model.Changed -= ModelOnChanged;
        IsDisposed = true;
    }

    private void ModelOnChanged(object sender, EventArgs e)
    {
        if (IsDisposed)
        {
            throw new ObjectDisposedException(ToString());
        }
        //Do something
    }
}

//############################################
class Model
{
    public event EventHandler<EventArgs> Changed;

    private void OnChanged(EventArgs e)
    {
        EventHandler<EventArgs> handler = Changed;
        if (handler != null)
            handler(this, e);
    }

    public void Change()
    {
        OnChanged(null);
    }
}

//############################################
class Logic
{
    private readonly Model m_Model;

    public Logic(Model model)
    {
        m_Model = model;
        m_Model.Changed += ModelOnChanged;
    }

    private void ModelOnChanged(object sender, EventArgs e)
    {
        OnChanged(null);
    }

    public event EventHandler<EventArgs> Changed;

    private void OnChanged(EventArgs e)
    {
        EventHandler<EventArgs> handler = Changed;
        if (handler != null)
            handler(this, e);
    }
}

//############################################
class Program
{
    private static void Main(string[] args)
    {
        var model = new Model();
        var logic = new Logic(model);

        var view = new View(logic, model);
        model.Change();
        //And crash!
    }
}

与えられた例のどこで修正を提案しますか?Modelそして、Logicクラスはイベントのサブスクリプションの順序を知らずにビジネスを行っているだけです。また、設計上の欠陥ViewControl実装も見当たりません。

Modelを実装している3つの異なるチームがあり、これら4つのコンポーネントだけでなく、何百ものコンポーネントがあると想像してくださいLogicUIこの問題はどこでも発生する可能性があります。

私が探しているのは、この特定のケースでのローカル修正ではありませんが、それを防ぐためのパターンを見つけたいと思います。例:「コントロールは、破棄されたインスタンスでのイベント呼び出しを適切に無視する必要があります」または「ロジックはモデルでのサブスクリプションを防止する必要があります。UIのみがこれを許可されます。」等


容認された答えに加えて

はい、破棄されたオブジェクトのイベントコールバックは例外をスローするべきではありません。さらに一般的に:

...イベントハンドラーは、イベントのサブスクライブが解除された後でも呼び出される場合でも、堅牢である必要があります。

それにはいくつかの理由があります-EricLippertの素晴らしい記事EventsandRacesを参照してください

4

4 に答える 4

2

私が見たパターンの1つは次のとおりです。処分された場合は、例外をスローする以外に何もしないでください。

private void ModelOnChanged(object sender, EventArgs e)
{
    if (IsDisposed) { return; } // i.e. Do nothing

    //Do something
}

このパターンの最大の問題の1つは、IDisposable決定論的および非決定論的なメモリ管理を同時に行おうとしていることです。あなたは電話することができますDispose()、またはGCはあなたのためにそれをすることができます。ファイナライザーなどですべての混乱を引き起こします。

単に参照カウンターを保持する言語(最後の参照がなくなったときに「デストラクタ」を呼び出す)とは異なり、.NETは、オブジェクトのメモリが解放された可能性があるが、そのオブジェクトへの参照がまだ存在するアプローチを選択します。したがって、コードが無効な状態のオブジェクトにアクセスしないようにする必要があります。これは通常、次の2つの形式のいずれかを取ります。

  1. IsDisposedすべてをチェックし、廃棄された場合はPublicスローしますObjectDisposedException
  2. オブジェクトが廃棄された場合は暗黙的に何もしません(早期返却)

最初のオプションは、あなたがすぐに間違いを犯したことを知っているので、長期的にはあなたを噛む可能性が低くなります。ただし、動作が予測できず、一時的な結合の問題がある場合はObjectDisposedException、プログラム全体を処理する必要があります。その場合は、「何もしない」アプローチを選択して、プログラム全体の毛羽立ちを少なくすることができます。残念ながら、あなたが呼び出した方法がそれを仕事にしたように見えるので、それはあなたを噛む可能性があります。

今まで考えていなかったもう1つのオプションは、Disposedクラスレベルで参照されているイベントオブジェクトをサブスクライブすることですIDisposable。オブジェクトが破棄されたら、フィールドをnullに設定します。同様に、オブジェクトで何かを行う前に、IsDisposed(公開されている場合)を確認できます(ジャンプする前に確認してください)。

Public Class Foo
  Private _disposableObject As IDisposableFoo

  Private Sub OnBarDisposed(sender As Object, e As EventArgs) Handles IDisposableFoo
    _disposableObject = Nothing 
    'Hmm, now we'll get null-references everywhere
  End Sub

と...

Public Sub DoesStuffWithIDisposableObject()
  If Me.DisposableObjectReference.IsDisposed Then Exit Sub

  'Yay, valid reference! Let's get stuff done!
End Sub

それでもおそらく最善の選択肢ではありませんが、残念ながら、言語の設計により、そのような種類のくだらないものは避けられません。

于 2012-07-24T17:45:43.453 に答える
2

を投げるかどうかを決定する際にObjectDisposedExceptionは、いくつかの要因を考慮する必要があります。

  1. オブジェクトは、使用できなくなったリソースを使用せずに、特定のメソッド呼び出しのコントラクトを満たすことができますか?
  2. 関数が正常に戻った場合、呼び出し元は必要なリソースなしでは実行できないことを期待する可能性があります。つまり、失敗は避けられないため、遅らせるのではなく早めにスローする必要があります。または、呼び出し元は正しいことを実行する可能性があります。例外がスローされない場合は?

多くの場合、特に「update-event」シナリオでは、呼び出し元は呼び出されたメソッドが何をするかを特に気にしません。呼び出しのセマンティクスは、基本的に「必要だと思うことは何でもする」です。このような操作は、オブジェクトが廃棄されている場合でも、何もする必要がないと判断するだけで、どのオブジェクトでも正常に実行できます。使用しているコールバックパターンで、イベントハンドラーがイベントパブリッシャーにそれ以上のイベントを受信したくないことを通知できない場合(Microsoftの通常のイベントパターンではそのような機能は提供されません)、Disposedオブジェクトがイベントパブリッシャーが誤った状態のままにされたためにコールバックを受信し続けますが、例外をスローしても、おそらくその問題を解決するのにあまり役立たず、さらに多くの問題が発生する可能性があります。

于 2012-07-24T22:50:44.103 に答える
1

何かが破棄されたコントロールで何かを行おうとすると、ObjectDisposedExceptionがスローされると思います。コントロールを破棄するということは、それが終わったことを意味するので、もう何もそれを使おうとしないはずです。もしそうなら、私はそれを修正する必要のあるプログラムのエラーと見なします。

于 2012-07-24T17:48:25.190 に答える
1

配置されたオブジェクトに関するガイドラインで、私は次のように読みました。

廃棄されたオブジェクトに関するいくつかのルールがあります。まず、すでに破棄されたオブジェクトに対してDisposeを呼び出しても、何も実行されません。次に、すでに破棄されたオブジェクトで他のプロパティまたはメソッドを呼び出すと、ObjectDisposedExceptionがスローされます

別のガイドライン

オブジェクトが廃棄されたら、立ち入り禁止と見なす必要があります。慣例により、Disposeが呼び出された後にそのメソッドのいずれかが呼び出された場合、使い捨てオブジェクトは例外をスローする必要があります。この正確な目的のためにフレームワーククラスライブラリに追加されたSystem名前空間には、ObjectDisposedExceptionと呼ばれる組み込みの例外タイプがあります。

これらのルールに従うと、コードは完全に問題ありません(ただし、例外がスローされます)。

両方の引用符は、そのメソッドまたはプロパティのいずれかが呼び出された場合、破棄されたオブジェクトは例外をスローする必要があることを示しています。「パブリックメソッドまたはプロパティのいずれかが呼び出された場合は、例外をスローします」と言いたいと思います。

プライベートメソッドが呼び出された場合、何もしないのが安全だと思います。したがって、あなたの質問に対する答えは次のとおりです。「はい、破棄されたコントロールはイベントコールバックを安全に無視できるはずです」。

ところで:たぶん、質問は「オブジェクトを処分する責任があるのは誰か」かもしれません。

与えられた例では、ModelOnChangedメソッドでコントロールがそれ自体を破棄できるようにすることができます。そのための一般的なガイドラインは見つかりませんでしたが、いくつかの提案がありました。

于 2012-07-24T20:09:25.647 に答える