Testowanie pod kątem równości odwołań (Identity) (Przewodnik programowania w języku C#)
Nie trzeba implementować żadnej logiki niestandardowej w celu obsługi porównań równości referencyjnej w typach. Ta funkcja jest udostępniana dla wszystkich typów przez metodę statyczną Object.ReferenceEquals.
W poniższym przykładzie pokazano, jak określić, czy dwie zmienne mają równości odwołania, co oznacza, że odwołują się do tego samego obiektu w pamięci.
W przykładzie pokazano również, dlaczego Object.ReferenceEquals zawsze zwraca false
dla typów wartości. Jest to spowodowane pudełkowaniem , które tworzy oddzielne wystąpienia obiektów dla każdego argumentu typu wartości. Ponadto nie należy używać ReferenceEquals do określania równości ciągów.
Przykład
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) = {0}",
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) = {0}",
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 = {0} tcB.Num: {1}", tcB.Name, 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) = {0}",
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) = {0}",
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 = \"{0}\" strB = \"{1}\"", strA, 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) = {0}",
Object.ReferenceEquals(stringC, strB));
// The string class overloads the == operator to perform an equality comparison.
Console.WriteLine("stringC == strB = {0}", 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
*/
Implementacja Equals
w uniwersalnej klasie bazowej System.Object również wykonuje sprawdzanie równości referencji, ale najlepiej nie używać tego, ponieważ jeśli klasa przypadkowo zastąpi metodę, wyniki mogą nie być zgodne z oczekiwaniami. To samo dotyczy operatorów ==
i !=
. Gdy operują na typach referencyjnych, domyślne zachowanie ==
i !=
to sprawdzanie równości referencji. Jednak klasy pochodne mogą przeciążać operatora, aby wykonać sprawdzanie równości wartości. Aby zminimalizować potencjalny błąd, najlepiej zawsze używać ReferenceEquals, gdy trzeba określić, czy dwa obiekty mają równość odwołań.
Stałe ciągi w tym samym zestawie są zawsze internowane przez środowisko uruchomieniowe. Oznacza to, że obsługiwane jest tylko jedno wystąpienie każdego unikatowego ciągu literału. Jednak środowisko uruchomieniowe nie gwarantuje, że ciągi utworzone w czasie wykonywania są internowane, ani nie gwarantuje, że dwa równe ciągi stałe w różnych zestawach są internowane.
Notatka
ReferenceEquals
zwraca false
dla typów wartości z powodu konwersji boksującej na, ponieważ każdy argument jest niezależnie opakowywany w osobny obiekt.