Reifikacja oznacza ogólnie (poza informatyką) „uczynienie czegoś prawdziwym”.
W programowaniu coś jest reifikowane, jeśli jesteśmy w stanie uzyskać dostęp do informacji o tym w samym języku.
W przypadku dwóch całkowicie niezwiązanych z typami ogólnymi przykładów czegoś, co C # robi i nie zostało zreifikowane, weźmy metody i dostęp do pamięci.
Języki OO na ogół mają metody (i wiele, które nie mają funkcji, które są podobne, ale nie są związane z klasą). W związku z tym możesz zdefiniować metodę w takim języku, wywołać ją, być może nadpisać i tak dalej. Nie wszystkie takie języki pozwalają na traktowanie samej metody jako danych do programu. C # (a tak naprawdę .NET zamiast C #) pozwala na użycie MethodInfo
obiektów reprezentujących metody, więc w C # metody są reifikowane. Metody w języku C # to „obiekty pierwszej klasy”.
Wszystkie języki praktyczne mają sposoby na dostęp do pamięci komputera. W języku niskiego poziomu, takim jak C, możemy zajmować się bezpośrednio mapowaniem między adresami numerycznymi używanymi przez komputer, więc takie podejście int* ptr = (int*) 0xA000000; *ptr = 42;
jest rozsądne (o ile mamy dobry powód, by podejrzewać, że uzyskanie dostępu do adresu pamięci 0xA000000
w ten sposób wygrywa '' coś wysadzić). W C # nie jest to rozsądne (możemy to wymusić w .NET, ale przy zarządzaniu pamięcią .NET przenosząc rzeczy, jest mało prawdopodobne, że będzie przydatne). C # nie ma zreifikowanych adresów pamięci.
Tak więc, ponieważ refied oznacza „ urzeczywistniony ”, „reifikowany typ” jest typem, o którym możemy „rozmawiać” w danym języku.
W przypadku leków generycznych oznacza to dwie rzeczy.
Jednym z nich jest to, że List<string>
jest to typ taki, jaki jest string
lub int
jest. Możemy porównać ten typ, poznać jego nazwę i zapytać o to:
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
Konsekwencją tego jest to, że możemy „mówić” o typach parametrów metody ogólnej (lub metody klasy ogólnej) w samej metodzie:
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
}
Z reguły robienie tego zbyt często jest „śmierdzące”, ale ma wiele przydatnych przypadków. Na przykład spójrz na:
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;
}
Nie powoduje to wielu porównań między typem TSource
i różnymi typami dla różnych zachowań (zazwyczaj znak, że w ogóle nie powinieneś używać typów ogólnych), ale dzieli się między ścieżką kodu dla typów, które mogą być null
(powinny zostać zwrócone, null
jeśli nie znaleziono elementu i nie wolno dokonywać porównań w celu znalezienia minimum, jeśli jeden z porównywanych elementów to null
) oraz ścieżkę kodu dla typów, których nie można null
(powinna zostać wyrzucona, jeśli nie znaleziono elementu, i nie trzeba się martwić o możliwość null
elementów ).
Ponieważ TSource
jest to „rzeczywiste” w metodzie, to porównanie można wykonać w czasie wykonywania lub w czasie jittingu (ogólnie w czasie jittingu, z pewnością powyższy przypadek robiłby to w czasie jittingu i nie generowałby kodu maszynowego dla ścieżki, która nie została wybrana) i mamy oddzielna „rzeczywista” wersja metody dla każdego przypadku. (Chociaż w ramach optymalizacji kod maszynowy jest współdzielony dla różnych metod dla różnych parametrów typu referencyjnego, ponieważ może to nie wpływać na to, a zatem możemy zmniejszyć ilość jitted kodu maszynowego).
(Nie jest powszechne mówienie o reifikacji typów ogólnych w C #, chyba że masz do czynienia również z Javą, ponieważ w C # po prostu przyjmujemy tę reifikację za pewnik; wszystkie typy są reifikowane. W Javie typy nieogólne są określane jako reifikowane, ponieważ to to rozróżnienie między nimi a typami rodzajowymi).