Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Типы и операции, принимающие значение null
Взглянем на булевский тип. Он допускает присваивание значения либо true, либо false. Но как в таком случае поступить, если нужно определить неизвестное значение? Здесь как раз пригодится тип, допускающий значение null (nullable type). Если в своих программах вы используете типы, допускающие значение null, то всегда должны учитывать эффект, который дает применение значения null с различными операциями. Обычно при использовании унарной или бинарной операции с такими типами результатом будет null, если один или оба операнда будут равны null. Например: int? а = null; int? b = а + 4; // Ь = null int? с = а * 5; // с = null Однако при сравнении типов, принимающих значение null, если хотя бы один из операндов будет null, сравнение всегда даст false. Это значит, что нельзя ожидать, что условие даст в результате true, только потому, что противоположное условие дает false, как часто случается в программах, работающих с типами, не принимающими null. Например: int? а = null; int? b = -5; if (a >= b) System.Console.WriteLine("a >= b"); else System.Console.WriteLine("a < b"); Возможность значения null означает, что произвольно комбинировать в выражении типы, принимающие значения null, и типы, не допускающие их, нельзя. Это обсуждается ниже в подразделе “Преобразования типов ”. Операция поглощения null Операция поглощения null (??) представляет собой сокращенный механизм, обеспечивающий возможность работы с null-значениями в выражениях, включающих ссылочные типы и типы, которые допускают null. Операция размещается между двумя операндами — первый должен иметь тип, допускающий null-значения, или ссылочный, а второй должен быть того же типа, что и первый, либо типа, неявно преобразуемого к типу первого операнда. Операция поглощения null работает следующим образом: - если первый операнд не равен null, то все выражение принимает значение первого операнда; - если первый операнд равен null, все выражение принимает значение второго операнда. Например: int? а = null; int b; b = a?? 10; // b имеет значение 10 a = 3; b = a?? 10; //b имеет значение 3 Если второй операнд не может быть неявно преобразован к типу первого операнда, генерируется ошибка компиляции. Приоритеты операций В табл. 7.4 представлены приоритеты операций С#. Операции, указанные в верхней части таблицы, имеют наивысший приоритет (т.е. вычисляются первыми в выражении, содержащем множество операций).
Таблица 7.4. Приоритеты операций C#
В сложных выражениях для получения корректного результата не следует слишком полагаться на приоритеты операций. Применение скобок для указания порядка вычисления операций проясняет код и позволяет избежать возможной путаницы. Безопасность типов В разделе 6.1 отмечалось, что промежуточный язык (IL) поддерживает строгую безопасность типов в коде. Строгая типизация обеспечивает работу многих служб, представляемых.NET, включая безопасность и межъязыковое взаимодействие. Как и следовало ожидать от языка, компилируемого в IL, C# также является строго типизированным. Помимо прочего, это означает, что типы данных не всегда являются гладко взаимозаменяемыми. В этом разделе речь пойдет о преобразованиях между примитивными типами. Язык C# также поддерживает преобразование между разными ссылочными типами и всегда позволяет определить, как создаваемые вами типы данных ведут себя при преобразовании в другие типы и обратно. Обе эти темы обсуждаются далее в настоящей главе. С другой стороны, обобщения (generics) позволяют избежать некоторых наиболее часто встречающихся ситуаций, когда может понадобиться преобразование типов. Подробности ищите.в главах 5 и 10. Преобразования типов Часто возникает необходимость в преобразовании данных из одного типа в другой. Рассмотрим следующий код: byte value1 = 10; byte value2 = 23; byte total; total = valuel + value2; Console.WriteLine(total); Если вы попытаетесь скомпилировать эти строки, то получите сообщение об ошибке: Cannot implicitly convert type ’int' to 'byte' Неявное преобразование типа 'int' в ’byte' невозможно Проблема здесь в том, что если складывать два байта, результат будет возвращен как int, а не другой byte. Это потому, что byte может содержать только восемь бит данных, поэтому сложение двух байт очень легко может дать результат, который не уместится в один тип byte. Если результат необходимо сохранить в переменной типа byte, его потребуется преобразовать в byte. Последующие разделы посвящены двум механизмам такого преобразования, поддерживаемым в C# - явному (explicit) и неявному (implicit) преобразованию.
Неявные преобразования Преобразования типов обычно могут быть выполнены автоматически (неявно) только в том случае, когда при этом можно гарантировать, что значение не будет изменено никоим образом. Именно по этой причине не работает предыдущий пример; пытаясь преобразовать int в byte, потенциально можно потерять 3 байта данных. Компилятор не позволит это сделать, если только явно не сообщить ему о том, что именно это и требуется. Однако если поместить результат в переменную long вместо byte, то проблем не будет: byte valuel = 10; byte value2 = 23; long total; // это скомпилируется нормально total = valuel + value2;
Console.WriteLine(total); Этот код скомпилируется без ошибок, поскольку long содержит больше байт, чем byte, так что нет риска утери данных. В таких случаях компилятор спокойно осуществляет требуемые преобразования, и его не нужно просить об этом явно. В табл. 7.5 показаны неявные преобразования типов, поддерживаемые С#. Таблица 7.5. Неявные пребразования типов, поддерживаемые в C#
Как и следовало ожидать, неявные преобразования допускаются только от меньших целых типов к большим, но не наоборот. Можно также преобразовывать данные между целыми и действительными значениями, однако здесь правила слегка отличаются. Допускается преобразовывать данные между типами одинакового размера, например, из int/uint в float и long/ulong - в double, однако можно также выполнять преобразование long/ulong в float. При этом может быть потеряно 4 байта, но это означает только, что полученное значение float будет менее точным, чем double; компилятор не трактует это как ошибку, потому что модуль значения не изменяется. Можно также присваивать значение переменной типа без знака переменной типа со знаком, если значение первой окажется в пределах допустимых значений второй. - Относительно преобразования типов, допускающих null-значения, приняты описанные ниже дополнительные соглашения. - Допускающие null типы неявно преобразуются в другие типы, допускающие null, подчиняясь правилам, описанным в табл. 7.5; т.е. int? неявно преобразуется в long?, float?, double? и decimal?. - Не допускающие null типы неявно преобразуются в типы, допускающие null, согласно тем же правилам. То есть int неявно преобразуется в long, float, double и decimal. - Допускающие null типы не могут быть неявно преобразованы в типы, не допускающие null; для этого должно выполняться явное преобразование, как описано в следующем разделе. Дело в том, что переменные этих типов могут содержать значение null, которое невозможно представить типом, не допускающим null.
Явные преобразования Многие преобразования типов не могут быть выполнены явно, и компилятор выдаст ошибку при попытке сделать это. Ниже перечислены некоторые из них: - int в short - возможна потеря данных; - int в uint - возможна потеря данных; - uint в int - возможна потеря данных; - float в int - теряется все, что находится после десятичной запятой; - любой числовой тип в char - возможна потеря данных; - decimal в любой числовой тип - потому что тип decimal внутренне устроен иначе, чем целые числа и числа с плавающей точкой; - int? в int - тип, допускающий null, может иметь значение null. Однако такие преобразования можно выполнить принудительно, используя приведения (cast). Когда вы приводите один тип к другому, то сознательно заставляете компилятор выполнить преобразование. Приведение типов выглядит следующим образом: long val = 30000; int i = (int)val; // Корректное приведение. // Максимальное значение int равно 2147483647. Тип, к которому выполняется приведение, помещается в круглые скобки непосредственно перед преобразуемым значением. Если вы ранее имели дело с языком С, то подобное приведение должно быть знакомо. Если вы работали со специальными ключевыми словами приведения типов C++, такими как static_cast, то имейте в виду, что в C# ничего подобного нет, и надо использовать старый синтаксис приведения С. Приведение может оказаться опасной операцией. Даже простейшее приведение longк intможет вызвать проблемы, когда исходное значение longбольше, чем максимально допустимое значение int: long val = 3000000000; int i = (int)val; // Неверное приведение. // Максимальное значение int равно 2147483647. В этом случае вы не получите ошибки, но также не получите и ожидаемого результата. Если запустить этот код и вывести на экран значение i, то получится вот что: -1294967296 Всегда имеет смысл предполагать, что явное приведение не даст ожидаемого результата. Как вы уже видели, C# предлагает операцию checked, которую можно использовать для проверки того, вызывает ли действие арифметическое переполнение. С помощью этой операции можно проверить, безопасно ли приведение, и если нет — заставить исполняющую среду сгенерировать исключение: long val = 3000000000; int i = checked ((int)val); Памятуя о том, что явные приведения типов потенциально опасны, вы должны позаботиться о включении в свое приложение кода, обрабатывающего возможные сбои таких приведений. Раздел 6.15 посвящена описанию обработки структурированных исключений с применением операторов try и catch. Используя приведения, можно преобразовывать большинство примитивных типов друг в друга; например, в следующем коде 0.5 добавляется к price, а результат приводится к int:
double price = 25.30; int approximatePrice = (int)(price + 0.5); Это даст цену, округленную до ближайшего доллара. Однако при таком преобразовании теряются данные - именно те, что следуют после десятичной запятой. Поэтому подобное преобразование никогда не должно применяться, если нужно выполнять дальнейшие вычисления с модифицированным значением цены. Тем не менее, это удобно, когда необходимо вывести примерное значение завершенных либо частично завершенных вычислений, не утомляя пользователя множеством цифр после запятой. В следующем примере показано, что произойдет при преобразовании беззнакового целого в char: ushort с = 43; char symbol = (char) с; Console.WriteLine(symbol); На экран будет выведен символ с ASCII-кодом 43 - т.е. знак +. Таким образом, вы можете попробовать выполнить любое, какое захотите, преобразование, между числовыми типами (включая char), и оно будет работать (например, decimal в char или наоборот). Преобразование между типами значений не ограничено отдельными переменными, как мы видели до сих пор. Можно также преобразовывать элемент массива типа double в переменную-член структуры типа int: struct ItemDetails { public string Description; public int ApproxPrice; } // ... double [] Prices = { 25.30, 26.20, 27.40, 30.00 }; ItemDetails id; id.Description = "Что-то"; id.ApproxPrice = (int) (Prices [0] +0.5); Чтобы преобразовать тип, допускающий null, к типу, null не допускающему, или же в другой тип, допускающий null, когда при этом может происходить потеря данных, следует применять явное приведение. Важно отметить, что это истинно даже при преобразовании данных между элементами с одинаковым типом, лежащим в основе, например, из int? в int или из float? в float. Это потому, что допускающий null тип может Содержать значение null, которое невозможно представить типом, такого значения не допускающим. Там, где возможно явное приведение между двумя эквивалентными, не допускающими null типами, возможно также явное приведение между двумя аналогичными типами, допускающими null. Однако если выполняется приведение допускающего null типа в тип, не допускающий null, и при этом первая переменная имеет значение null, то генерируется исключение InvalidOperationException, например: int? а = null; int b = (int)а; // Сгенерирует исключение Используя явное приведение и проявив немного осторожности и внимания, можно преобразовать любой экземпляр простого типа значений в почти любой другой тип. Однако существуют и ограничения - поскольку предполагается применение типов значений, между собой можно преобразовывать числовые типы, тип char и типы перечислений. Приводить булевский тип в любой другой или наоборот нельзя. Для выполнения преобразований между числами и строками в библиотеке классов.NET предусмотрены специальные методы. Класс Object реализует возвращающий строку метод ToString(), который переопределен во всех базовых типах.NET: int i = 10; string s = i.ToString();
Аналогично, если необходимо разобрать строку и извлечь из нее числовое или булевское значение, можно воспользоваться методом Parse(), который поддерживают все предопределенные типы значений: string s = "100"; int i = int.Parse (s); Console.WriteLine (i + 50); // Добавляем 50, чтобы доказать, // что это действительно int. Следует отметить, что Parse() регистрирует ошибку, генерируя исключение, если оказывается не в состоянии преобразовать строку (например, если вы попытаетесь преобразовать строку Hello в целое число). Исключения описаны в разделе 6.15. Упаковка и распаковка В разделе 6.2 вы узнали, что все типы - простые предопределенные вроде int и char, а также сложные наподобие классов и структур - наследуются от типа object. Это значит, что даже с литералами можно обращаться как с объектами: string s = 10.ToString(); Однако вы также видели, что типы данных C# подразделяются на типы значений, которые размещаются в стеке, и ссылочные типы, размещаемые в куче. Как объяснить возможность вызова методов для int, если int - не более чем четырехбайтовое значение в стеке? Способ, который использует C# для достижения такого эффекта, называется упаковкой (boxing); Упаковка и ее противоположность - распаковка (unboxing) - позволяют преобразовывать типы значений в ссылочные типы и затем обратно в типы значений. Эта тема была включена в раздел, посвященный приведению, потому, что, по сути, при этом выполняется приведение значения к типу object. Упаковка - термин, применяемый для описания трансформации типа значения в ссылочный тип. В основном, при этом исполняющая среда создает временный “ящик” типа ссылки на объект в куче. Такое преобразование может происходить неявно, как в предыдущем примере, но его можно также выполнять явно: int myIntNumber = 20; object myObject = myIntNumber; Распаковка - это термин, описывающий обратный процесс, когда ранее упакованная величина приводится обратно к типу значений. Здесь используется термин приведение, поскольку это должно быть сделано явно. Используемый синтаксис подобен тому, что применяется при явном приведении типов, описанном выше: int myIntNumber = 20; object myObject = mylntNumber; // Упаковать int int mySecondNumber = (int)myObject; // Распаковать обратно в int Вы можете распаковать только ту переменную, которая была ранее упакована. Если выполнить последнюю строку, когда myObject не является упаковкой для int, во время выполнения будет сгенерировано исключение. Одно важное предостережение: при распаковке следует соблюдать осторожность, чтобы принимающая переменная имела в себе достаточно места для сохранения всех байтов распаковываемого значения. Например, int в C# имеет длину только 32 бита, а потому распаковка значения long (длиной 64 бита) в int, как показано ниже, приведет к генерации исключения InvalidCastException: long myLongNumber = 333333423; object myObject = (object)myLongNumber; int mylntNumber = (int)myObject; Проверка равенства объектов После того, как мы обсудили операции в целом и кратко коснулись операции равенства в частности, стоит задуматься - что может означать равенство, когда речь идет о классах и структурах? Понимание механики сравнения объектов существенно для программирования логических выражений, а также важно для реализации перегрузок и приведений. Это - тема, на которой будет сосредоточено внимание в оставшейся части главы. Механизмы сравнения объектов различаются в зависимости от того, сравниваются ли ссылочные типы (экземпляры классов) или же типы значений (примитивные типы данных, экземпляры структур или перечислений). В последующих разделах мы независимо рассмотрим сравнение ссылочных типов и типов значений.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Последнее изменение этой страницы: 2016-12-30; просмотров: 321; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.144.124.232 (0.045 с.) |