ここで提供する情報は新しいものではなく、完全を期すために追加しただけです。
このコードの考え方は非常に簡単です。
- オブジェクトには一意のIDが必要ですが、これはデフォルトではありません。代わりに、次善の策に頼らなければなりません。
RuntimeHelpers.GetHashCode
、一種の一意のIDを取得することです。
- 一意性をチェックするには、これを使用する必要があることを意味します
object.ReferenceEquals
- ただし、それでも一意のIDが欲しいので、
GUID
、定義上一意である。
- 必要がない場合はすべてをロックしたくないので、は使用しません
ConditionalWeakTable
。
組み合わせると、次のコードが得られます。
public class UniqueIdMapper
{
private class ObjectEqualityComparer : IEqualityComparer<object>
{
public bool Equals(object x, object y)
{
return object.ReferenceEquals(x, y);
}
public int GetHashCode(object obj)
{
return RuntimeHelpers.GetHashCode(obj);
}
}
private Dictionary<object, Guid> dict = new Dictionary<object, Guid>(new ObjectEqualityComparer());
public Guid GetUniqueId(object o)
{
Guid id;
if (!dict.TryGetValue(o, out id))
{
id = Guid.NewGuid();
dict.Add(o, id);
}
return id;
}
}
これを使用するには、のインスタンスを作成し、UniqueIdMapper
それがオブジェクトに対して返すGUIDを使用します。
補遺
したがって、ここではもう少し進んでいます。少し書き留めさせてくださいConditionalWeakTable
。
ConditionalWeakTable
いくつかのことを行います。最も重要なことは、ガベージコレクターを気にしないことです。つまり、このテーブルで参照するオブジェクトは関係なく収集されます。オブジェクトを検索すると、基本的には上記の辞書と同じように機能します。
知りたくない?結局のところ、GCによってオブジェクトが収集されているとき、オブジェクトへの参照があるかどうかがチェックされ、ある場合は収集されます。だからからのオブジェクトがある場合ConditionalWeakTable
、なぜ参照されたオブジェクトが収集されるのでしょうか。
ConditionalWeakTable
他の一部の.NET構造でも使用される小さなトリックを使用します。オブジェクトへの参照を格納する代わりに、実際にはIntPtrを格納します。これは実際の参照ではないため、オブジェクトを収集できます。
したがって、この時点で、対処すべき2つの問題があります。まず、オブジェクトをヒープ上で移動できるため、IntPtrとして何を使用しますか?次に、オブジェクトにアクティブな参照があることをどのようにして知るのでしょうか。
- オブジェクトをヒープに固定することができ、その実際のポインタを保存できます。GCがオブジェクトを削除するためにヒットすると、ピンを外して収集します。ただし、これは固定されたリソースを取得することを意味します。これは、多くのオブジェクトがある場合(メモリの断片化の問題のため)はお勧めできません。これはおそらくそれがどのように機能するかではありません。
- GCがオブジェクトを移動すると、GCがコールバックし、参照を更新できます。これは、外部呼び出しによって判断して実装される方法である可能性があります
DependentHandle
少し洗練されていると思います。
- オブジェクト自体へのポインターではなく、GCからのすべてのオブジェクトのリスト内のポインターが格納されます。IntPtrは、このリストのインデックスまたはポインタです。リストは、オブジェクトが世代を変更した場合にのみ変更されます。その時点で、単純なコールバックがポインターを更新できます。Mark&Sweepの動作を覚えている場合、これはより理にかなっています。ピン留めはなく、削除は以前と同じです。これがでの動作方法だと思います
DependentHandle
。
この最後のソリューションでは、リストバケットが明示的に解放されるまで、ランタイムがリストバケットを再利用しないようにする必要があります。また、ランタイムへの呼び出しによってすべてのオブジェクトを取得する必要もあります。
彼らがこのソリューションを使用していると仮定すると、2番目の問題にも対処できます。Mark&Sweepアルゴリズムは、どのオブジェクトが収集されたかを追跡します。収集され次第、この時点でわかります。オブジェクトがオブジェクトがそこにあるかどうかを確認すると、 'Free'が呼び出され、ポインターとリストエントリが削除されます。オブジェクトは本当になくなっています。
この時点で注意すべき重要な点の1つConditionalWeakTable
は、が複数のスレッドで更新され、スレッドセーフではない場合、状況がひどくうまくいかないことです。その結果、メモリリークが発生します。これが、すべてのConditionalWeakTable
単純な「ロック」を行うです。これにより、これは発生しません。
もう1つ注意すべき点は、エントリのクリーンアップはときどき発生する必要があることです。実際のオブジェクトはGCによってクリーンアップされますが、エントリはクリーンアップされません。これがConditionalWeakTable
サイズが大きくなる理由です。特定の制限に達すると(ハッシュの衝突の可能性によって決定されます)、Resize
オブジェクトをクリーンアップする必要があるかどうかをチェックするがトリガーされます(クリーンアップする必要がある場合)free
、GCプロセスで呼び出され、IntPtr
ハンドルがます。
これがDependentHandle
直接公開されない理由でもあると思います。物事をいじってメモリリークを発生させたくないのです。そのための次善の策はWeakReference
、IntPtr
オブジェクトの代わりに)ですが、残念ながら「依存関係」の側面は含まれていません。
あとは、メカニックをいじって、依存関係の動作を確認できます。必ず複数回起動して、結果を確認してください。
class DependentObject
{
public class MyKey : IDisposable
{
public MyKey(bool iskey)
{
this.iskey = iskey;
}
private bool disposed = false;
private bool iskey;
public void Dispose()
{
if (!disposed)
{
disposed = true;
Console.WriteLine("Cleanup {0}", iskey);
}
}
~MyKey()
{
Dispose();
}
}
static void Main(string[] args)
{
var dep = new MyKey(true); // also try passing this to cwt.Add
ConditionalWeakTable<MyKey, MyKey> cwt = new ConditionalWeakTable<MyKey, MyKey>();
cwt.Add(new MyKey(true), dep); // try doing this 5 times f.ex.
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
Console.WriteLine("Wait");
Console.ReadLine(); // Put a breakpoint here and inspect cwt to see that the IntPtr is still there
}