.NETの一意のオブジェクト識別子
-
09-09-2019 - |
質問
インスタンスの一意の識別子を取得する方法はありますか?
GetHashCode()
同じインスタンスを指す 2 つの参照については同じです。ただし、2 つの異なるインスタンスは (非常に簡単に) 同じハッシュ コードを取得できます。
Hashtable hashCodesSeen = new Hashtable();
LinkedList<object> l = new LinkedList<object>();
int n = 0;
while (true)
{
object o = new object();
// Remember objects so that they don't get collected.
// This does not make any difference though :(
l.AddFirst(o);
int hashCode = o.GetHashCode();
n++;
if (hashCodesSeen.ContainsKey(hashCode))
{
// Same hashCode seen twice for DIFFERENT objects (n is as low as 5322).
Console.WriteLine("Hashcode seen twice: " + n + " (" + hashCode + ")");
break;
}
hashCodesSeen.Add(hashCode, null);
}
デバッグ アドインを作成しているのですが、プログラムの実行中に一意となる参照用の何らかの ID を取得する必要があります。
すでにインスタンスの内部アドレスを取得することができました。これは、ガベージ コレクター (GC) がヒープを圧縮する (= オブジェクトを移動する = アドレスを変更する) まで一意です。
スタック オーバーフローの質問 Object.GetHashCode() のデフォルト実装 関連しているかもしれません。
デバッガ API を使用してデバッグ中のプログラム内のオブジェクトにアクセスしているため、オブジェクトは私の制御下にはありません。私がオブジェクトを制御していれば、独自の一意の識別子を追加するのは簡単です。
すでに表示されているオブジェクトを検索できるように、ハッシュテーブル ID -> オブジェクトを構築するための一意の ID が必要でした。とりあえず、こんな感じで解決しました。
Build a hashtable: 'hashCode' -> (list of objects with hash code == 'hashCode')
Find if object seen(o) {
candidates = hashtable[o.GetHashCode()] // Objects with the same hashCode.
If no candidates, the object is new
If some candidates, compare their addresses to o.Address
If no address is equal (the hash code was just a coincidence) -> o is new
If some address equal, o already seen
}
解決
参考資料 は オブジェクトの一意の識別子。これを文字列などに変換する方法がわかりません。(これまで見てきたように) 参照の値は圧縮中に変更されますが、以前のすべての値 A は値 B に変更されるため、安全なコードに関する限り、依然として一意の ID です。
関係するオブジェクトが制御下にある場合は、次を使用してマッピングを作成できます。 弱い参照 (ガベージ コレクションの防止を避けるため) 選択した ID (GUID、整数など) への参照から。ただし、これにより、ある程度のオーバーヘッドと複雑さが追加されます。
他のヒント
.NET 4以降のみ
皆さん、朗報です!
この仕事に最適なツールは .NET 4 に組み込まれており、その名前は ConditionalWeakTable<TKey, TValue>
. 。このクラス:
- 辞書のように、任意のデータを管理オブジェクト インスタンスに関連付けるために使用できます (ただし、 は 辞書ではありません)
- メモリアドレスに依存しないため、ヒープを圧縮する GC の影響を受けません。
- テーブルにキーとして入力されたからといってオブジェクトを存続させるわけではないため、プロセス内のすべてのオブジェクトを永久に存続させることなく使用できます。
- 参照の等価性を使用してオブジェクトの同一性を決定します。moveover では、クラス作成者はこの動作を変更して使用できるようにすることはできません。 一貫して あらゆる種類のオブジェクトに対して
- オンザフライで設定できるため、オブジェクト コンストラクター内にコードを挿入する必要はありません。
ObjectIDGenerator のクラスチェックアウトされましたか?これは、あなたがしようとし、そして何マルクGravellは説明しているものを行います。
ObjectIDGeneratorは、以前に同定されたオブジェクトを追跡します。あなたは、オブジェクトのIDを要求した場合、ObjectIDGeneratorは、既存のIDを返す、または生成し、新しいIDを覚えているかどうかを知っています。
IDはObjectIDGeneratorインスタンスの生活のためのユニークです。一般的に、ObjectIDGeneratorの生活は、それを作成したフォーマッタと同じくらい長持ち。オブジェクトIDは、指定されたシリアル化されたストリーム内の意味を持ち、及びシリアル化されたオブジェクト・グラフ内の他の人への参照を持つオブジェクトトラッキングのために使用されます。
は、ハッシュテーブルを使用して、ObjectIDGeneratorは、IDがどのオブジェクトに割り当てられている保持します。一意各オブジェクトを識別するオブジェクト参照は、実行時のガベージコレクションヒープ内のアドレスです。オブジェクトの基準値は、シリアライズ中に変更することができるが、情報が正確であるように、テーブルは自動的に更新されます。
オブジェクトIDは、64ビットの数です。割り当ては1から始まるので、ゼロは有効なオブジェクトIDになることはありません。フォーマッタは、値null参照(Visual BasicではNothing)であるオブジェクト参照を表すためにゼロ値を選択することができます。
RuntimeHelpers.GetHashCode()
を助けるかもしれない( MSDN )。
あなたは、第二に、あなた自身のことを開発することができます。たとえばます:
class Program
{
static void Main(string[] args)
{
var a = new object();
var b = new object();
Console.WriteLine("", a.GetId(), b.GetId());
}
}
public static class MyExtensions
{
//this dictionary should use weak key references
static Dictionary<object, int> d = new Dictionary<object,int>();
static int gid = 0;
public static int GetId(this object o)
{
if (d.ContainsKey(o)) return d[o];
return d[o] = gid++;
}
}
あなたが最速のアクセスのためのあなた自身の、例えば、System.Guid.NewGuid()または単に整数のようにユニークなIDを持っているとどうなるかを選択することができます。
どのようにこの方法についてます:
新しい値に最初のオブジェクトのフィールドを設定します。第二の目的で同じフィールドが同じ値を持っている場合、それはおそらく同じインスタンスです。それ以外の場合は、異なるように出ます。
さて異なる新しい値に最初のオブジェクトのフィールドを設定します。第二の目的で同じフィールドが別の値に変更された場合、それは間違いなく同じインスタンスです。
バック終了時に、それの元の値に最初のオブジェクトのフィールドを設定することを忘れないでください。
問題?
Visual Studioで一意のオブジェクト識別子を作成することが可能である:ウォッチウィンドウでは、オブジェクト変数を右クリックし、コンテキストメニューからオブジェクトID の作るを選択します。
。残念ながら、これはマニュアルステップであり、そして私は、識別子は、コードを介してアクセスすることができます信じていません。
は、手動で、このような識別子を自分で割り当てる必要があります - 。インスタンス内、または外部のいずれか
データベースに関連するレコードの場合、主キーは有用である可能性がある(それでも重複を取得することができます)。また、どちらかのGuid
を使用するか、またはInterlocked.Increment
を使用して割り当て、独自のカウンタを維持する(そしてオーバーフローしそうにないことを十分に大きくし)ます。
私は、これは回答されていることを知っているが、それはあなたが使用できることに注意することは、少なくとも便利です。
http://msdn.microsoft.com/en-私たち/ライブラリ/ system.object.referenceequals.aspxする
どの直接あなたの「ユニークID」を与えるが、WeakReferencesと結合していないだろう(とHashSetの?)あなたは、さまざまなインスタンスを追跡するのはかなり簡単な方法を与えることができる。
ここで提供する情報は新しいものではなく、完全を期すために追加しただけです。
このコードの考え方は非常にシンプルです。
- オブジェクトには一意の 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 によって収集されるとき、GC はそのオブジェクトへの参照があるかどうかを確認し、存在する場合はそれらを収集します。したがって、そこからのオブジェクトがある場合、 ConditionalWeakTable
, では、なぜ参照されたオブジェクトが収集されるのでしょうか?
ConditionalWeakTable
は、他の .NET 構造でも使用されている小さなトリックを使用します。オブジェクトへの参照を保存する代わりに、実際には IntPtr を保存します。これは実際の参照ではないため、オブジェクトを収集できます。
したがって、この時点で対処すべき問題が 2 つあります。まず、オブジェクトはヒープ上で移動できるので、IntPtr として何を使用するでしょうか?次に、オブジェクトにアクティブな参照があることをどのようにして知ることができるでしょうか?
- オブジェクトをヒープ上に固定し、その実際のポインタを格納できます。GC が削除対象のオブジェクトにヒットすると、そのオブジェクトの固定を解除して収集します。ただし、これは固定されたリソースを取得することを意味するため、オブジェクトが多数ある場合には (メモリの断片化の問題により) これは良い考えではありません。おそらくこれは仕組みではありません。
- GC がオブジェクトを移動すると、コールバックが行われ、参照が更新されます。外部呼び出しから判断すると、このように実装されている可能性があります。
DependentHandle
- しかし、それはもう少し洗練されていると思います。 - オブジェクト自体へのポインタではなく、GC からのすべてのオブジェクトのリスト内のポインタが格納されます。IntPtr は、このリスト内のインデックスまたはポインターのいずれかです。リストは、オブジェクトの世代が変わるときにのみ変更され、その時点で単純なコールバックでポインターを更新できます。マーク&スイープの仕組みを覚えていれば、これはより理解できます。ピン止めはなく、取り外しも以前と同じです。これがどのように機能するかだと思います
DependentHandle
.
この最後の解決策では、リスト バケットが明示的に解放されるまでランタイムがリスト バケットを再利用しないことが必要です。また、ランタイムの呼び出しによってすべてのオブジェクトが取得されることも必要です。
彼らがこのソリューションを使用していると仮定すると、2 番目の問題にも対処できます。マーク アンド スイープ アルゴリズムは、どのオブジェクトが収集されたかを追跡します。収集され次第、この時点で分かります。オブジェクトが存在するかどうかを確認すると、オブジェクトは「Free」を呼び出し、ポインタとリスト エントリを削除します。物体は本当に消えてしまいました。
この時点で注意すべき重要な点は、次のような場合には物事がひどく間違った方向に進むということです。 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
}
特定の用途のために独自のコードでモジュールを作成している場合は、 マジネーターの手法 かもしれない 働いています。しかし、いくつかの問題もあります。
初め, 、公式文書にはそうあります ない ことを保証する GetHashCode()
一意の識別子を返します (「 Object.GetHashCode メソッド ()):
ハッシュ コードが等しいことがオブジェクトの同等性を意味すると想定すべきではありません。
2番, 非常に少量のオブジェクトがあると仮定します。 GetHashCode()
ほとんどの場合に機能しますが、このメソッドは一部の型によってオーバーライドされる可能性があります。
たとえば、クラス C を使用していて、それがオーバーライドされるとします。 GetHashCode()
常に 0 を返すようにします。そうすれば、C のすべてのオブジェクトは同じハッシュ コードを取得します。残念ながら、 Dictionary
, HashTable
他のいくつかの連想コンテナはこのメソッドを使用します。
ハッシュ コードは、Dictionary<TKey, TValue> クラス、Hashtable クラス、DictionaryBase クラスから派生した型などのハッシュ ベースのコレクションにオブジェクトを挿入して識別するために使用される数値です。GetHashCode メソッドは、オブジェクトの同等性を迅速にチェックする必要があるアルゴリズムにこのハッシュ コードを提供します。
したがって、このアプローチには大きな制限があります。
そして さらにもっと, 、汎用ライブラリを構築したい場合はどうすればよいでしょうか?使用されているクラスのソース コードを変更できないだけでなく、その動作も予測できません。
感謝しています ジョン そして サイモン 回答が投稿されているので、コード例とパフォーマンスに関する提案を以下に投稿します。
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Collections.Generic;
namespace ObjectSet
{
public interface IObjectSet
{
/// <summary> check the existence of an object. </summary>
/// <returns> true if object is exist, false otherwise. </returns>
bool IsExist(object obj);
/// <summary> if the object is not in the set, add it in. else do nothing. </summary>
/// <returns> true if successfully added, false otherwise. </returns>
bool Add(object obj);
}
public sealed class ObjectSetUsingConditionalWeakTable : IObjectSet
{
/// <summary> unit test on object set. </summary>
internal static void Main() {
Stopwatch sw = new Stopwatch();
sw.Start();
ObjectSetUsingConditionalWeakTable objSet = new ObjectSetUsingConditionalWeakTable();
for (int i = 0; i < 10000000; ++i) {
object obj = new object();
if (objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
if (!objSet.Add(obj)) { Console.WriteLine("bug!!!"); }
if (!objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
public bool IsExist(object obj) {
return objectSet.TryGetValue(obj, out tryGetValue_out0);
}
public bool Add(object obj) {
if (IsExist(obj)) {
return false;
} else {
objectSet.Add(obj, null);
return true;
}
}
/// <summary> internal representation of the set. (only use the key) </summary>
private ConditionalWeakTable<object, object> objectSet = new ConditionalWeakTable<object, object>();
/// <summary> used to fill the out parameter of ConditionalWeakTable.TryGetValue(). </summary>
private static object tryGetValue_out0 = null;
}
[Obsolete("It will crash if there are too many objects and ObjectSetUsingConditionalWeakTable get a better performance.")]
public sealed class ObjectSetUsingObjectIDGenerator : IObjectSet
{
/// <summary> unit test on object set. </summary>
internal static void Main() {
Stopwatch sw = new Stopwatch();
sw.Start();
ObjectSetUsingObjectIDGenerator objSet = new ObjectSetUsingObjectIDGenerator();
for (int i = 0; i < 10000000; ++i) {
object obj = new object();
if (objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
if (!objSet.Add(obj)) { Console.WriteLine("bug!!!"); }
if (!objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
public bool IsExist(object obj) {
bool firstTime;
idGenerator.HasId(obj, out firstTime);
return !firstTime;
}
public bool Add(object obj) {
bool firstTime;
idGenerator.GetId(obj, out firstTime);
return firstTime;
}
/// <summary> internal representation of the set. </summary>
private ObjectIDGenerator idGenerator = new ObjectIDGenerator();
}
}
私のテストでは、 ObjectIDGenerator
で 10,000,000 個のオブジェクト (上記のコードの 10 倍) を作成すると、オブジェクトが多すぎることを示す例外がスローされます。 for
ループ。
また、ベンチマーク結果は、 ConditionalWeakTable
実装は 1.8 倍高速です ObjectIDGenerator
実装。