Реіфікація означає загалом (поза інформатикою) "зробити щось реальне".
У програмуванні щось вдосконалюється, якщо ми маємо доступ до інформації про нього самою мовою.
Для двох абсолютно не пов'язаних з дженериками прикладів того, що C # робить і не змінилось, візьмемо методи та доступ до пам'яті.
Мови ОО зазвичай мають методи (і багато хто не має функцій , схожих, хоча і не пов'язаних з класом). Як такий, ви можете визначити метод такою мовою, назвати його, можливо, замінити його тощо. Не всі подібні мови дозволяють вам реально мати сам метод як дані програми. C # (і насправді .NET, а не C #) дозволяє вам використовувати MethodInfo
об'єкти, що представляють методи, тому в C # методи переробляються. Методи в C # - це "об'єкти першого класу".
Усі практичні мови мають певні засоби для доступу до пам'яті комп'ютера. Мовою низького рівня, такою як C, ми можемо мати справу безпосередньо з відображенням між числовими адресами, якими користується комп’ютер, тому подібне подобається int* ptr = (int*) 0xA000000; *ptr = 42;
розумним (доки у нас є вагомі підстави підозрювати, що таким чином отримати доступ до адреси пам'яті 0xA000000
" т щось підірвати). У C # це не розумно (ми можемо просто змусити його в .NET, але при переміщенні управління пам'яттю .NET це не дуже корисно). C # не має змінених адрес пам'яті.
Отож, як рефіковані засоби "зробили реальним" "рефіфікований тип" - це тип, про який ми можемо "поговорити" у відповідній мові.
У генериці це означає дві речі.
По- перше, List<string>
це тип так само , як string
і int
є. Ми можемо порівняти цей тип, отримати його ім’я та дізнатись про нього:
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
Наслідком цього є те, що ми можемо "говорити про" типи параметрів загального методу (або методу загального класу) в межах самого методу:
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
}
Як правило, робити це занадто сильно "смердюче", але в ньому є багато корисних випадків. Наприклад, подивіться на:
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;
}
Це не робить багато порівнянь між типом TSource
та різними типами для різних типів поведінки (як правило, це знак, що ви взагалі не повинні використовувати дженерики), але він розділяє між кодовим шляхом на типи, які можуть бути null
(повинні повертатися, null
якщо жоден елемент не знайдений, і він не повинен проводити порівняння, щоб знайти мінімум, якщо один із елементів порівнюється null
) та шлях коду для типів, які не можуть бути null
(слід кидати, якщо жодного елемента не знайдено, і не потрібно турбуватися про можливість null
елементів ).
Оскільки TSource
в методі є "реальним", це порівняння може бути здійснене або під час виконання, або з часом джиттінга (як правило, час джиттінгу, звичайно, вищевикладений випадок зробив би це в час тренування і не створював машинного коду для не взятого шляху), і у нас є окрема "реальна" версія методу для кожного випадку. (Хоча як оптимізація, машинний код поділяється на різні методи для різних параметрів типу опорного типу, тому що це може бути, не впливаючи на це, і, отже, ми можемо зменшити кількість машинного коду, що пройшов).
(Це не прийнято говорити про матеріалізації універсальних типів в C # , якщо ви також мати справи з Java, тому що в C # ми просто прийняти це уречевлення належного, все тип упредметнені В Java, не загальні типи називається. Упредметнені бо - це відмінність між ними та родовими типами).