3

ユーザーがフォルダーからドメイン オブジェクトを削除するなどの場合に更新するために、UI の一部は IObservableElementEnumerable.EnumerableChanged を使用します。

UI が破棄されると、イベントからサブスクライブを解除します...またはそう考えました。登録解除は効果がなく、イベント ハンドラーがまだ呼び出されていることがわかりました。これにより、多くの奇妙なバグが発生しましたが、メモリ リークも発生しました。

サブスクリプション解除が機能するのは、IObservableElementEnumerableFactory.GetEnumerable(obj) を再度呼び出す代わりに、IObservableElementEnumerable 参照を保存する場合のみです。しかし、これは、フォルダ オブジェクトへのライブ参照を保持する可能性が高く、フォルダ自体がユーザーによって削除されると壊れます。

これは、GetEnumerable() のドキュメントに次のように明確に記載されているため、特に不可解です。これは保証と解釈されるべきではありませんか?

サブスクリプション解除が機能しない理由があるはずですか?

次のコードは、Petrel 2011 の問題を再現します (メニュー拡張機能を備えた単純なプラグインに追加するか、ここ(DropBox)で完全なソリューションを入手してください)。

using System;
using System.Linq;
using System.Windows.Forms;
using Slb.Ocean.Core;
using Slb.Ocean.Petrel;
using Slb.Ocean.Petrel.Basics;
using Slb.Ocean.Petrel.UI;

namespace ObservableElementEnumerable
{
  public class OEEForm : Form
  {
    private Droid _droid;
    private bool _disposed;

    public OEEForm()
    {
      IInput input = PetrelProject.Inputs;
      IIdentifiable selected = input.GetSelected<object>().FirstOrDefault() as IIdentifiable;
      if (selected == null)
      {
        PetrelLogger.InfoOutputWindow("Select a folder first");
        return;
      }
      _droid = selected.Droid;

      GetEnumerable().EnumerableChanged += enumerable_EnumerableChanged;
      PetrelLogger.InfoOutputWindow("Enumerable subscribed");
    }

    protected override void Dispose(bool disposing)
    {
      base.Dispose(disposing);
      if (disposing && !_disposed)
      {
        GetEnumerable().EnumerableChanged -= enumerable_EnumerableChanged;
        PetrelLogger.InfoOutputWindow("Enumerable unsubscribed (?)");
        _droid = null;
        _disposed = true;
      }
    }

    IObservableElementEnumerable GetEnumerable()
    {
      if (_disposed)
        throw new ObjectDisposedException("OEEForm");
      object obj = DataManager.Resolve(_droid);
      IObservableElementEnumerableFactory factory = CoreSystem.GetService<IObservableElementEnumerableFactory>(obj);
      IObservableElementEnumerable enumerable = factory.GetEnumerable(obj);
      return enumerable;
    }

    void enumerable_EnumerableChanged(object sender, ElementEnumerableChangeEventArgs e)
    {
      PetrelLogger.InfoOutputWindow("Enumerable changed");
      if (_disposed)
        PetrelLogger.InfoOutputWindow("... but I am disposed and unsubscribed!");
    }
  }

  public static class Menu1
  {
    public static void OEEBegin1_ToolClick(object sender, System.EventArgs e)
    {
      OEEForm f = new OEEForm();
      f.Show();
    }
  }
}

複製するには:

  1. プラグインで Petrel を実行する
  2. オブジェクトを含むフォルダーを使用してプロジェクトをロードする
  3. フォルダを選択
  4. プラグイン メニュー項目を有効にする
  5. ポップアップを開いた状態で、フォルダ内のオブジェクトを削除します
  6. ポップアップしたフォームを閉じる
  7. フォルダ内のオブジェクトを削除する

メッセージ ログは、フォームが破棄された後もイベント ハンドラーが呼び出されていることを明確に示しているはずです。

4

1 に答える 1

3

イベントを接続することにより、基になる列挙型への参照を既に保持しています。イベントも参考になります。列挙可能なものへの参照を保持し、購読しているインスタンスと同じインスタンスから購読を解除するだけです。

ユーザーによって削除されたオブジェクトの問題に対処するには、delete イベントをリッスンする必要があります。

于 2012-06-12T19:02:31.043 に答える