Реификация обычно означает (вне компьютерной науки) «сделать что-то реальное».
В программировании что-то улучшается, если мы можем получить доступ к информации о нем на самом языке.
Для двух совершенно не связанных с дженериками примеров того, что C # делает, а что нет, давайте рассмотрим методы и доступ к памяти.
ОО-языки обычно имеют методы (и многие из них не имеют функций , которые похожи, но не связаны с классом). Таким образом, вы можете определить метод на таком языке, вызвать его, возможно переопределить и так далее. Не все такие языки позволяют вам иметь дело с самим методом как данными для программы. C # (и на самом деле .NET, а не C #) позволяет вам использовать MethodInfo
объекты, представляющие методы, поэтому в C # методы реализованы. Методы в C # являются «объектами первого класса».
Все практические языки имеют некоторые средства для доступа к памяти компьютера. На низкоуровневом языке, таком как C, мы можем напрямую иметь дело с отображением между числовыми адресами, используемыми компьютером, поэтому подобное int* ptr = (int*) 0xA000000; *ptr = 42;
разумно (если у нас есть веские основания подозревать, что доступ к адресу памяти 0xA000000
таким образом выиграл) взорвать что-нибудь). В C # это не разумно (мы можем просто заставить это сделать в .NET, но с управлением движением памяти .NET это вряд ли будет полезно). C # не имеет адресной памяти.
Таким образом, поскольку refied означает «сделано реальным», «reified type» - это тип, о котором мы можем «говорить» на данном языке.
В общем, это означает две вещи.
Во- первых, 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 неуниверсальные типы называются reified, потому что это это различие между ними и родовыми типами).