Procedura: testare l'uguaglianza di riferimenti (identità) (Guida per programmatori C#)
Non è necessario implementare alcuna logica personalizzata per supportare i confronti di uguaglianza dei riferimenti nei tipi. Questa funzionalità viene fornita per tutti i tipi dal metodo Object.ReferenceEquals statico.
Nell'esempio seguente viene illustrato come determinare se due variabili presentano uguaglianza di riferimenti, ovvero se fanno riferimento allo stesso oggetto in memoria.
Nell'esempio viene inoltre illustrato perché Object.ReferenceEquals restituisca sempre false per i tipi di valore e perché non sia consigliabile utilizzare ReferenceEquals per determinare l'uguaglianza tra stringhe.
Esempio
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 asignment: 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 copied on assignment. tsD and tsC have
// the same values but are not the same object.
TestStruct tsD = tsC;
Console.WriteLine("After asignment: 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 asignment: ReferenceEquals(tcA, tcB) = True
tcB.Name = TestClass 42 tcB.Num: 42
After asignment: ReferenceEquals(tsC, tsD) = False
ReferenceEquals(strA, strB) = True
strA = "Goodbye world!" strB = "Hello world!"
After strA changes, ReferenceEquals(strA, strB) = False
*/
L'implementazione di Equals nella classe di base universale Object esegue anche un controllo dell'uguaglianza dei riferimenti, ma è meglio non servirsene perché, se una classe esegue l'override del metodo, i risultati potrebbero non essere quelli previsti. La stessa considerazione vale anche per gli operatori == e !=. Quando agiscono sui tipi di riferimento, il comportamento predefinito di == e != prevede l'esecuzione di un controllo dell'uguaglianza dei riferimenti. Le classi derivate possono tuttavia eseguire l'overload dell'operatore per eseguire un controllo dell'uguaglianza dei valori. Per ridurre al minimo la possibilità di errori, è meglio utilizzare sempre ReferenceEquals quando è necessario determinare se due oggetti presentano uguaglianza di riferimenti.
Stringhe costanti all'interno dello stesso assembly vengono sempre archiviate dal runtime. In altre parole, viene mantenuta una sola istanza di ogni stringa letterale univoca. Il runtime tuttavia non garantisce che le stringhe create in fase di esecuzione vengano inserite né garantisce che vengano inserite due stringhe costanti uguali in assembly diversi.