Come testare l'uguaglianza dei riferimenti (Identità) (Guida per programmatori C#)
Non necessiti di implementare alcuna logica personalizzata per supportare i confronti di uguaglianza dei riferimenti nei tuoi tipi. Questa funzionalità viene fornita per tutti i tipi dal metodo Object.ReferenceEquals statico.
Nell'esempio seguente viene illustrato come determinare se due variabili hanno 'uguaglianza dei riferimenti, il che significa che fanno riferimento allo stesso oggetto in memoria.
L'esempio mostra anche perché Object.ReferenceEquals restituisce sempre false
per i tipi valore. Ciò è dovuto alla boxing, che crea istanze di oggetti separate per ogni tipo di valore. Inoltre, non è consigliabile usare ReferenceEquals per determinare l'uguaglianza di stringhe.
Esempio
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
*/
L'implementazione di Equals
nella classe di base universale System.Object esegue anche un controllo di uguaglianza dei riferimenti, ma è consigliabile non usarlo perché, se una classe esegue l'override del metodo, i risultati potrebbero non essere quello previsto. Lo stesso vale per gli operatori ==
e !=
. Quando operano sui tipi riferimento, il comportamento predefinito di ==
e !=
consiste nell'eseguire un controllo di uguaglianza dei riferimenti. Tuttavia, le classi derivate possono sovraccaricare l'operatore per controllare l'uguaglianza dei valori. Per ridurre al minimo il rischio di errore, è consigliabile usare sempre ReferenceEquals quando è necessario determinare se due oggetti hanno l'uguaglianza dei riferimenti.
Le stringhe costanti all'interno dello stesso assembly vengono sempre internate dal runtime. Ovvero, viene mantenuta una sola istanza di ogni stringa letterale univoca. Tuttavia, il runtime non garantisce che le stringhe create in fase di esecuzione vengano internalizzate, né garantisce che due stringhe costanti uguali presenti in assembly diversi vengano internalizzate.
Nota
ReferenceEquals
restituisce false
per i tipi valore per via di boxing, poiché ogni argomento viene sottoposto a un'operazione di boxing separatamente in un oggetto separato.