Что такое овеществление?

Я знаю, что Java реализует параметрический полиморфизм (Generics) с стиранием. Я понимаю, что такое стирание.

Я знаю, что С# реализует параметрический полиморфизм с reification. Я знаю, что вы можете написать

public void dosomething(List<String> input) {}
public void dosomething(List<Int> input) {}

или что во время выполнения вы можете знать, что такое параметр типа некоторого параметризованного типа, но я не понимаю, что это такое.

  • Что такое тип reified?
  • Что такое значение reified?
  • Что происходит, когда тип/значение подтверждается?

Ответ 1

Рейтификация - это процесс принятия абстрактной вещи и создания конкретной вещи.

Термин reification в С# generics относится к процессу, в котором определение общего типа и один или несколько аргументов универсального типа (абстрактная вещь) объединяются для создания новый общий тип (конкретная вещь).

Говоря иначе, это процесс определения List<T> и int и создания конкретного типа List<int>.

Чтобы понять это, сравните следующие подходы:

  • В Java-генераторах определение общего типа преобразуется, по существу, в один конкретный общий тип, разделяемый всеми комбинациями аргументов разрешенного типа. Таким образом, несколько типов (тип исходного кода) сопоставляются с одним (двоичным уровнем) типа, но в результате информация о аргументах типа экземпляра отбрасывается в этот экземпляр (стирание стилей).

    • В качестве побочного эффекта этого метода реализации единственными аргументами общего типа, которые являются естественными, являются те типы, которые могут совместно использовать двоичный код своего конкретного типа; что означает те типы, чьи места хранения имеют взаимозаменяемые представления; что означает ссылочные типы. Использование типов значений в качестве аргументов универсального типа требует их бокса (размещение их в простой оболочке ссылочного типа).
    • Никакой код не дублируется для реализации дженериков таким образом.
    • Информация о типе, которая могла быть доступна во время выполнения (с использованием отражения), теряется. Это, в свою очередь, означает, что специализация типичного типа (возможность использования специализированного исходного кода для какой-либо конкретной комбинации общих аргументов) очень ограничена.
    • Этот механизм не требует поддержки среды выполнения.
    • Существует несколько обходных путей для сохранения информации о типе, которые могут использовать Java-программа или язык JVM.
  • В С# generics определение универсального типа сохраняется в памяти во время выполнения. Всякий раз, когда требуется новый конкретный тип, среда выполнения объединяет определение общего типа и аргументы типа и создает новый тип (reification). Таким образом, мы получаем новый тип для каждой комбинации аргументов типа во время выполнения.

  • В шаблонах С++ определение шаблона сохраняется в памяти во время компиляции. Всякий раз, когда требуется новый экземпляр типа шаблона в исходном коде, компилятор объединяет определение шаблона и аргументы шаблона и создает новый тип. Таким образом, мы получаем уникальный тип для каждой комбинации аргументов шаблона во время компиляции.

    • Этот метод реализации позволяет создать экземпляр любой комбинации типов.
    • Известно, что он дублирует двоичный код, но достаточно интеллектуальная цепочка инструментов все еще может обнаружить это и совместно использовать код для некоторых экземпляров.
    • Само определение шаблона не "скомпилировано" - только конкретные конкретные экземпляры фактически скомпилированы. Это накладывает меньше ограничений на компилятор и позволяет в большей степени специализацию шаблона.
    • Так как экземпляры шаблонов выполняются во время компиляции, здесь не требуется поддержка времени выполнения.
    • Этот процесс в последнее время называется мономорфизацией, особенно в сообществе Rust. Слово используется в отличие от параметрического полиморфизма, который является названием понятия, из которого генерируются генерики.

Ответ 2

Reification означает вообще (вне компьютерной науки) "сделать что-то реальное".

В программировании что-то подтверждается, если мы можем получить доступ к информации об этом на самом языке.

Для двух полностью не связанных с генериком примеров того, что С# делает и не имеет reified, давайте возьмем методы и доступ к памяти.

В языках OO обычно есть методы (и многие из них не имеют функций, сходных, хотя и не связанных с классом). Таким образом, вы можете определить метод на таком языке, назовите его, возможно, переопределите его и так далее. Не все такие языки позволяют вам фактически обрабатывать сам метод как данные для программы. С# (и действительно,.NET, а не С#) позволяет вам использовать объекты MethodInfo, представляющие методы, поэтому в методах С# подтверждается. Методы в С# являются "объектами первого класса".

