ユーザーがフォルダーからドメイン オブジェクトを削除するなどの場合に更新するために、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();
}
}
}
複製するには:
- プラグインで Petrel を実行する
- オブジェクトを含むフォルダーを使用してプロジェクトをロードする
- フォルダを選択
- プラグイン メニュー項目を有効にする
- ポップアップを開いた状態で、フォルダ内のオブジェクトを削除します
- ポップアップしたフォームを閉じる
- フォルダ内のオブジェクトを削除する
メッセージ ログは、フォームが破棄された後もイベント ハンドラーが呼び出されていることを明確に示しているはずです。