Атомарность ссылок на переменные
К атомарным действиям относятся чтение и запись следующих типов данных: bool, char, byte, sbyte, short, ushort, uint, int, float и ссылочных типов. В дополнение к этому атомарными действиями являются чтение и запись типов перечислений с базовым типом в приведенном выше списке. Чтение и запись других типов, включая long, ulong, double и decimal, а также пользовательских типов, не обязательно являются атомарными действиями. Если речь не идет о предназначенных для этой цели библиотечных функциях, гарантия атомарного чтения, изменения и записи, например в случае приращения или уменьшения, отсутствует.
6. Преобразования
С помощью преобразований выражения могут обрабатываться как выражения определенного типа. Преобразования позволяют обрабатывать выражения заданного типа как имеющие другой тип, а выражения без типа – как выражения определенного типа. Преобразования бывают явными и неявными, что определяет необходимость использования явного приведения. Например, преобразование из типа int к типу long является неявным, поэтому выражения типа int могут неявно обрабатываться как тип long. Противоположное преобразование — из типа long к типу int — является явным, поэтому в этом случае требуется явное приведение.
int a = 123;
long b = a; // implicit conversion from int to long
int c = (int) b; // explicit conversion from long to int
Некоторые преобразования определяются языком. В программе также можно определить собственные преобразования (§6.4).
6.1 Неявные преобразования
К неявным преобразованиям относятся следующие:
· Преобразования идентификатора
· Неявные числовые преобразования
· Неявные преобразования перечисляемых типов.
· Неявные преобразования обнуляемых типов.
· Преобразования литерала NULL.
· Неявные преобразования ссылочных типов
· Преобразования упаковки.
· Неявные преобразования динамических типов
· Неявные преобразования выражений констант
· Пользовательские неявные преобразования
· Преобразования анонимных функций
· Преобразования группы методов
Неявные преобразования могут происходить в разнообразных ситуациях, включая вызовы членов функций (§7.5.4), выражения приведения (§7.7.6) и присваивания (§7.17).
Предопределенные неявные выражения всегда выполняются успешно и никогда не порождают исключений. Корректно разработанные пользовательские преобразования также должны отвечать этим требованиям.
С точки зрения преобразования, типы object и dynamic считаются эквивалентными.
Однако динамические преобразования (§6.1.8 and §6.2.6) применяются только к выражениям типа dynamic (§4.7).
6.1.1 Преобразование идентификатора
Преобразование идентификатора обеспечивает приведение типа к такому же типу. Это преобразование используется только для того, чтобы задать возможность преобразования к конкретному типу сущности, уже имеющей этот тип.
Поскольку типы object и dynamic считаются эквивалентными, существует тождественное преобразование между типами object и dynamic, а также между сформированными типами, которые не изменяются при замене всех экземпляров типа dynamic на тип object.
6.1.2 Неявные числовые преобразования
Существуют следующие неявные преобразования числовых типов:
· Из sbyte в short, int, long, float, double или decimal.
· Из byte в short, ushort, int, uint, long, ulong, float, double или decimal.
· Из short в int, long, float, double или decimal.
· Из ushort в int, uint, long, ulong, float, double или decimal.
· Из int в long, float, double или decimal.
· Из типа uint в тип long, ulong, float, double или decimal.
· Из long в float, double или decimal.
· Из типа ulong в тип float, double или decimal.
· Из char в ushort, int, uint, long, ulong, float, double или decimal.
· Из типа float в тип double.
Преобразования из int, uint, long или ulong в float и из long или ulong в double могут привести к потере точности, но никогда не приведут к потере величины. Другие неявные преобразования числовых типов никогда не приводят к потере данных.
Из-за отсутствия неявных преобразований в тип char значения других целочисленных типов не преобразуются в тип char автоматически.
6.1.3 Неявные преобразования перечисляемых типов
Неявное преобразование перечисляемых типов позволяет преобразовать десятичный целочисленный литерал 0 в любой перечисляемый тип, а также в любой обнуляемый тип с базовым перечисляемым типом. В последнем случае выполняется преобразование к базовому перечисляемому типу и последующее свертывание результата (§4.1.10).
6.1.4 Неявные преобразования обнуляемых типов
Предопределенные неявные преобразования для необнуляемых типов значений могут также использоваться и с обнуляемыми формами таких типов. Для каждого предопределенного неявного преобразования идентификатора или числового типа, которое выполняет преобразование из необнуляемого типа значений S к аналогичному типу T, существуют следующие неявные преобразования обнуляемых типов:
· Неявное преобразование из типа S? в тип T?.
· Неявное преобразование из типа S в тип T?.
Вычисление неявного преобразования обнуляемого типа, основанного на преобразовании из S к T, осуществляется следующим образом:
· Если выполняется преобразование обнуляемых типов из S? в T?:
o Если исходное значение равно NULL (значение свойства HasValue равно false), результатом является значение NULL типа T?.
o В противном случае преобразование вычисляется посредством развертывания из S? в S, преобразования из S к T и последующего свертывания (§4.1.10) из T к T?.
· Если выполняется преобразование обнуляемых типов из S в T?, преобразование вычисляется как базовое преобразование из S в T с последующим свертыванием из типа T в тип T?.
6.1.5 Преобразования литерала NULL.
Существует неявное преобразование литерала null в любой обнуляемый тип. В результате преобразования получается значение NULL (§4.1.10) заданного обнуляемого типа.
6.1.6 Неявные преобразования ссылочных типов
Существуют следующие неявные преобразования ссылочных типов:
· Из любого ссылочного типа в типы object и dynamic.
· Из любого типа класса S к любому типу класса T (где S является производным от T).
· Из любого типа класса S к любому типу интерфейса T при условии, что S реализует T.
· Из любого типа интерфейса S к любому типу интерфейса T (где S является производным от T).
· Из типа массива S с элементами типа SE в тип массива T с элементами типа TE при условии выполнения следующих требований:
o S и T различаются только по типу элементов. Другими словами, S и T имеют одинаковое число измерений.
o SE и TE являются ссылочными типами.
o Существует неявное преобразование ссылочных типов из SE в TE.
· Из любого типа массива в тип System.Array и реализуемые этим типом интерфейсы.
· Из одномерного типа массива S[] к System.Collections.Generic.IList<T> и его базовым интерфейсам, если существует неявное преобразование идентификатора или ссылочного типа из S к T.
· Из любого типа делегата в тип System.Delegate и реализуемые этим типом интерфейсы.
· Из любого литерала NULL к любому ссылочному типу.
· Из любого ссылочного типа в ссылочный тип T, если для него предусмотрено неявное тождественное преобразование или преобразование ссылочных типов из ссылочного типа T0 и для типа T0 предусмотрено тождественное преобразование в тип T.
· Из любого ссылочного типа в тип интерфейса или делегата T, если для него предусмотрено неявное тождественное преобразование или преобразование ссылочных типов в тип интерфейса или делегата T0 и тип T0 является вариантно-преобразуемым (§13.1.3.2) в тип T.
· Неявные преобразования, включающие параметры типа, которые имеют ссылочный тип. Дополнительные сведения о неявных преобразованиях, включающих параметры типа, см. в §6.1.10.
Неявные преобразования ссылочных типов всегда выполняются успешно и не требуют проверки во время выполнения.
Неявные или явные преобразования ссылочных типов никогда не изменяют ссылочный идентификатор преобразуемого объекта. Другими словами, преобразование ссылочного типа может изменить тип ссылки, однако никогда не изменяет тип или значение объекта, на который указывает ссылка.
6.1.7 Преобразования упаковки
Преобразование упаковки обеспечивает неявное преобразование типа значений в ссылочный тип. Преобразование упаковки возможно из любого необнуляемого типа значения в типы object и dynamic, в тип System.ValueType, а также в любой тип интерфейса, реализуемый необнуляемым типом значения. Кроме того, перечисляемый тип может быть преобразован в тип System.Enum.
Преобразование упаковки из обнуляемого типа к ссылочному типу существует только в том случае, если существует преобразование упаковки из базового необнуляемого типа значений к ссылочному типу.
Для типа значения возможно преобразование упаковки в тип интерфейса I, если для него предусмотрено преобразование упаковки в тип интерфейса I0 и для типа I0 предусмотрено тождественное преобразование в тип I.
Для типа значения возможно преобразование упаковки в тип интерфейса I, если для него предусмотрено преобразование упаковки в тип интерфейса или делегата I0 и тип I0 является вариантно-преобразуемым (§13.1.3.2) в тип I.
Упаковка значения необнуляемого типа значений включает в себя выделение экземпляра объекта и копирование этого значения типа значений в указанный экземпляр. Структура может быть упакована в тип System.ValueType, поскольку он является базовым классом для всех структур (§11.3.2).
Упаковка значения обнуляемого типа осуществляется следующим образом:
· Если исходное значение равно NULL (значение свойства HasValue равно false), результатом является пустая ссылка целевого типа.
· В противном случае результатом является ссылка на упакованное значение типа T, полученное в результате развертывания и упаковки исходного значения.
Дополнительные сведения о преобразованиях упаковки см. в §4.3.1.
6.1.8 Неявные преобразования динамических типов
Существует неявное преобразование динамических типов из выражения типа dynamic в любой тип T. Преобразование является динамически привязанным (§7.2.2) — это означает, что во время выполнения будет выполняться поиск неявного преобразования типа выражения времени выполнения в тип T. Если преобразование не обнаружено, во время выполнения возникает исключение.
Обратите внимание, что это неявное преобразование противоречит совету, данному в §6.1, о том, что неявное преобразование не должно вызывать исключений. Однако по сути это не столько преобразование, сколько поискпреобразования, вызывающего исключение. Риск возникновения исключений во время выполнения является неотъемлемым при использовании динамической привязки. Если динамическая привязка преобразования не требуется, можно сначала преобразовать выражение в тип object, а затем в требуемый тип.
Следующий пример иллюстрирует неявное динамическое преобразование:
object o = “object”
dynamic d = “dynamic”;
string s1 = o; // Fails at compile-time – no conversion exists
string s2 = d; // Compiles and succeeds at run-time
int i = d; // Compiles but fails at run-time – no conversion exists
В присвоениях объектам s2 и i используются неявные динамические преобразования, в которых привязка операций откладывается до времени выполнения. Во время выполнения выполняется поиск неявных преобразований из типа объекта времени выполнения d — string — в целевой тип. Удается найти преобразование в тип string, но не в тип int.
6.1.9 Неявные преобразования выражений констант
Неявные преобразования выражений констант обеспечивают следующие преобразования:
· Преобразование константного выражения (§7.19) типа int в тип sbyte, byte, short, ushort, uint или ulong возможно, если значение константного выражения принадлежит диапазону целевого типа.
· Преобразование константного выражения типа long к типу ulong, если значение константного выражения не является отрицательным.
6.1.10 Неявные преобразования, включающие параметры типа
Существуют следующие неявные преобразования для заданного параметра типа T:
· Из T к эффективному базовому классу C, из T к любому базовому для C классу, а также из T к любому интерфейсу, реализованному классом C. Если во время выполнения T является типом значения, преобразование выполняется как преобразование упаковки. Иначе преобразование выполняется как неявное преобразование ссылочного типа или преобразование идентификации.
· Из T к типу интерфейса I, принадлежащему эффективному набору интерфейсов T, а также из T к любому базовому интерфейсу I. Если во время выполнения T является типом значения, преобразование выполняется как преобразование упаковки. Иначе преобразование выполняется как неявное преобразование ссылочного типа или преобразование идентификации.
· Из T в параметр типа U, если T зависит от U (§10.1.5). Если во время выполнения U является типом значения, T и U обязательно будут одним типом и преобразование не выполняется. Иначе, если T является типом значений, во время выполнения преобразование выполняется как преобразование упаковки. Иначе преобразование выполняется как неявное преобразование ссылочного типа или преобразование идентификации.
· Из литерала NULL к T, если T является ссылочным типом.
· Из типа T в ссылочный тип I, если для него предусмотрено неявное преобразование в ссылочный тип S0 и для типа S0 предусмотрено тождественное преобразование в тип S. Если во время выполнения это преобразование выполняется так же, как преобразование в тип S0.
· Из типа T в тип интерфейса I, если для него предусмотрено неявное преобразование в тип интерфейса или делегата I0 и тип I0 является вариантно-преобразуемым (§) в тип I (§13.1.3.2). Если во время выполнения T является типом значения, преобразование выполняется как преобразование упаковки. Иначе преобразование выполняется как неявное преобразование ссылочного типа или преобразование идентификации.
Если известно, что T является ссылочным типом (§10.1.5), все описанные выше преобразования классифицируются как неявные преобразования ссылочных типов (§6.1.6). Если T не является ссылочным типом, описанные выше преобразования классифицируются как преобразования упаковки (§6.1.7).
6.1.11 Пользовательские неявные преобразования
Пользовательское неявное преобразование включает в себя необязательное стандартное неявное преобразование, за которым следует выполнение пользовательского оператора неявного преобразования, а затем другое необязательное стандартное неявное преобразование. Точные правила вычисления пользовательских неявных преобразований описываются в §6.4.4.
6.1.12 Преобразования анонимных функций и преобразования группы методов
Анонимные функции и группы методов не имеют типа, однако могут быть неявно преобразованы к типу делегата или типу дерева выражений. Дополнительные сведения о преобразованиях анонимных функций и группы методов см. в §6.5 и §6.6 соответственно.
6.2 Явные преобразования
К явным преобразованиям относятся следующие преобразования:
· Все неявные преобразования.
· Явные преобразования числовых типов.
· Явные преобразования перечисляемых типов.
· Явные преобразования обнуляемых типов.
· Явные преобразования ссылочных типов.
· Явные преобразования типов интерфейса.
· Преобразования распаковки.
· Неявные динамические преобразования.
· Пользовательские явные преобразования.
Явные преобразования могут произойти в выражениях приведения (§7.7.6).
Набор явных преобразований включает все неявные преобразования. Это означает, что допустимы избыточные выражения приведения.
Явные преобразования, не относящиеся к неявным, не всегда выполняются успешно, могут привести к потере данных и могут выполняться в областях типов, существенно различающихся для явной нотации.
6.2.1 Явные преобразования числовых типов
Явные преобразования числовых типов предназначены для преобразования из одного числового типа к другому числовому типу, для которого не существует неявного преобразования (§6.1.2):
· Из типа sbyte в тип byte, ushort, uint, ulong или char.
· Из byte в sbyte и char.
· Из типа short в тип sbyte, byte, ushort, uint, ulong или char.
· Из типа ushort в тип sbyte, byte, short или char.
· Из типа int в тип sbyte, byte, short, ushort, uint, ulong или char.
· Из типа uint в тип sbyte, byte, short, ushort, int или char.
· Из типа long в тип sbyte, byte, short, ushort, int, uint, ulong или char.
· Из типа ulong в тип sbyte, byte, short, ushort, int, uint, long или char.
· Из типа char в тип sbyte, byte или short.
· Из float в sbyte, byte, short, ushort, int, uint, long, ulong, char или decimal.
· Из double в sbyte, byte, short, ushort, int, uint, long, ulong, char, float или decimal.
· Из decimal в sbyte, byte, short, ushort, int, uint, long, ulong, char, float или double.
Поскольку явные преобразования включают в себя все неявные и явные преобразования числовых типов, всегда возможно преобразование из одного числового типа к другому числовому типу с помощью выражения приведения (§7.7.6).
Явные преобразования числовых типов могут привести к потере данных или порождению исключений. Явные преобразования числовых типов осуществляются следующим образом:
· Для преобразований из одного целого типа в другой порядок выполнения зависит от контекста проверки переполнения (§7.6.12), в котором выполняется такое преобразование:
o В контексте checked преобразование завершается успешно, если значение исходного операнда находится в диапазоне целевого типа; если значение исходного операнда находится за пределами диапазона целевого типа создается исключение System.OverflowException.
o В контексте unchecked преобразование всегда выполняется успешно следующим образом:
· Если размер исходного типа превышает размер конечного, дополнительные самые старшие разряды исходного значения отбрасываются. Результат обрабатывается как значение конечного типа.
· Если размер исходного типа меньше размера конечного, размер исходного значения расширяется до размера конечного типа за счет добавления знака или нулей. Расширение знаком используется для исходных типов со знаком, расширение нулями — для исходных типов без знака. Результат обрабатывается как значение конечного типа.
· Если размеры исходного и конечного типов совпадают, исходное значение обрабатывается как значение конечного типа.
· Для преобразований из типа decimal к целому типу исходное значение округляется в сторону нуля до ближайшего целого значения. Результатом преобразования является полученное целое значение. Если результирующее целое значение находится вне диапазона целевого типа, создается исключение System.OverflowException.
· Для преобразований из типа float или double к целому типу порядок выполнения зависит от контекста проверки переполнения (§7.6.12), в котором выполняется такое преобразование:
o В контексте checked преобразование всегда выполняется следующим образом.
· Если значение операнда равно NaN или бесконечности, возникает исключение System.OverflowException.
· В противном случае исходный операнд округляется в сторону нуля до ближайшего целого значения. Если это целое значение принадлежит диапазону конечного типа, оно возвращается в качестве результата преобразования.
· В противном случае создается исключение System.OverflowException.
o В контексте unchecked преобразование всегда выполняется успешно следующим образом:
· Если операнд имеет значение NaN или равен бесконечности, результатом преобразования является неопределенное значение конечного типа.
· В противном случае исходный операнд округляется в сторону нуля до ближайшего целого значения. Если это целое значение принадлежит диапазону конечного типа, оно возвращается в качестве результата преобразования.
· В противном случае в качестве результата преобразования возвращается неопределенное значение конечного типа.
· При преобразовании из типа double в тип float значение double округляется до ближайшего значения float. Если значение double слишком мало для представления в качестве float, результатом становится положительный нуль или отрицательный нуль. Если значение double слишком велико для представления в качестве float, результатом становится положительная бесконечность или отрицательная бесконечность. Если значение double равно NaN, результатом также будет NaN.
· При преобразовании из типа float или double в тип decimal исходное значение преобразуется в представление decimal и при необходимости округляется до ближайшего числа после 28-го десятичного разряда (§4.1.7). Если исходное значение слишком мало для представления в качестве значения типа decimal, в качестве результата возвращается нуль. Если исходное значение равно NaN, бесконечности или слишком велико для представления в виде decimal, создается исключение System.OverflowException.
· При преобразовании из типа decimal в тип float или double значение decimal округляется до ближайшего значения double или float. Это преобразование может привести к потере точности, но никогда не порождает исключений.
6.2.2 Явные преобразования перечисляемых типов
Поддерживаются следующие явные преобразования перечисляемых типов:
· Из типа sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double или decimal в любой перечисляемый тип.
· Из любого перечисляемого типа в тип sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double или decimal.
· Из любого перечисляемого типа к любому другому перечисляемому типу.
Явное преобразование между двумя перечисляемыми типами выполняется посредством рассмотрения любого из участвующих перечисляемых типов в качестве базового для этого перечисляемого типа с последующим выполнением явного или неявного числового преобразования результирующих типов. Например, если для перечисляемого типа E базовым является тип int, преобразование из E в тип byte выполняется как явное преобразование числового типа (§6.2.1) из int в byte, а преобразование из byte в E — как неявное преобразование числового типа (§6.1.2) из byte в int.
6.2.3 Явные преобразования обнуляемых типов
Явные преобразования обнуляемых типов позволяют использовать явные преобразования, выполняемые для необнуляемых типов значений, с обнуляемыми формами таких типов. Для каждого предопределенного явного преобразования из необнуляемого типа значений S к необнуляемому типу значений T (§6.1.1, §6.1.2, §6.1.3, §6.2.1 и §6.2.2) существуют следующие преобразования обнуляемых типов:
· Явное преобразование из S? в T?.
· Явное преобразование из типа S в тип T?.
· Явное преобразование из типа S? в тип T.
Вычисление преобразования обнуляемого типа, основанного на преобразовании из S к T, осуществляется следующим образом:
· Если выполняется преобразование обнуляемых типов из S? в T?:
o Если исходное значение равно NULL (значение свойства HasValue равно false), результатом является значение NULL типа T?.
o В противном случае преобразование вычисляется посредством развертывания из S? в S, преобразования из S к T и последующего свертывания из T к T?.
· Если выполняется преобразование обнуляемых типов из S в T?, преобразование вычисляется как базовое преобразование из S в T с последующим свертыванием из типа T в тип T?.
· Если выполняется преобразование обнуляемых типов из S? в T, преобразование вычисляется как развертывание из S? в S с последующим базовым преобразованием из типа S в тип T.
Обратите внимание, что, если значение равно null, попытка преобразования обнуляемого значения приведет к исключению.
6.2.4 Явные преобразования ссылочных типов
Поддерживаются следующие явные преобразования ссылочных типов:
· Из типов object и dynamic в любой другой ссылочный тип.
· Из любого типа класса S к любому типу класса T, если S является базовым классом для T.
· Из любого типа класса S к любому типу интерфейса T, если S не является запечатанным и S не реализует T.
· Из любого типа интерфейса S к любому типу класса T, если T не является запечатанным или T реализует S.
· Из любого типа интерфейса S к любому типу интерфейса T, если S не является производным от T.
· Из типа массива S с элементами типа SE в тип массива T с элементами типа TE при условии выполнения следующих требований:
o S и T различаются только по типу элементов. Другими словами, S и T имеют одинаковое число измерений.
o SE и TE являются ссылочными типами.
o Существует явное преобразование ссылочных типов из SE в TE.
· Из любого типа System.Array и реализуемых этим типом интерфейсов в любой тип массива.
· Из одномерного типа массива S[] к System.Collections.Generic.IList<T> и его базовым интерфейсам, если существует явное преобразование ссылочного типа из S к T.
· Из System.Collections.Generic.IList<S> и его базовых интерфейсов к одномерному типу массива T[], если существует явное преобразование идентификатора или ссылочного типа из S к T.
· Из любого типа System.Delegate и реализуемых этим типом интерфейсов в любой тип делегата.
· Из ссылочного типа в ссылочный тип T, если для него предусмотрено явное преобразование ссылочных типов в ссылочный тип T0 и для типа T0 предусмотрено тождественное преобразование в тип T.
· Из ссылочного типа в тип интерфейса или делегата T, если для него предусмотрено явное преобразование ссылочных типов в тип интерфейса или делегата T0 и тип T0 является вариантно-преобразуемым в тип T или тип T является вариантно-преобразуемым в тип T0 (§13.1.3.2).
· Из D<S1…Sn> в D<T1…Tn>, где D<X1…Xn> является универсальным типом делегата, D<S1…Sn> не является совместимым или идентичным типу D<T1…Tn> и для каждого параметра типа Xi типа D выполняются следующие условия:
o Если Xi является инвариантным, тип Si идентичен типу Ti.
o Если Xi является ковариантным, существует неявное или явное тождественное преобразование или преобразование ссылочных типов из Si в Ti.
o Если Xi является контрвариантным, типы Si and Ti идентичны или одновременно являются ссылочными типами.
· Явные преобразования, включающие параметры типа, которые имеют ссылочный тип. Дополнительные сведения о явных преобразованиях, включающих параметры типа, см. в §6.2.7.
Для явных преобразований ссылочных типов требуется проверка корректности во время выполнения.
Для успешного завершения явного преобразования ссылочных типов во время выполнения необходимо, чтобы значение исходного операнда было равно null или существовало неявное преобразование ссылочных типов (§6.1.6) или преобразование упаковки (§6.1.7), позволяющее преобразовать фактический тип объекта, на который ссылается исходный операнд, в целевой тип. В случае неудачного завершения явного преобразования ссылочных типов воздается исключение System.InvalidCastException.
Неявные или явные преобразования ссылочных типов никогда не изменяют ссылочный идентификатор преобразуемого объекта. Другими словами, преобразование ссылочного типа может изменить тип ссылки, однако никогда не изменяет тип или значение объекта, на который указывает ссылка.
6.2.5 Преобразования распаковки
Преобразование распаковки обеспечивает явное преобразование ссылочного типа к типу значений. Преобразование распаковки можно выполнять из типов object, dynamic и System.ValueType в любой необнуляемый тип значения, а также из любого типа интерфейса в любой необнуляемый тип значения, реализующий этот тип интерфейса. Кроме того, можно выполнить распаковку типа System.Enum в любой перечисляемый тип.
Преобразование распаковки из ссылочного типа к обнуляемому типу существует в том случае, если существует преобразование распаковки из ссылочного типа к необнуляемому типу значений, являющемуся базовым для обнуляемого типа.
Для типа значения S возможно преобразование распаковки из типа интерфейса I, если для него предусмотрено преобразование распаковки из типа интерфейса I0 и для типа I0 предусмотрено тождественное преобразование в тип I.
Для типа значения S возможно преобразование распаковки из типа интерфейса I, если для него предусмотрено преобразование распаковки из типа интерфейса или делегата I0 и тип I0 является вариантно-преобразуемым в тип I или тип I является вариантно-преобразуемым в тип I0 (§13.1.3.2).
При выполнении операции распаковки сначала проверяется, является ли экземпляр объекта упакованным значением указанного типа значений. После этого выполняется копирование значения из экземпляра. При распаковке пустой ссылки в обнуляемый тип возвращается значение NULL обнуляемого типа. Структура может быть распакована из типа System.ValueType, поскольку он является базовым классом для всех структур (§11.3.2).
Дополнительные сведения о преобразованиях распаковки см. в §4.3.2.
6.2.6 Неявные динамические преобразования.
Существует явное преобразование динамических типов из выражения типа dynamic в любой тип T. Преобразование является динамически привязанным (§7.2.2) — это означает, что во время выполнения будет выполняться поиск явного преобразования типа выражения времени выполнения в тип T. Если преобразование не обнаружено, во время выполнения возникает исключение.
Если динамическая привязка преобразования не требуется, можно сначала преобразовать выражение в тип object, а затем в требуемый тип.
Предположим, что определен следующий класс.
class C
{
int i;
public C(int i) { this.i = i; }
public static explicit operator C(string s)
{
return new C(int.Parse(s));
}
}
Следующий пример иллюстрирует явное динамическое преобразование:
object o = "1";
dynamic d = "2";
var c1 = (C)o; // Compiles, but explicit reference conversion fails
var c2 = (C)d; // Compiles and user defined conversion succeeds
Наилучшее преобразование o в C, найденное во время компиляции, — это явное преобразование ссылочных типов. Оно завершится сбоем во время выполнения, поскольку “1” в действительности не является C. Однако преобразование d в C, как явное динамическое преобразование, отложено до времени выполнения, когда обнаруживается и успешно выполняется пользовательское преобразование из типа d времени выполнения — string — в C.
6.2.7 Явные преобразования, включающие параметры типа
Существуют следующие явные преобразования для заданного параметра типа T:
· Из эффективного базового для типа T класса C к T, а также из любого базового для C класса к T. Если во время выполнения T является типом значения, преобразование выполняется как преобразование распаковки. В противном случае преобразование выполняется как явное преобразование ссылочного типа или идентификатора.
· Из любого типа интерфейса к T. Если во время выполнения T является типом значения, преобразование выполняется как преобразование распаковки. В противном случае преобразование выполняется как явное преобразование ссылочного типа или идентификатора.
· Из T к любому типу интерфейса I, если уже не существует неявного преобразования из T к I. Если во время выполнения T является типом значения, преобразование выполняется как преобразование упаковки, за которым следует явное преобразование ссылочных типов. В противном случае преобразование выполняется как явное преобразование ссылочного типа или идентификатора.
· Из параметра типа U в T, если T зависит от U (§10.1.5). Если во время выполнения U является типом значения, T и U обязательно будут одним типом и преобразование не выполняется. Если в процессе выполнения T является типом значений, во время выполнения преобразование выполняется как преобразование распаковки. В противном случае преобразование выполняется как явное преобразование ссылочного типа или идентификатора.
Если известно, что T является ссылочным типом, все описанные выше преобразования классифицируются как явные преобразования ссылочных типов (§6.2.4). Если T не является ссылочным типом, описанные выше преобразования классифицируются как преобразования распаковки (§6.2.5).
Приведенные выше правила не допускают прямого явного преобразования из безусловного параметра типа к типу, не являющемуся интерфейсом, поскольку его результат может быть непредсказуем. Это связано с необходимостью избежать несогласованности и достичь более точного представления семантики таких преобразований. Например, рассмотрим следующее заявление:
class X<T>
{
public static long F(T t) {
return (long)t; // Error
}
}
Если разрешено прямое явное преобразование из t к int, можно предполагать, что выражение X<int>.F(7) возвратит 7L. Однако результат будет другим, поскольку стандартные преобразования числовых типов применяются только в том случае, если типы являются числовыми во время привязки. Чтобы семантика была понятной, этот пример необходимо записать следующим образом:
class X<T>
{
public static long F(T t) {
return (long)(object)t; // Ok, but will only work when T is long
}
}
Теперь этот код будет компилироваться, но при выполнении выражения X<int>.F(7) будет создано исключение, поскольку упакованный тип int не может быть явно преобразован в long.
6.2.8 Пользовательские явные преобразования
Пользовательское явное преобразование включает в себя необязательное стандартное явное преобразование, за которым следует выполнение пользовательского оператора явного преобразования, а затем другое необязательное стандартное явное преобразование. Точные правила вычисления пользовательских явных преобразований описываются в §6.4.5.
6.3 Стандартные преобразования
Стандартные преобразования — это предопределенные преобразования, которые могут произойти в составе пользовательских.
6.3.1 Стандартные неявные преобразования
К стандартным неявным преобразованиям относятся следующие преобразования:
· Преобразования идентификатора (§6.1.1)
· Неявные преобразования числовых типов (§6.1.2).
· Неявные преобразования обнуляемых типов (§6.1.4).
· Неявные преобразования ссылочных типов (§6.1.6).
· Преобразования упаковки (§6.1.7).
· Неявные преобразования выражений констант (§6.1.8).
· Неявные преобразования, включающие параметры типа (§6.1.10).
К стандартными неявным преобразованиям не относятся пользовательские неявные преобразования.
6.3.2 Стандартные явные преобразования
К стандартным явным преобразованиям относятся все стандартные неявные преобразования, а также подмножество явных преобразований, для которых существуют обратные стандартные неявные преобразования. Другими словами, если существует стандартное неявное преобразование из типа A к типу B, также существует стандартное явное преобразование из типа A к B и из типа B к A.
6.4 Пользовательские преобразования
В C# можно дополнить предопределенные явные и неявные преобразования пользовательскими преобразованиями. Пользовательские преобразования определяются посредством объявления операторов преобразования (§10.10.3) в типах класса и структуры.
6.4.1 Допустимые пользовательские преобразования
В C# допускается объявление лишь некоторых пользовательских преобразований. В частности, не допускается переопределение уже существующего явного или неявного преобразования.
Для данного исходного типа S и конечного типа T, если S или T является типом, допускающим присваивание пустой ссылки, пусть S0 и T0 ссылаются на свои основные типы, иначе S0 и T0 равны S и T соответственно. В классе или структуре разрешено объявлять преобразование от исходного типа S к конечному типу T, если только справедливо все следующее:
· S0 и T0 являются разными типами;
· либо S0, либо T0 является типом структуры или класса, где имеет место объявление этого оператора;
· ни S0, ни T0 не является типом интерфейса;
· без преобразований, определенных пользователем, не существует преобразование от S к T или от T к S.
Дополнительные сведения об ограничениях, накладываемых на пользовательские преобразования, см. в §10.10.3.
6.4.2 Операторы преобразования с нулификацией
Для заданного пользовательского оператора преобразования из необнуляемого типа значений S к необнуляемому типу значений T существует оператор преобразования с нулификацией, который используется для преобразования из S? к T?. Этот оператор преобразования с нулификацией выполняет развертывание из типа S? в тип S, за которым следует пользовательское преобразование из типа S в тип T, после чего выполняется развертывание и<