Операции преобразования

, как следует из их названия, являются операциями, преобразующими объекты одного типа в объекты другого типа. могут допускать как неявные, так и явные преобразования. Неявные преобразования выполняются при простом присваивании, в то время как явные требуют знакомого синтаксиса приведения, где целевой тип указывается в скобках, предшествующих экземпляру, из которого присваивается значение.

На неявные операции накладывается одно важное ограничение. Стандарт С# требует, чтобы неявные операции не генерировали исключений и всегда гарантированно завершались успешно, без потери информации. Если соблюсти это требование не удается, то преобразование должно быть явным.

Например, при преобразовании одного типа в другой всегда имеется возможность утери информации, если целевой тип не настолько выразительный, как исходный.

Рассмотрим преобразование long в short. Ясно, что информация может быть утеряна, если значение long окажется больше максимально допустимого для типа short (short.MaxValue). Такое преобразование должно быть явным и требовать от пользователя применения синтаксиса приведения.

Теперь предположим, что выполняется противоположное преобразование, т.е. short в long. Такое преобразование всегда проходит успешно, поэтому оно может быть неявным.

Выполнение явных преобразований из типа с большим диапазоном хранимых значений в тип с меньшим диапазоном может дать ошибку усечения, если исходное значение окажется слишком большим, чтобы быть представленным в маленьком типе. Например, при выполнении явного приведения long к short можете возникнуть ситуация переполнения. По умолчанию скомпилированный код будет молча производить усечение. Если скомпилировать код с опцией компилятора /checked*, то при попытке явного преобразования long к short будет генерироваться исключение System.OverflowException. Рекомендуется всегда применять опцию /checked*.

Давайте посмотрим, какие операции преобразования должны быть предусмотрены для Complex. Довольно несложно представить, по крайней мере, один определенный случай — преобразование из double в Complex. Несомненно, такое преобразование должно быть неявным.

Другой вариант — Complex в double — требует явного преобразования. (Поскольку приведение Complex к double все равно не имеет смысла и показано здесь только для примера, в качестве результата этого приведения можно возвращать значение по модулю, а не просто вещественную часть комплексного числа.) Рассмотрим пример реализации операций приведения:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System;
public struct Complex
{
public Complex ( double real, double imaginary )  {
this.real = real;
this.imaginary = imaginary;
// Переопределение System.Object
public override string ToString ()  {
return String.Format( "({0},  {1})", real, imaginary );
public double Magnitude {
get {
return Math.Sqrt( Math.Pow(this.real, 2) + Math.Pow(this.imaginary, 2)  );
}      }
public static implicit operator Complex ( double d )  {
return new Complex ( d, 0 ) ;
}
public static explicit operator double ( Complex с )  {
return c.Magnitude;
// Остальные методы опущены для ясности, private double real;
private double imaginary;
}
public class EntryPoint {
static void Main()  {
Complex cpxl = new Complex ( 1.0, 3.0 );
Complex cpx2 =2.0;
// Использовать неявную операцию,
double d = (double) cpxl;
// Использовать явную операцию.
Console.WriteLine ( "cpxl = {0}", cpxl );
Console.WriteLine ( "cpx2 = {0}", cpx2 );
Console.WriteLine( "d = {0}", d );
}

В коде метода Main используются операции преобразования. При реализации операций преобразования будьте осторожны, чтобы не создать для пользователей никаких сюрпризов или путаницы с неявными преобразованиями.

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

Если написать множество операций неявного преобразования, не имеющих семантического смысла, пользователи в один прекрасный день будут весьма удивлены, когда компилятор решит выполнить преобразование, когда они вовсе этого не ожидали. Например, компилятор может выполнять неявное преобразование, пытаясь подогнать аргумент при вызове метода. Даже если операции преобразования имеют семантический смысл, они могут таить в себе ряд сюрпризов, поскольку у компилятора есть возможность молча преобразовывать экземпляры одного типа в другой, когда он сочтет это необходимым.

В отличие от С++, где конструкторы с одним параметром по умолчанию ведут себя как операции преобразования, С# требует явного написания операций неявного преобразования для определяемых вами типов.

Таким образом, вы не сможете нечаянно создать операцию неявного преобразования, не осознавая того, что делаете (как это возможно в С++). Однако для того, чтобы предоставить такие преобразования, необходимо всегда подчиняться правилам перегрузки методов.

Да, я понимаю последствия своих высказываний и возможную путаницу, вызванную применением слов явный и неявный. Я явно надеюсь не запутать вас неявно…

Рассмотрим случай, когда Complex предоставляет другую операцию явного преобразования в экземпляр Fraction, а также в экземпляр double. Это потребует добавления в Complex методов со следующими сигнатурами:

1
2
public static explicit operator double ( Complex d )
public static explicit operator Fraction ( Complex f )

Эти два метода принимают тип Complex и возвращают другой тип. Однако правила перегрузки четко указывают, что тип возврата в сигнатуре метода не учитывается. Если следовать этому правилу, такие два метода вносят неоднозначность, приводящую к ошибке компиляции. Но на самом деле они не вносят неоднозначности, поскольку существует специальное правило, разрешающее рассматривать тип возврата операции преобразования как часть сигнатуры.

Между прочим, ключевые слова implicit и explicit не входят в сигнатуру методов операций преобразования, поэтому иметь одновременно и явную, и неявную версии одной и той же операции преобразования невозможно. Естественно, по крайней мере, один из типов в сигнатуре операции преобразования должен быть включающим типом.

Реализация операции преобразования из типа Apples в тип Oranges для типа Complex не допускается.

Загадка плавающей точки
Джон Скит (Jon Skeet) привел блестящий пример, который доказывает, что в некоторых случаях, имея дело с числами с плавающей точкой, неявные преобразования могут привести к потере данных. Джон иллюстрирует это утверждение следующим примером:

1
2
3
4
5
6
7
8
9
10
11
12
using System;
public class Test {
static void Main () {
long 11 = long.MaxValue - 5;
long 12 = long.MaxValue - 4;
double dl = 11;
double d2 = 12;
Console.WriteLine(dl);
Console.WriteLine(d2);
Console.WriteLine(11 == 12);
Console.WriteLine(dl == d2);
}

После запуска этого кода на выполнение будет получен несколько неожиданный результат:

9.22337203685478Е+18
9.22337203685478Е+18
False
True

Прежде чем поднимать шум по этому поводу, следует вспомнить, что числа с плавающей точкой являются одной из самых коварных и часто недооцениваемых областей программирования. Более того, имея дело с числами с плавающей точкой, операции сравнения часто учитывают только значащие разряды, а остальная часть рассматривается как допустимая погрешность. Тематике правильного представления чисел с плавающей точкой на машинах, рассчитанных, по сути, только на целочисленные вычисления, посвящено множество диссертаций.

Заинтересованные могут ознакомиться с приложением D к монографии Numerical Computation Guide (Руководство по числовым расчетам), которую можно найти по адресу http://docs.sun.com/source/806-3568/ncg_goldberg.html.
Кстати, если в предыдущем примере заменить тип double на decimal, то получится менее неожиданный результат, ввиду того, что decimal может представлять больше десятичных разрядов, чем double.

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100