Все практические языки имеют некоторые средства доступа к памяти компьютера. На низкоуровневом языке, таком как C, мы можем напрямую обращаться с отображением между числовыми адресами, используемыми компьютером, поэтому подходящие int* ptr = (int*) 0xA000000; *ptr = 42; являются разумными (если у нас есть веские основания подозревать, что доступ к адресу памяти 0xA000000 таким образом не взорвется что-то). В С# это не разумно (мы можем просто заставить его в .NET, но с управлением памятью .NET, перемещение вокруг него вряд ли будет полезным). С# не имеет адресов reified памяти.

Итак, поскольку refied означает "сделать реальным", "тип reified" - это тип, который мы можем "говорить" на соответствующем языке.

В дженериках это означает две вещи.

Один из них заключается в том, что 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 (должны throw, если элемент не найден, и не нужно беспокоиться о возможности элементов null).

Так как TSource является "реальным" внутри метода, это сравнение может быть выполнено либо во время выполнения, либо во время джиттинга (обычно время джиттинга, конечно, вышеупомянутый случай будет делать это во время стрельбы и не выводить машинный код для пути не), и у нас есть отдельная "реальная" версия метода для каждого случая. (Хотя, как оптимизация, машинный код используется для разных методов для разных типов типа ссылочного типа, поскольку он может не влиять на это, и, следовательно, мы можем уменьшить количество машинного кода jitted).

(Не так просто говорить о переопределении родовых типов в С#, если вы не имеете дело с Java, потому что в С# мы просто принимаем это подтверждение как данность, все типы аутентифицируются. В Java не общие типы называются потому что это различие между ними и родовыми типами).

Ответ 3

Как duffymo уже отметил, "reification" не является ключевым отличием.

В Java генерические средства в основном используются для улучшения поддержки времени компиляции - это позволяет использовать строго типизированные, например, коллекций в вашем коде, и для вас безопасны типы безопасности. Тем не менее, это существует только во время компиляции - скомпилированный байт-код больше не имеет понятия дженериков; все типовые типы преобразуются в "конкретные" типы (используя object, если общий тип неограничен), добавляя преобразования типов и проверки типов по мере необходимости.

В .NET обобщения являются неотъемлемой функцией CLR. Когда вы компилируете общий тип, он остается общим в сгенерированном ИЛ. Он не просто преобразован в не-общий код, как в Java.

Это несколько влияет на то, как дженерики работают на практике. Например:

  • Java имеет SomeType<?>, чтобы позволить вам передать какую-либо конкретную реализацию данного генерического типа. С# не может этого сделать - каждый конкретный (reified) общий тип является его собственным типом.
  • Неограниченные общие типы в Java означают, что их значение сохраняется как object. Это может повлиять на производительность при использовании типов значений в таких дженериках. В С#, когда вы используете тип значения в родовом типе, он сохраняет тип значения.

Чтобы дать образец, предположим, что у вас есть общий тип List с одним общим аргументом. В Java List<String> и List<Int> в то же время будут иметь тот же самый тип во время выполнения - типичные типы действительно существуют только для кода времени компиляции. Все вызовы, например. GetValue будет преобразовано в (String)GetValue и (Int)GetValue соответственно.

В С#, List<String> и List<Int> - два разных типа. Они не взаимозаменяемы, и их безопасность типов также применяется во время выполнения. Независимо от того, что вы делаете, new List<int>().Add("SomeString") никогда не будет работать - базовое хранилище в List<Int> действительно представляет собой некоторый целочисленный массив, а в Java - массив object. В С# не задействованы приемы, нет бокса и т.д.

Это также должно сделать очевидным, почему С# не может делать то же самое, что Java с SomeType<?>. В Java все родовые типы "производные от" SomeType<?> заканчиваются тем же типом. В С# все различные конкретные SomeType<T> являются их собственным отдельным типом. Удаляя проверки времени компиляции, можно передать SomeType<Int> вместо SomeType<String> (и действительно, все, что означает SomeType<?>, означает "игнорировать проверки времени компиляции для данного генерического типа" ). В С# это невозможно, даже для производных типов (т.е. Вы не можете сделать List<object> list = (List<object>)new List<string>();, хотя string получен из object).

Обе реализации имеют свои плюсы и минусы. Было несколько раз, когда я бы любил, чтобы иметь возможность разрешать SomeType<?> как аргумент в С#, но просто не имеет смысла, как работают дроиды С#.

Ответ 4

Обоснование представляет собой концепцию объектно-ориентированного моделирования.

Reify - это глагол, который означает "сделать что-то абстрактное реальное" .

Когда вы выполняете объектно-ориентированное программирование, оно типично для моделирования объектов реального мира как программных компонентов (например, Window, Button, Person, Bank, Vehicle и т.д.).

Он также распространяет также на абстрактные понятия на компоненты (например, WindowListener, Broker и т.д.)