Я знаю, что Java реализует параметрический полиморфизм (Generics) с стиранием. Я понимаю, что такое стирание.
Я знаю, что C # реализует параметрический полиморфизм с овеществлением. Я знаю, что может заставить тебя написать
public void dosomething(List<String> input) {}
public void dosomething(List<Int> input) {}
или что вы можете знать во время выполнения, что является параметром типа некоторого параметризованного типа, но я не понимаю, что это такое .
- Что такое ограниченный тип?
- Что такое ценность?
- Что происходит, когда тип / значение определяется?
c#
generics
reification
Мартейн
источник
источник
if
повторение - это процесс преобразованияswitch
конструкции обратно вif
/else
, когда она ранее была преобразована изif
/else
вswitch
...Ответы:
Реификация - это процесс взятия абстрактной вещи и создания конкретной вещи.
Термин reification в C # generics относится к процессу, с помощью которого определение универсального типа и один или несколько аргументов универсального типа (абстрактная вещь) объединяются, чтобы создать новый универсальный тип (конкретную вещь).
Выражаясь иначе, это процесс принятия определения
List<T>
иint
и производства конкретногоList<int>
типа.Чтобы понять это далее, сравните следующие подходы:
В Java-обобщениях определение универсального типа преобразуется по существу в один конкретный универсальный тип, общий для всех допустимых комбинаций аргументов типа. Таким образом, несколько типов (уровень исходного кода) отображаются на один тип (двоичный уровень), но в результате информация об аргументах типа экземпляра в этом экземпляре отбрасывается (стирание типа) .
В обобщениях C # определение универсального типа сохраняется в памяти во время выполнения. Всякий раз, когда требуется новый конкретный тип, среда выполнения объединяет определение универсального типа и аргументы типа и создает новый тип (reification). Таким образом, мы получаем новый тип для каждой комбинации аргументов типа во время выполнения .
System.Type
класса (даже если конкретная комбинация аргументов универсального типа, которую вы создаете, не выполнялась) не появляются в вашем исходном коде напрямую).В шаблонах C ++ определение шаблона сохраняется в памяти во время компиляции. Всякий раз, когда в исходном коде требуется новое создание экземпляра типа шаблона, компилятор объединяет определение шаблона и аргументы шаблона и создает новый тип. Таким образом, мы получаем уникальный тип для каждой комбинации аргументов шаблона во время компиляции .
источник
Реификация обычно означает (вне компьютерной науки) «сделать что-то реальное».
В программировании что-то улучшается, если мы можем получить доступ к информации о нем на самом языке.
Для двух совершенно не связанных с дженериками примеров того, что 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
есть. Мы можем сравнить этот тип, получить его имя и узнать о нем:Следствием этого является то, что мы можем «говорить» о типах параметров универсального метода (или метода универсального класса) внутри самого метода:
Как правило, делать это слишком "вонючим", но у него много полезных случаев. Например, посмотрите на:
Это не делает много сравнений между типом
TSource
и различными типами для различных поведений (как правило, признак того, что вы вообще не должны были использовать дженерики), но это разделяет путь кода для типов, которые могут бытьnull
(должны возвращаться,null
если элемент не найден, и он не должен сравнивать, чтобы найти минимум, если один из сравниваемых элементов естьnull
), и путь к коду для типов, которые не могут бытьnull
(должен выдавать, если элемент не найден, и не должен беспокоиться о возможностиnull
элементов ).Поскольку
TSource
в методе «реально», это сравнение может быть выполнено либо во время выполнения, либо во время джиттинга (как правило, во время джиттинга, безусловно, вышеописанный случай будет делать это во время джиттинга и не будет производить машинный код для неиспользованного пути), и у нас есть Отдельная «реальная» версия метода для каждого случая. (Хотя в качестве оптимизации машинный код используется совместно для разных методов для разных параметров типа ссылочного типа, потому что это может не повлиять на это, и, следовательно, мы можем уменьшить количество совмещенного машинного кода).(Не принято говорить о повторении обобщенных типов в C #, если только вы не имеете дело с Java, потому что в C # мы просто принимаем это преобразование как должное; все типы повторяются. В Java неуниверсальные типы называются reified, потому что это это различие между ними и родовыми типами).
источник
Min
что выше, полезно? В противном случае очень трудно выполнить задокументированное поведение.Enumerable.Min<TSource>
отличается тем, что оно не генерирует не-ссылочные типы в пустой коллекции, но возвращает значение по умолчанию). (TSource), и задокументировано только как «Возвращает минимальное значение в общей последовательности». Я бы сказал, что оба должны выдавать пустую коллекцию или что нулевой элемент должен быть передан в качестве базовой линии, а компаратор / функция сравнения всегда должна быть передана)Как уже заметил Даффимо , «овеществление» не является ключевым отличием.
В Java дженерики в основном существуют для улучшения поддержки во время компиляции - это позволяет вам использовать строго типизированные, например, коллекции, в вашем коде и обеспечивать безопасность типов для вас. Однако это существует только во время компиляции - скомпилированный байт-код больше не имеет понятия обобщений; все универсальные типы преобразуются в «конкретные» типы (используя,
object
если универсальный тип не ограничен), добавляя преобразования типов и проверки типов по мере необходимости.В .NET дженерики являются неотъемлемой особенностью CLR. Когда вы компилируете универсальный тип, он остается универсальным в сгенерированном IL. Он не просто превращается в неуниверсальный код, как в Java.
Это несколько влияет на то, как генерики работают на практике. Например:
SomeType<?>
должна позволять вам передавать любую конкретную реализацию заданного универсального типа. C # не может этого сделать - каждый конкретный ( усовершенствованный ) универсальный тип имеет свой собственный тип.object
. Это может оказать влияние на производительность при использовании типов значений в таких обобщенных типах. В C #, когда вы используете тип значения в универсальном типе, он остается типом значения.Для примера давайте предположим, что у вас есть
List
универсальный тип с одним универсальным аргументом. В JavaList<String>
и вList<Int>
конечном итоге будет точно такого же типа во время выполнения - универсальные типы действительно существуют только для кода времени компиляции. Все вызовы, например,GetValue
будут преобразованы в(String)GetValue
и(Int)GetValue
соответственно.В C #
List<string>
иList<int>
есть два разных типа. Они не являются взаимозаменяемыми, и их безопасность типов также обеспечивается во время выполнения. Независимо от того, что вы делаете, никогда неnew List<int>().Add("SomeString")
будет работать - лежащим в основе хранилищем на самом деле является некоторый целочисленный массив, в то время как в Java это обязательно массив. В C # не задействованы броски, бокс и т. Д.List<int>
object
Это также должно сделать очевидным, почему C # не может делать то же самое, что и Java
SomeType<?>
. В Java все универсальные типы, «производные от», вSomeType<?>
конечном итоге являются одинаковыми. В C # все различные специфическиеSomeType<T>
типы имеют свой отдельный тип. Удаляя проверки во время компиляции, можно пропуститьSomeType<Int>
вместоSomeType<String>
(и на самом деле все, чтоSomeType<?>
означает, это «игнорировать проверки во время компиляции для данного универсального типа»). В C # это невозможно, даже для производных типов (то есть, вы не можете сделать,List<object> list = (List<object>)new List<string>();
даже еслиstring
это производное отobject
).Обе реализации имеют свои плюсы и минусы. Было несколько раз, когда я хотел бы иметь возможность просто разрешить
SomeType<?>
в качестве аргумента в C # - но это просто не имеет смысла, как работают дженерики C #.источник
List<>
,Dictionary<,>
и так далее в C #, но разрыв между тем , что и в данном конкретном списке или словарем занимает совсем немного отражения на мост. Дисперсия в интерфейсах помогает в некоторых случаях, когда мы, возможно, когда-то хотели легко преодолеть этот разрыв, но не во всех.List<>
для создания нового определенного универсального типа - но это все равно означает создание определенного типа, который вы хотите. Но вы не можете использоватьList<>
в качестве аргумента, например. Но да, по крайней мере, это позволяет вам преодолеть разрыв с помощью отражения.T
может удовлетворять ограничению типа места хранения, -U
это когдаT
иU
имеют одинаковый тип, илиU
это тип, который может содержать ссылку на экземплярT
. Было бы невозможно иметь осмысленное место хранения типа,SomeType<?>
но теоретически было бы возможно иметь общее ограничение этого типа.Реификация - это объектно-ориентированная концепция моделирования.
Reify - это глагол, означающий «сделать что-то абстрактное реальным» .
Когда вы занимаетесь объектно-ориентированным программированием, обычно моделируют объекты реального мира как программные компоненты (например, «Окно», «Кнопка», «Человек», «Банк», «Автомобиль» и т. Д.).
Также принято преобразовывать абстрактные концепции в компоненты (например, WindowListener, Broker и т. Д.)
источник