Reificazione significa generalmente (al di fuori dell'informatica) "rendere qualcosa reale".
Nella programmazione, qualcosa viene reificato se siamo in grado di accedere alle informazioni al riguardo nella lingua stessa.
Per due esempi completamente non generici di qualcosa che C # fa e non ha reificato, prendiamo i metodi e l'accesso alla memoria.
Le lingue OO generalmente hanno metodi (e molti che non hanno funzioni simili ma non legate a una classe). Come tale puoi definire un metodo in una tale lingua, chiamarlo, forse sovrascriverlo e così via. Non tutte queste lingue ti consentono di gestire il metodo stesso come dati di un programma. C # (e in realtà .NET anziché C #) ti consente di utilizzare MethodInfo
oggetti che rappresentano i metodi, quindi in C # i metodi sono reificati. I metodi in C # sono "oggetti di prima classe".
Tutte le lingue pratiche hanno alcuni mezzi per accedere alla memoria di un computer. In un linguaggio di basso livello come C possiamo occuparci direttamente della mappatura tra gli indirizzi numerici utilizzati dal computer, quindi cose del genere int* ptr = (int*) 0xA000000; *ptr = 42;
sono ragionevoli (fintanto che abbiamo una buona ragione per sospettare che l'accesso alla memoria 0xA000000
in questo modo abbia vinto ' far esplodere qualcosa). In C # questo non è ragionevole (possiamo semplicemente forzarlo in .NET, ma con la gestione della memoria .NET che sposta le cose in giro non è molto probabile che sia utile). C # non ha indirizzi di memoria reificati.
Quindi, poiché refied significa "reso reale", un "tipo reificato" è un tipo di cui possiamo "parlare" nella lingua in questione.
In generici questo significa due cose.
Uno è che List<string>
è un tipo come string
o lo int
sono. Possiamo confrontare quel tipo, ottenere il suo nome e chiederci:
Console.WriteLine(typeof(List<string>).FullName); // System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
Console.WriteLine(typeof(List<string>) == (42).GetType()); // False
Console.WriteLine(typeof(List<string>) == Enumerable.Range(0, 1).Select(i => i.ToString()).ToList().GetType()); // True
Console.WriteLine(typeof(List<string>).GenericTypeArguments[0] == typeof(string)); // True
Una conseguenza di ciò è che possiamo "parlare" dei tipi di parametri di un metodo generico (o metodo di una classe generica) all'interno del metodo stesso:
public static void DescribeType<T>(T element)
{
Console.WriteLine(typeof(T).FullName);
}
public static void Main()
{
DescribeType(42); // System.Int32
DescribeType(42L); // System.Int64
DescribeType(DateTime.UtcNow); // System.DateTime
}
Di norma, farlo troppo è "puzzolente", ma ha molti casi utili. Ad esempio, guarda:
public static TSource Min<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw Error.ArgumentNull("source");
Comparer<TSource> comparer = Comparer<TSource>.Default;
TSource value = default(TSource);
if (value == null)
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
do
{
if (!e.MoveNext()) return value;
value = e.Current;
} while (value == null);
while (e.MoveNext())
{
TSource x = e.Current;
if (x != null && comparer.Compare(x, value) < 0) value = x;
}
}
}
else
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
if (!e.MoveNext()) throw Error.NoElements();
value = e.Current;
while (e.MoveNext())
{
TSource x = e.Current;
if (comparer.Compare(x, value) < 0) value = x;
}
}
}
return value;
}
Questo non fa molti confronti tra il tipo di TSource
e vari tipi per comportamenti diversi (generalmente un segno che non dovresti aver usato affatto generici) ma si divide tra un percorso di codice per tipi che possono essere null
(dovrebbe restituire null
se nessun elemento trovato, e non deve fare confronti per trovare il minimo se uno degli elementi a confronto è null
) e il percorso del codice per i tipi che non possono essere null
(dovrebbero essere lanciati se nessun elemento trovato e non devono preoccuparsi della possibilità di null
elementi ).
Poiché TSource
è "reale" all'interno del metodo, questo confronto può essere effettuato sia in fase di runtime sia in fase di jitting (generalmente tempo di jitting, sicuramente il caso sopra lo farebbe al momento del jitting e non produrrebbe codice macchina per il percorso non preso) e abbiamo un versione "reale" separata del metodo per ciascun caso. (Anche se come ottimizzazione, il codice macchina è condiviso per diversi metodi per diversi parametri del tipo di riferimento, perché può essere senza influire su questo, e quindi possiamo ridurre la quantità di codice macchina escluso).
(Non è comune parlare di reificazione di tipi generici in C # a meno che non si tratti anche di Java, perché in C # diamo per scontata questa reificazione; tutti i tipi sono reificati. In Java, i tipi non generici sono indicati come reificati perché è una distinzione tra loro e tipi generici).