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


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

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

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

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

  • что такое овеществленный тип?
  • что такое овеществленный ценность?
  • что происходит, когда тип/значение овеществленные?
4 147

4 ответа:

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

чтобы выразить это по-другому, это процесс принятия определения из List<T> и int и производить бетонную List<int> тип.

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

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

    1. в качестве побочного эффекта этого метода реализации единственными аргументами универсального типа, которые изначально разрешены, являются те типы, которые могут совместно использовать двоичный код своего конкретного типа; что означает те типы, места хранения которых имеют взаимозаменяемые представления; что означает ссылочные типы. использование типов значений в качестве аргументов универсального типа требует их упаковки (поместив их в простой ссылочный тип обертка.)
    2. код не дублируется для реализации дженериков таким образом.
    3. информация о типе, которая могла быть доступна во время выполнения (с помощью отражения), теряется. Это, в свою очередь, означает, что специализация родового типа (возможность использования специализированных исходный код для любой конкретной общей комбинации аргументов) очень ограничен.
    4. этот механизм не требует поддержки со стороны среды.
    5. есть несколько обходные пути для сохранения информации о типе что программа Java или язык на основе JVM может использовать.
  • в универсальных кодах C# определение универсального типа сохраняется в памяти во время выполнения. Всякий раз, когда требуется новый конкретный тип, среда выполнения объединяет определение универсального типа и аргументы типа и создает новый тип (овеществление). Таким образом мы получаем новый тип для каждой комбинации аргументов типа at время работы.

    1. этот метод реализации позволяет создавать любые типы комбинаций аргументов типа. Использование типов значений в качестве аргументов универсального типа не вызывает боксирования, так как эти типы получают свою собственную реализацию. (бокс все еще существует в C#, конечно - но это происходит в других сценариях, не этот.)
    2. дублирование кода может быть проблемой - но на практике это не так, потому что достаточно умный реализаций (это включает в себя Microsoft .NET и моно) может совместно использовать код для некоторых экземпляров.
    3. информация о типе сохраняется, что позволяет в определенной степени специализироваться, изучая аргументы типа с помощью отражения. Однако степень специализации ограничена, в результате чего составляется определение универсального типа до любое овеществление происходит (это делается с помощью компиляция определения с учетом ограничений на параметры типа таким образом, компилятор должен уметь "понимать" определение даже при отсутствии конкретных аргументов типа).
    4. этот метод реализации сильно зависит от поддержки времени выполнения и JIT-компиляции (именно поэтому вы часто слышите, что c# дженерики имеют некоторые ограничения на таких платформах, как iOS, где генерация динамического кода ограничена).
    5. в контексте c# дженерики, овеществление делается для вас среда выполнения. Однако, если вы хотите более наглядно понять разницу между определением универсального типа и конкретного универсального типа вы всегда можете выполнить овеществление самостоятельно, используя System.Type класс (даже если конкретная комбинация аргументов универсального типа, которую вы создаете, не появилась в исходном коде напрямую).
  • в шаблонах C++ определение шаблона сохраняется в памяти при компиляции время. Всякий раз, когда в исходном коде требуется создать новый экземпляр типа шаблона, компилятор объединяет определение шаблона и аргументы шаблона и создает новый тип. Таким образом мы получаем уникальный вид для каждой комбинации аргументов шаблона во время компиляции.

    1. этот метод реализации позволяет создавать любые типы комбинаций аргументов типа.
    2. это, как известно, дублировать двоичный код, но достаточно умный цепочка инструментов все еще может обнаружить это и поделиться кодом для некоторых экземпляров.
    3. само определение шаблона не "компилируется" -на самом деле компилируются только его конкретные экземпляры. Это накладывает меньше ограничений на компилятор и позволяет повысить степень шаблон специализации.
    4. поскольку экземпляры шаблонов выполняются во время компиляции, здесь также не требуется поддержка среды выполнения.
    5. этот процесс в последнее время упоминается как monomorphization, особенно в сообществе ржавчины. Слово используется в отличие от параметрический полиморфизм, что является названием концепции, из которой происходят дженерики.

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

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

для двух совершенно не связанных с дженериками примеров чего-то, что C# делает и не имеет овеществления, давайте возьмем методы и доступ к памяти.

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

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

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

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

это 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

следствием этого является то, что мы можем "говорить о" общий метод (или метод общего class) типы параметров внутри самого метода:

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 "реальный" в методе, это сравнение может быть сделано либо во время выполнения или время jitting (обычно jitting время, разумеется, дело было не так во время jitting и не производят машинный код для путь не принято) и у нас есть отдельный "реальный" вариант метода для каждого случая. (Хотя в качестве оптимизации машинный код используется совместно для разных методов для разных параметров типа ссылочного типа, поскольку он может не влиять на это, и поэтому мы можем уменьшить количество машинного кода jitted).

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

как duffymo уже отмечалось, "овеществление" не является ключевым различием.

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

в .NET дженерики являются неотъемлемой частью среды CLR. При компиляции универсального типа он остается универсальным в сгенерированном IL. Он не просто преобразуется в неродовой код, как в Java.

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

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

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

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

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

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

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

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

при объектно-ориентированном программировании обычно моделируются объекты реального мира в качестве программных компонентов (например, окно, кнопка, человек, Банк, автомобиль и т. д.)

также распространено овеществление абстрактных понятий в компоненты (например, WindowListener, Broker и т. д.)