63
Int i = Masha: // неявное преобразование
Masha = (Monster) 500; // явное преобразование
Неявное преобразованиевыполняется автоматически:
при присваивании объекта переменной целевого типа, как в примере;
при использовании объекта в выражении, содержащем переменные целевого типа;
при передаче объекта в метод на место параметра целевого типа;
при явном приведении типа.
+
Явное преобразование выполняется при использовании операции приведения типа.
Все операции класса должны иметь разные сигнатуры. В отличие от других видов методов, для операций преобразования тип возвращаемого значения включается в сигнатуру, иначе нельзя было бы определять варианты преобразования данного типа в несколько других.
Ключевые слова implicit иexplicitв сигнатуру не включаются, следовательно, для одного и того же преобразования нельзя определить одновременно явную и неявную версии.
Неявное преобразованиеследует определять так, чтобы при его выполнении не возникала потеря точности и не генерировались исключения. Если эти ситуации возможны, преобразование следует описать как явное.
1 2 3 4 5 6 7 8 9 10 ... 24
Практическая работа № 1.17. Работа с типом данных структура Цель работы: изучение типа данных структура
Теоретический материал Типы структур
Структуры по своей внутренней организации похожи на классы, они содержат набор полей и методов. Как правило, их используют для объявления типов, которые определяются только значениями полей и не имеют индивидуальности. Например, объекты, описывающие транзакции, несмотря на то, что значения их полей могут совпадать не будут тождественными, то есть нам их нужно уметь различать несмотря на внешнее сходство. А точки на геометрической плоскости, которые задаются двумя координатами, такой индивидуальности не имеют, и если координаты двух точек совпадают, то это значит, что речь идет об одной и той же точке. Именно для таких типов хорошо подходят структуры. Для их объявления используется ключевое слово struct: struct Point
{ public Point(double x, double y)
{
X = x;
Y = y;
} public double X {get;} public double Y {get;}
}
Point p1 = new Point(1,2);
Console.WriteLine($”({p1.X}, (p1.Y})”);
Типы значений, допускающие null
Про типы значений, допускающих null см. ниже “Nullable-типы (нулевые типы) и операция ??”.
Типы значений кортежей
Кортежи используются для группировки данных, которые могут иметь разные типы в единую именованную сущность. Они являются объектами типа System.ValueTuple. Объявим кортеж, состоящий из двух элементов типа double:
(double, double) tp1 = (1.0, 2.0); // явное задание типов элементов кортежа var tp2 = (8.1, 4.3); // использование var для объявления кортежа
Поля кортежа могут быть именованными:
(double X, double Y) tp3 = (3.2, 5.34); var tp4 = (X: 1.2, Y: 3.4); var X = 5.6; var Y = 7.8; var tp5 = (X, Y);
Более подробно про кортежи типов System.ValueTuple (тип-значение) и System.Tuple
(ссылочный тип) будет рассказано в одном из следующих уроков.
Ссылочные типы
Переменные ссылочного типа располагаются в куче, за их уничтожение отвечает сборщик мусора, поэтому про них нельзя точно сказать, когда занимаемая ими память будет освобождена.
72
Переменная представляется в виде ссылки на соответствующее место в куче. Ссылочные типы являются наследниками от System.Object.
Типы классов
Классы являются наиболее фундаментальным элементов в системе типов C#. Тип
System.Object, который является родительским для всех типов данных представляет собой класс.
Из рассмотренных выше типов данных, класс больше всего похож на структуру, у них даже объявление похожи, только вместо ключевого слова struct нужно использовать class. class Persone
{ public Persone(string name, int age)
{
Name = name;
Age = age;
} public string Name {get;set;} public int Age {get;set;}
}
Persone persone1 = new Persone("John", 21);
Console.WriteLine($"Persone: Name: {persone1.Name}, Age: {persone1.Age})");
Среди классов в C# можно выделить ряд классов, которые играю важную роль в языке, они перечислены в таблице ниже.
Класс Описание
System.Object Базовый класс для всех типов в C#
System.ValueType
Базовый класс для всех типов-значений
System.Enum Базовый класс для всех перечислений
System.Array Базовый класс для всех массивов
System.Delegate
Базовый класс для всех делегатов
System.Exception
Базовый класс для всех исключений
System.String Класс, определяющий строкой тип данных
Типы интерфейсов
Интерфейс представляет собой набор методов, свойств, событий и индексаторов. До версии C# 8.0 интерфейс предполагал только декларацию (объявление) указанных выше элементов, начиная с 8.0, в рамках интерфейса можно располагать реализацию по умолчанию.
Фактически интерфейс представляет собой контракт, а класс, который от него наследуюется, реализует этот контракт.
Ход работы
1. Создадим интерфейс для описания человека, у которого есть два свойства имя: Name, и возраст: Age: interface IPersone
{ string Name {get;set;} int Age {get;set;}
}
2. Изменим объявление класса Persone, так, чтобы он представлял реализацию интерфейса IPersone: class Persone: IPersone
{
//…
}
3. Объявим переменную типа IPersone:
IPersone persone2 = new Persone("Jim", 25);
Console.WriteLine($"Persone: Name: {persone2.Name}, Age: {persone2.Age})");
Более подробно про интерфейсы будет рассказано в одном из следующих уроков.
73
Типы массивов
Массив – это структура данных, которая позволяет хранить один или более элементов.
Массивы в C# делятся на одномерные и многомерные, среди последних наибольшее распространение получили двумерные массивы. Все массивы являются наследниками класса
System.Array.
Создание и инициализация одномерного массива: int[] nArr1 = new int[5]; nArr1[0] = 0; nArr1[1] = 1; nArr1[2] = 2; nArr1[3] = 3; nArr1[4] = 4;
Пример прямоугольного массива, в нем строки имеют одинаковую длину: int[,] nMx = new int[2,2]; // прямоугольный массив nMx[0,0]=0; nMx[0,1]=1; nMx[1,0]=2; nMx[1,1]=3;
Пример зубчатого (jagged) массива, в нем строки могут иметь разную длину: int[][] jg = new int[2][]; // зубчатый массив jg[0] = new int[3]; jg[1] = new int[1];
Более подробно про массивы будет рассказано в одном из следующих уроков.
Типы делегатов
Делегаты являются аналогом указателей на функции из языков C / C++. Они используются в случаях, когда нужно передать некоторую функциональность как аргумент, перенаправлять вызовы и т.д.
Nullable-типы (нулевые типы) и операция ??
Объявление и инициализация Nullable-переменных
В работе с типами-значениями есть одна особенность, они не могут иметь значение null.
При наличии любой из следующих строк кода, компиляция программы не будет выполнена: int nv = null; bool bv = null;
На практике, особенно при работе с базами данных, может возникнуть ситуация, когда в записи из таблицы пропущены несколько столбцов (нет данных), в этом случае, соответствующей переменной нужно будет присвоить значение null, но она может иметь тип int или double, что приведет к ошибке.
Можно объявить переменную с использованием символа ? после указания типа, тогда она станет nullable-переменной – переменной поддерживающей null-значение: int? nv1 = null; bool? bv1 = null;
Использование символа ? является синтаксическим сахаром для конструкции
Nullable, где T – это имя типа. Представленные выше примеры можно переписать так:
Nullable nv1 = null;
Nullable bv1 = null;
Проверка на null. Работа с HasValue и Value
Для того чтобы проверить, что переменная имеет значение null можно воспользоваться оператором is с шаблоном типа: bool? flagA = true; if(flagA is bool valueOfFlag)
74
{
Console.WriteLine("flagA is not null, value: {valueOfFlag}");
}
Также можно воспользоваться свойствами класса Nullable:
Nullable.HasValue
Возвращает true если переменная имеет значение базового типа. То есть если она не null.
Nullable.Value
Возвращает значение переменной если HasValue равно true, иначе выбрасывает исключение InvalidOperationException. bool? flagB = false; if(flagB.HasValue)
{
Console.WriteLine("flagB is not null, value: {flagB.Value}");
}
Приведение Nullable-переменной к базовому типу
При работе с Nullable-переменными их нельзя напрямую присваивать переменным базового типа. Следующий код не будет скомпилирован: double? nvd1 = 12.3; double nvd2 = nvd1; // error
Для приведения Nullable-переменной к базовому типу можно воспользоваться явным приведением: double nvd3 = (double) nvd1;
В этом случае следует помнить, что если значение Nullable-переменной равно null, то при выполнении данной операции будет выброшено исключение InvalidOperationException.
Второй вариант – это использование оператора ??, при этом нужно дополнительно задаться значением, которое будет присвоено переменной базового типа если в исходной лежит значение null: double nvd4 = nvd1 ?? 0.0;
Console.WriteLine(nvd4); bool? nvb1 = null; bool nvb2 = nvb1 ?? false;
Console.WriteLine(nvb1);
Console.WriteLine(nvb2);
Второй вариант позволяет более лаконично обрабатывать ситуацию, когда вызов какого- то метода может возвращать null, а результат его работы нужно присвоить типу-значению, при этом заранее известно, какое значение нужно присвоить переменной в этой ситуации: static int? GetValue(bool flag)
{ if (flag == true) return 1000; else return null;
} static void Main(string[] args)
{ int test1 = GetValue(true) ?? 123;
Console.WriteLine(test1); int test2 = GetValue(false) ?? 123;
Console.WriteLine(test2);
}
Ключевое слово dynamic
75
Вначале статьи мы говорили о том, что есть языки со статической и динамической типизацией, C# – язык со статической типизацией, т.е. типы переменных определяются на этапе компиляции. Но в рамках платформы .NET есть возможность работать с Python и Ruby в реализациях IronPython и IronRuby, но это языки с динамической типизацией, в них тип определяется во время выполнения программы. Для того чтобы можно было в C# проекте работать с тем, что было создано в рамках IronPython (или IronRuby) начиная с C# 4, в языке появилось ключевое слово dynamic и среда DLR (Dynamic Language Runtime), благодаря которой можно создавать динамические объекты, тип которых будет определен на этапе выполнения программы, а не в процессе компиляции.
С помощью ключевого слова dynamic объявляются переменные, для которых нужно опустить проверку типов в процессе компиляции. Для этой переменной не производится присвоение типа из BCL (Base Class Library) – стандартной библиотеки классов .NET, фактически dynamic – это тип System.Object с дополнительным набором метаданных, они нужны для определения типа переменной в процессе выполнения (так называемое, позднее связывание).
Ниже приведены несколько примеров, на которых можно разобраться с тем, как работать с dynamic:
// Создадим переменную типа dynamic и проинициализируем ее double значением dynamic dval1 = 12.3;
// Посмотрим на ее значение и тип
Console.WriteLine($"Value: {dval1}");
Console.WriteLine($"Type: {typeof(dval1)}"));
// Изменим значение переменной: dval1 += 17;
Console.WriteLine($"Value: {dval1}");
Console.WriteLine($"Type: {typeof(dval1)}"));
// Присвоим переменной значение другого типа: bool dval1 = true;
// Посмотрим на ее значение и тип
Console.WriteLine($"Value: {dval1}");
Console.WriteLine($"Type: {typeof(dval1)}"));
Как вы можете видеть значение и тип переменной dval1 менялись в процессе выполнения программы. При этом нужно помнить, что если вы присвоили переменной dynamic, какое-то значение, которое определило ее тип, а пытаетесь с ней работать как с переменной другого типа, то будет вызвано исключение: dynamic dval2 = "hello"; // в переменной dval2 хранится строковое значение
Console.WriteLine($"Value: {dval2}");
Console.WriteLine($"Type: {typeof(dval2)}"); dval2 = 123; // теперь значение типа int dval2 = dval2.ToUpper() // попытка вызвать на ней .ToUpper() приведет к ошибке
Оператор default
Оператор default создает значение по умолчанию для указанного типа, используется оно следующим образом: default(T), где T – это тип, для которого нужно создать соответствующее значение.
4. Объявим переменную типа int и присвоим ей значение по умолчанию с помощью new: int n3 = new int();
Console.WriteLine($"Default int value: {n3}");
Тоже самое можно сделать с помощью оператора default: int n4 = default(int);
Console.WriteLine($"Value of int that inited by default(T): {n4}");
Если C# может самостоятельно вывести тип, то можно воспользоваться не оператором, а литерой default, без явного указания типа: