web-dev-qa-db-ja.com

.NETの一意のオブジェクト識別子

インスタンスの一意の識別子を取得する方法はありますか?

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)がヒープを圧縮する(=オブジェクトを移動する=アドレスを変更する)まで一意である、インスタンスの内部ADDRESSを取得できました。

Stack Overflowの質問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
}
108
Martin Konicek

参照オブジェクトの一意の識別子です。これを文字列などに変換する方法はわかりません。参照の値は圧縮中に変更されます(これまで見てきました)が、以前の値Aはすべて値Bに変更されます。安全なコードが懸念されるため、それはまだ一意のIDです。

関係するオブジェクトが制御下にある場合は、 weak reference (ガベージコレクションを回避するため)を使用して、選択したID(GUID、整数など)への参照からマッピングを作成できます。ただし、ある程度のオーバーヘッドと複雑さが追加されます。

40
Jon Skeet

.NET 4以降のみ

皆さん、良いニュースです!

このジョブに最適なツールは.NET 4に組み込まれており、 ConditionalWeakTable<TKey, TValue> 。このクラス:

  • 辞書のように管理オブジェクトインスタンスに任意のデータを関連付けるために使用できます(ただし、isは辞書ではありません)
  • メモリアドレスに依存しないため、ヒープを圧縮するGCの影響を受けません
  • オブジェクトがテーブルにキーとして入力されたという理由だけでオブジェクトを存続させないため、プロセス内のすべてのオブジェクトを永久に存続させることなく使用できます。
  • 参照の等価性を使用してオブジェクトの同一性を判断します。移動、クラス作成者はこの動作を変更できないため、あらゆるタイプのオブジェクトで一貫して使用できます
  • その場で移入できるため、オブジェクトコンストラクター内にコードを挿入する必要はありません。
68
Jon

ObjectIDGenerator クラスをチェックアウトしましたか?これはあなたがやろうとしていること、そしてマーク・グラヴェルが説明していることを行います。

ObjectIDGeneratorは、以前に識別されたオブジェクトを追跡します。オブジェクトのIDを要求すると、ObjectIDGeneratorは既存のIDを返すか、新しいIDを生成して記憶するかを認識します。

IDは、ObjectIDGeneratorインスタンスの存続期間を通じて一意です。一般に、ObjectIDGeneratorの寿命は、それを作成したフォーマッターが存在する限り続きます。オブジェクトIDは、特定のシリアル化されたストリーム内でのみ意味を持ち、シリアル化されたオブジェクトグラフ内で他のオブジェクトへの参照を持つオブジェクトを追跡するために使用されます。

ハッシュテーブルを使用して、ObjectIDGeneratorはどのIDがどのオブジェクトに割り当てられているかを保持します。各オブジェクトを一意に識別するオブジェクト参照は、ランタイムガベージコレクションヒープ内のアドレスです。オブジェクト参照値は、シリアル化中に変更される可能性がありますが、テーブルは自動的に更新されるため、情報は正確です。

オブジェクトIDは64ビットの数字です。割り当ては1から始まるため、ゼロは有効なオブジェクトIDにはなりません。フォーマッタは、値がnull参照(Visual BasicではNothing)であるオブジェクト参照を表すためにゼロ値を選択できます。

39
sisve

RuntimeHelpers.GetHashCode()が役立つ場合があります( [〜#〜] msdn [〜#〜] )。

34
Anton Gogolev

すぐに自分のものを開発できます。例えば:

   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として使用するものを選択するか、アクセスを高速化するために単に整数を選択できます。

7
majkinetor

この方法はどうですか:

最初のオブジェクトのフィールドに新しい値を設定します。 2番目のオブジェクトの同じフィールドに同じ値がある場合、それはおそらく同じインスタンスです。それ以外の場合は、別として終了します。

次に、最初のオブジェクトのフィールドを別の新しい値に設定します。 2番目のオブジェクトの同じフィールドが異なる値に変更された場合、それは間違いなく同じインスタンスです。

終了時に最初のオブジェクトのフィールドを元の値に戻すことを忘れないでください。

問題?

6
Dawg

Visual Studioで一意のオブジェクト識別子を作成することができます。ウォッチウィンドウでオブジェクト変数を右クリックし、コンテキストメニューからMake Object IDを選択します。

残念ながら、これは手動の手順であり、識別子にコード経由でアクセスできるとは思わない。

4
Thomas Bratt

インスタンス内または外部のいずれかで、手動でそのような識別子を割り当てる必要があります。

データベースに関連するレコードの場合、主キーが役立つ場合があります(ただし、重複する可能性があります)。または、Guidを使用するか、独自のカウンターを保持し、Interlocked.Incrementを使用して割り当てます(オーバーフローしないように十分大きくします)。

3
Marc Gravell

私はこれが回答されたことを知っていますが、少なくともあなたが使用できることに注意することは有用です:

http://msdn.Microsoft.com/en-us/library/system.object.referenceequals.aspx

これは「一意のID」を直接提供しませんが、WeakReferences(およびハッシュセット?)と組み合わせることで、さまざまなインスタンスを簡単に追跡できます。

2
Andrew Theken

特定の用途向けに独自のコードでモジュールを記述している場合、 majkinetorのメソッド[〜#〜] might [〜#〜]が機能しました。しかし、いくつかの問題があります。

最初に、公式ドキュメントは[〜#〜] not [〜#〜]を保証しますGetHashCode()一意の識別子を返します(Object.GetHashCodeメソッド()を参照):

等しいハッシュコードがオブジェクトの同等性を意味すると想定しないでください。

Second、ほとんどの場合GetHashCode()が機能するようにオブジェクトが非常に少ないと仮定します。このメソッドは一部のタイプでオーバーライドできます。
たとえば、クラスCを使用し、GetHashCode()をオーバーライドして常に0を返します。その後、Cのすべてのオブジェクトは同じハッシュコードを取得します。残念ながら、DictionaryHashTable、およびその他の連想コンテナはこのメソッドを使用します。

ハッシュコードは、Dictionary <TKey、TValue>クラス、Hashtableクラス、DictionaryBaseクラスから派生した型など、ハッシュベースのコレクション内のオブジェクトを挿入および識別するために使用される数値です。 GetHashCodeメソッドは、オブジェクトの等価性をすばやくチェックする必要があるアルゴリズムにこのハッシュコードを提供します。

したがって、このアプローチには大きな制限があります。

そして、さらに、汎用ライブラリを構築したい場合はどうしますか?使用されているクラスのソースコードを変更できないだけでなく、その動作も予測できません。

Jon および Simon が回答を投稿してくれたことに感謝します。コード例とパフォーマンスに関する提案を以下に投稿します。

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は例外をスローして、forループで10,000,000個のオブジェクト(上記のコードの10倍)を作成するときにオブジェクトが多すぎることを訴えます。

また、ベンチマークの結果は、ConditionalWeakTable実装がObjectIDGenerator実装よりも1.8倍高速であるということです。

0
Mr. Ree