次の方法で共有


参照等価性 (ID) をテストする方法 (C# プログラミング ガイド)

型の参照等価比較をサポートするためにカスタム ロジックを実装する必要はありません。 この機能は、静的 Object.ReferenceEquals メソッドによってすべての型に対して提供されます。

次の例は、2 つの変数 参照等価を持っているかどうかを判断する方法を示しています。これは、メモリ内の同じオブジェクトを参照していることを意味します。

この例では、Object.ReferenceEquals が常に値型の false を返す理由も示しています。 これは、値型引数ごとに個別のオブジェクト インスタンスを作成する ボックス化が原因です。 さらに、文字列の等価性を判断するために ReferenceEquals を使用しないでください。

using System.Text;

namespace TestReferenceEquality
{
    struct TestStruct
    {
        public int Num { get; private set; }
        public string Name { get; private set; }

        public TestStruct(int i, string s) : this()
        {
            Num = i;
            Name = s;
        }
    }

    class TestClass
    {
        public int Num { get; set; }
        public string? Name { get; set; }
    }

    class Program
    {
        static void Main()
        {
            // Demonstrate reference equality with reference types.
            #region ReferenceTypes

            // Create two reference type instances that have identical values.
            TestClass tcA = new TestClass() { Num = 1, Name = "New TestClass" };
            TestClass tcB = new TestClass() { Num = 1, Name = "New TestClass" };

            Console.WriteLine($"ReferenceEquals(tcA, tcB) = {Object.ReferenceEquals(tcA, tcB)}"); // false

            // After assignment, tcB and tcA refer to the same object.
            // They now have reference equality.
            tcB = tcA;
            Console.WriteLine($"After assignment: ReferenceEquals(tcA, tcB) = {Object.ReferenceEquals(tcA, tcB)}"); // true

            // Changes made to tcA are reflected in tcB. Therefore, objects
            // that have reference equality also have value equality.
            tcA.Num = 42;
            tcA.Name = "TestClass 42";
            Console.WriteLine($"tcB.Name = {tcB.Name} tcB.Num: {tcB.Num}");
            #endregion

            // Demonstrate that two value type instances never have reference equality.
            #region ValueTypes

            TestStruct tsC = new TestStruct( 1, "TestStruct 1");

            // Value types are boxed into separate objects when passed to ReferenceEquals.
            // Even if the same variable is used twice, boxing ensures they are different instances.
            TestStruct tsD = tsC;
            Console.WriteLine($"After assignment: ReferenceEquals(tsC, tsD) = {Object.ReferenceEquals(tsC, tsD)}"); // false
            #endregion

            #region stringRefEquality
            // Constant strings within the same assembly are always interned by the runtime.
            // This means they are stored in the same location in memory. Therefore,
            // the two strings have reference equality although no assignment takes place.
            string strA = "Hello world!";
            string strB = "Hello world!";
            Console.WriteLine($"ReferenceEquals(strA, strB) = {Object.ReferenceEquals(strA, strB)}"); // true

            // After a new string is assigned to strA, strA and strB
            // are no longer interned and no longer have reference equality.
            strA = "Goodbye world!";
            Console.WriteLine($"strA = '{strA}' strB = '{strB}'");

            Console.WriteLine("After strA changes, ReferenceEquals(strA, strB) = {0}",
                            Object.ReferenceEquals(strA, strB)); // false

            // A string that is created at runtime cannot be interned.
            StringBuilder sb = new StringBuilder("Hello world!");
            string stringC = sb.ToString();
            // False:
            Console.WriteLine($"ReferenceEquals(stringC, strB) = {Object.ReferenceEquals(stringC, strB)}");

            // The string class overloads the == operator to perform an equality comparison.
            Console.WriteLine($"stringC == strB = {stringC == strB}"); // true

            #endregion

            // Keep the console open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}

/* Output:
    ReferenceEquals(tcA, tcB) = False
    After assignment: ReferenceEquals(tcA, tcB) = True
    tcB.Name = TestClass 42 tcB.Num: 42
    After assignment: ReferenceEquals(tsC, tsD) = False
    ReferenceEquals(strA, strB) = True
    strA = "Goodbye world!" strB = "Hello world!"
    After strA changes, ReferenceEquals(strA, strB) = False
    ReferenceEquals(stringC, strB) = False
    stringC == strB = True
*/

System.Object ユニバーサル 基底クラスでの Equals の実装でも参照等価性チェックを実行しますが、クラスがメソッドをオーバーライドする場合、結果が期待した結果にならない可能性があるため、これを使用しないことをお勧めします。 == 演算子と != 演算子についても同様です。 参照型で動作している場合、==!= の既定の動作は、参照の等価性チェックを実行することです。 ただし、派生クラスでは、演算子をオーバーロードして値の等価チェックを実行できます。 エラーの可能性を最小限に抑えるには、2 つのオブジェクトの参照が等しいかどうかを判断する必要がある場合は、常に ReferenceEquals を使用することをお勧めします。

同じアセンブリ内の定数文字列は、常にランタイムによって強制されます。 つまり、一意のリテラル文字列ごとに 1 つのインスタンスのみが保持されます。 ただし、ランタイムは、実行時に作成された文字列がインターンされることを保証するものではなく、異なるアセンブリ内の 2 つの等しい定数文字列がインターンされることを保証しません。

手記

ReferenceEquals は、各引数が別のオブジェクトに個別にボックス化されるため、 ボックス化による値型のfalseを返します。

関連項目