Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Реализация пользовательских приведений
В настоящем разделе иллюстрируется применение явных и неявных пользовательских приведений на примере под названием SimpleCurrency. В этом примере определяется структура Currency, хранящая положительное денежное значение в американских долларах. Для этой цели в C# предусмотрен тип decimal, но все-таки может случиться, что вы захотите написать собственный класс, представляющий денежные значения, если возникнет потребность в какой-то сложной финансовой обработке, а, следовательно - в реализации специфических методов этого класса. Синтаксис приведения одинаков для структур и классов. Здесь мы приводим пример со структурой, но он будет работать точно так же, если объявить Currency классом. Изначальное определение структуры Currency выглядит следующим образом: struct Currency { public uint Dollars; public ushort Cents; public Currency(uint dollars, ushort cents) { this.Dollars = dollars; this.Cents = cents; } public override string ToString() { return string. Format (“${0}.{1,-2:00}”, Dollars, Cents); } } Применение беззнаковых типов данных для полей Dollar и Cents гарантирует, что экземпляры Currency будут содержать только положительные значения. Это ограничение мы реализуем таким способом, чтобы ниже проиллюстрировать некоторые моменты, связанные с явными приведениями. Вы можете использовать подобный класс для того, чтобы, например, хранить информацию о зарплате сотрудников компании (понятно, что зарплаты не бывают отрицательными). Для простоты мы делаем поля общедоступными, хртя обычно вы будете объявлять их приватными и определять соответствующие свойства для доступа к долларам и центам. Начнем с предположения, что вам понадобится преобразовывать экземпляры Currency в значения типа float, в которых целая часть представляет доллары. Другими словами, нужно сделать так, чтобы можно было записать следующий код: Currency balance = new Currency(10,50); float f = balance; // Нужно присвоить f значение 10.5 Чтобы обеспечить такую возможность, определим приведение. То есть добавим к определению Currency такой код: public static implicit operator float (Currency value) { return value.Dollars + (value.Cents/100.0f); } Это приведение неявное. В данном случае это - разумный выбор, потому что, как должно быть ясно из определения Currency, любое значение, которое можно сохранить в Currency, также может быть записано как float. При таком приведении не может случиться ничего неправильного.
Здесь есть небольшая натяжка: фактически, при преобразовании и int в float происходит потеря точности, но Microsoft в любом случае рассматривает эту ошибку как не столь существенную и разрешает неявное приведение uint в float. Однако если вы хотите преобразовывать float в Currency, то не гарантируется, что такое преобразование будет работать: float может содержать отрицательные значения, которые для Currency недопустимы. Кроме того, float может содержать числа намного большие, чем можно уместить в поле (uint)Dollar нашей структуры Currency. Поэтому если float содержит неподходящее значение, то преобразование его в Currency может дать непредсказуемый результат. Учитывая такую опасность, приведение float в Currency должно быть явным (explicit). Предпримем первую попытку, которая, хотя и не даст правильного результата, но послужит поучительным примером: public static explicit operator Currency (float value) { uint dollars = (uint)value; ushort cents = (ushort)((value-dollars)*100); return new Currency(dollars, cents); } Следующий код теперь успешно скомпилируется: float amount = 45.63f; Currency amount2 = (Currency)amount; Однако приведенный ниже код вызовет ошибку компиляции, поскольку в нем предпринимается попытка использовать явное приведение неявно: float amount = 45.63f; Currency amount2 = amount; // неверно Объявив приведение явным, вы тем самым предупреждаете разработчика о том, что следует быть осторожным, поскольку может случиться потеря данных. Однако, как вскоре будет показано, это не совсем желательное поведение структуры Currency. Попробуем написать тестовый пример и запустить его. У нас будет метод Main(), который создаст экземпляр Currency и попытается выполнить несколько преобразований. В начале кода напишем вывод на экран значения денежного баланса двумя разными способами (это понадобится для того, чтобы кое-что проиллюстрировать позднее), static void Main() { try { Currency balance = new Currency(50,35); Console.WriteLine(balance); Console.WriteLine("Баланс равен " + balance); Console.WriteLine("Баланс равен (используя ToString ()) " + balance.ToString()); float balance2 = balance; Console.WriteLine("После преобразования в float = " + balance2); balance = (Currency) balance2; Console.WriteLine("После преобразования обратно в Currency = " + balance); Console.WriteLine("Теперь преобразуем значение вне допустимого диапазона " +
"-$50.50 в Currency:"); checked { balance = (Currency)(-50.50); Console.WriteLine("Результат: " + balance.ToString()); } } catch(Exception e) { Console.WriteLine("Возникло исключение: " + e.Message); } } Обратите внимание, что весь код помещен в блок try, чтобы перехватывать любые исключения, которые могут случиться во время приведений. Кроме того, те строки, которые проверяют преобразование значений вне допустимого диапазона в Currency, помещены в блок checked, чтобы отловить отрицательные значения. Запуск этого кода даст следующий вывод: 50.35 Баланс равен $50.35 Баланс равен (используя ToString()) $50.35 После преобразования в float = 50.35 ПосЛе преобразования обратно в Currency = $50.34 Теперь преобразуем значение вне допустимого диапазона -$50.50 в Currency:
Понятно, что код работает не так хорошо, как ожидалось. Во-первых, обратное преобразование из float в Currency дает неправильный результат - $50.34 вместо $50.35. Во-вторых, не генерируется никаких исключений при попытке преобразования значения, выходящего за пределы допустимого диапазона. Первая проблема вызвана ошибками округления. Если приведение применяется для преобразования float в uint, то компьютер отбрасывает дробную часть вместо того, чтобы округлять ее. Компьютер сохраняет значения в двоичном виде, а не в десятичном, и дробная часть 0.35 не может быть точно представлена в двоичном виде (как и значение 1/3 не может быть представлено в десятичном виде - оно превращается в периодическую десятичную дробь 0,3333...). Поэтому компьютер сохраняет значение, которое чуть меньше, чем 0.35, и которое может быть представлено в двоичном формате. Умножив его на 100, вы получите число, на некоторую дробную величину меньше, чем 35, которое усекается до 34 центов. Понятно, что в этой ситуации ошибки, вызванные усечением, достаточно серьезны, и чтобы избежать их, нужно применить некоторый интеллектуальный способ округления. К счастью, Microsoft поставляет класс, который может это делать: System.Convert. Этот класс содержит большое количество статических методов, выполняющих разнообразные числовые преобразования, включая подходящий в этой ситуации Convert.ToUInt16(). Следует отметить, что дополнительная забота, которую реализуют методы System. Convert, требует некоторых затрат, отражающихся на производительности, поэтому использовать их нужно только тогда, когда в этом действительно есть необходимость. Теперь разберемся, почему не возникло исключение переполнения. Проблема вот в чем: место, где на самом деле случилось переполнение, на самом деле вовсе не находится в процедуре Main() - оно находится в коде операции приведения, который вызывается из метода Main(). И этот код не было помечен как checked. Решение состоит в заключении в блок checked также и тела операции приведения. Внеся эти два изменения, получим следующий код преобразования типа: public static explicit operator Currency (float value) { checked { uint dollars = (uint)value; ushort cents = Convert.ToUInt16((value-dollars)*100); return new Currency(dollars, cents); } } Следует обратить внимание, что для вычисления центов применяется Convert.ToUInt16(), как упоминалось ранее, но этот метод не используется для вычисления долларовой части суммы. Для работы с долларовой составляющей суммы нет необходимости в System. Convert, потому что усечение значения float дает то, что нужно.
Стоит упомянуть, что методы System.Convert также сами заботятся о контроле переполнения. Поэтому в данном конкретном случае, который мы здесь рассматриваем, нет необходимости помещать вызов Convert.ToUInt16() в checked-контекст. Однако этот контекст все еще нужен для явного приведения значения в доллары.
Пока мы не увидим новых результатов от использования checked-приведения, потому что в этом разделе потребуется внести еще несколько модификаций в пример SimpleCurrency. Если вы определяете приведение, которое будет использоваться очень часто и для которого производительность стоит на первом месте, то можете предпочесть не выполнять никакой проверки ошибок. Это вполне легитимная ситуация, если поведение такого преобразования и отсутствие контроля ошибок четко документированы. Приведение между классами Пример Currency включает только класс, который преобразуется в float и обратно - т.е. в один из предопределенных типов. Однако нет необходимости всегда в этот процесс вовлекать простые типы данных. Абсолютно законным является определение приведений между экземплярами разных структур или классов, определенных вами. Однако при этом следует помнить о ряде ограничений: - нельзя определять приведение между классами, если один из них является наследником другого (приведение такого рода, как вы увидите, уже существует); - приведение может быть определено внутри определения как исходного типа, так и типа назначения. Чтобы проиллюстрировать эти требования, предположим, что есть иерархия классов, показанная на рис. 7.1. Другими словами, классы С и D непрямо унаследованы от А. В этом случае единственными допустимыми пользовательским приведением между типами А, В, С и D будут приведения между классами С и D, потому что эти классы не наследуют друг друга. Код таких приведений может выглядеть следующим образом (предполагается, что они будут явными, как это обычно бывает при определении приведений между пользовательскими типами): public static explicit operator D(C value) { // и т.д. } public static explicit operator C(D value) { // и т.д. } При определении каждой из этих операций приведения у вас есть выбор, куда их поместить - внутрь определения класса С или же внутрь определения класса D, но никуда более. C# требует, чтобы определение приведения было помещено в определение либо исходного класса (или структуры), либо целевого. Побочным эффектом этого требования является невозможность определить приведение между двумя классами, не имея доступа на редактирование исходного кода хотя бы одного из них. И это разумно, поскольку подобным образом предотвращается определение приведений к вашим классам от независимых разработчиков. После того как определено некоторое приведение внутри одного класса, определить такое же приведение внутри другого класса уже нельзя. Очевидно, что должна существовать только одна версия приведения для каждого преобразования, иначе компилятор не будет знать, какую выбрать.
|
|||||||
Последнее изменение этой страницы: 2016-12-30; просмотров: 218; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.116.8.110 (0.018 с.) |