Object Identity / Equality

E’ noto che il metodo virtuale Equals ereditato da ogni oggetto dalla classe System.Object permette di definire un criterio di uguaglianza tra due oggetti non basato esclusivamente sulla reference (ovvero 2 oggetti sono uguali se puntano alla stessa istanza di un determinato oggetto).

E’ altresì noto che ridefinendo il metodo Equals siamo costretti a ridefinire il metodo GetHashCode per far si che in presenza di 2 oggetti uguali (Equals che ritorna true), il metodo GetHashCode ritorni lo stesso valore di hash per i 2 oggetti, questo perchè l’oggetto in questione potrebbe essere usato come chiave in una collezione di tipo HashTable o Dictionary<T,V>.

L’implementazione di GetHashCode fornita dalla classe System.Object fornisce un valore di hash distinto per ogni istanza di una data classe presente in un dato AppDomain, e tutto ciò è in linea con l’implementazione di Equals fornita da System.Object.

Ma se per un dato oggetto volessimo forzare l’uguaglianza per riferimento ?

Non basta usare ReferenceEquals(x, y) perchè, come detto, alcune classi come i dizionari utilizzano il metodo Equals per definire l’uguaglianza tra due oggetti (che di default si traduce in “identità” di un oggetto e non in “uguaglianza”).

Dovremmo ridefinire il medoto Equals in questo modo:

public override bool Equals(object obj) 
{ 
   return System.Object.ReferenceEquals(this, obj); 
}

ma in questo modo saremmo costratti ancha a ridefinire GetHashCode senza poter fornire l’implementazione standard di System.Object.

Questo problema si risolve utilizzando il medoto GetHashCode, ma quello fornito dalla classe System.Runtime.CompilersServices.RuntimeHelpers.

Questo metodo infatti, a dispetto del nome identito al metodo della classe System.Object, ritorna sempre un valore hash univoco per ogni istanza distinta di ogni oggetto in memoria, ovvero esattamente l’implementazione standard fornita da System.Object a prescindere se il metodo virtuale Object.GetHashCode sia o non sia ridefinito, ed adatta alla sola uguaglianza per riferimento.

Il tutto può essere inglobato in una classe Comparer apposita, in questo modo:

CLR – Forwarding type

Il CLR consente di spostare il codice di una classe da un assembly ad un altro senza  dover ricompilare il codice client che usa la classe in questione.

Questa caratteristica è nota come Type Forwarding.

Supponendo di avere la classe Class1 nell’assembly Ass1, se per questioni di refactory del codice sorgente la classe viene spostata nell’assembly Ass2, sembrerebbe a prima vista necessario ricompilare anche il codice che referenzia l’assembly Ass1 per aggiungere una reference all’assembly Ass2 e modificare la dichiarazione della classe Class1.

Tutto ciò non è necessario. Basta decorare l’assembly Ass1 (la nuova versione, quella priva della classe Class1) con l’attributo TypeForwardedToAttribute, in questo modo:

[assembly:TypeForwardedToAttribute(typeof(Class1))]

Così facendo, il codice client può continuare a referenziare l’assembly Ass1, e quindi non è necessaria una sua ricompilazione. Ogni richiesta che l’assembly riceverà per quella classe verrà reindirizzata al nuovo assembly che conterrà la classe stessa.

Ma tutto ciò ha una controindicazione a mio avviso non indifferente: l’assembly Ass1 che conteneva la classe che ora non contiene più dovrà per forza di cose referenziare l’assembly Ass2 che ora contiene il tipo.

Inoltre, e qui sarei curiosissimo di conoscere la motivazione, questo attributo non è utilizzabile in Visual Basic 2005 (e quindi con il .Net Framework 2.0). Infatti Visual Basic 2005 può solo “consumare un forwarded type” scritto in un altro linguaggio.