Программист, записывающий
Большинство операций в языке C#, их checked и unchecked, применимые к
Как это обычно делается, приведем
| | Категория | Операции | Порядок |
|---|---|---|---|
| 0 | Первичные | (expr) x.y f(x) a[x] x++ x-- new sizeof(t) typeof(t) checked(expr) unchecked(expr) |
Слева направо |
| 1 | Унарные | + - ! ~ ++x --x (T)x |
Слева направо |
| 2 | Мультипликативные (Умножение) | * / % |
Слева направо |
| 3 | Аддитивные (Сложение) | + - |
Слева направо |
| 4 | << >> |
Слева направо | |
| 5 | < > <= >= is as |
Слева направо | |
| 6 | Эквивалентность | == != |
Слева направо |
| 7 | Логическое И | |
Слева направо |
| 8 | Логическое исключающее ИЛИ (XOR) | ^ |
Слева направо |
| 9 | Логическое ИЛИ (OR) | | |
Слева направо |
| 10 | Условное И | |
Слева направо |
| 11 | || |
Слева направо | |
| 12 | Условное выражение | ?: |
Справа налево |
| 13 | Присваивание | = *= /= %= += -= <<= >>= = ^= |= |
Справа налево |
Под Express, предназначенной для анализа
/// <summary>
/// Анализ выражений
/// </summary>
public void Express()
{
//перегрузка операций
byte b1 =1, b2 =2, b3;
short sh1;
int in1;
//b3 = b1 + b2; //ошибка: результат типа int
b3 = (byte)(b1+b2);
//sh1 = b1 + b2; //ошибка: результат типа int
sh1 = (short)(b1+b2);
in1 = b1+ b2 + sh1;
Console.WriteLine("b3= " + b3 + " sh1= "+ sh1 +" in1= " + in1);
}//Express
Разберем этот фрагмент. Начнем с первого закомментированного оператора присваивания b3 = b1+b2;. byte, казалось бы, и результат должен быть типа byte и без помех присвоен переменной b3. Однако это не так. Для данных типа byte нет перегруженной реализации сложения. Ближайшей операцией является сложение целых типа int. Поэтому оба операнда преобразуются к типу int, выполняется операция сложения, результат имеет тип int и не может быть неявно преобразован в тип byte, - возникает ошибка еще на этапе компиляции. Корректная запись показана в следующем операторе. Аналогичная ситуация возникает, когда в левой части оператора стоит переменная типа short, - и здесь необходимо явное int.
Давайте разберем, как в данном примере организован вывод в методе WriteLine. До сих пор при вызове метода задавалось несколько параметров и использовалась форма вывода данных с подстановкой значений параметров в строку, заданную первым параметром. Здесь же есть только один параметр - это строка, заданная сложным + ". Операнды сложения имеют разный тип: левый операнд имеет тип string, правый - арифметический ( byte, short, int ). В этом случае арифметический тип преобразуется к типу string и выполняется string вызывается метод ToString(), определенный для всех встроенных типов. Результатом этого WriteLine.
Полагаю, что разбор данного примера и материалы предыдущей лекции, где приводилась иерархия преобразований внутри арифметического типа и обсуждались вопросы выбора реализации перегруженного метода, дают необходимое представление о том, как работает
Вычисление (expr), определяются значения полей объекта - x.y, вычисляются функции - f(x), переменные с индексами - a[i]. Выполнение этих операций достаточно понятно и не нуждается в комментировании. Операции checked и unchecked включают и выключают проверку преобразований арифметического типа в
Операции x++ и x--. Префиксные операции имеют на единицу меньший x увеличивается ( ++ ) или уменьшается ( -- ) на единицу. Для префиксных ( ++x, --x ) операций результатом их выполнения является измененное значение x, постфиксные операции возвращают в качестве результата значение x до изменения. Приведу пример применения этих операций, дополнив метод Express новым фрагментом:
//операции increment и decrement
//Следующее выражение допустимо, но писать подобное никогда не следует
in1 = ++in1 +in1+ in1++;
//in2 = ++in1 + in1++ + in1;
Console.WriteLine(" in1= " + in1);
Обратите внимание, что хотя у постфиксной операции высший in1++, затем ++in1, и только потом будет проводиться сложение. Нет, вычисления проводятся в том порядке, в котором они написаны. Поскольку на входе значение in1 было равно 6, то
7(7) + 7 + 7(8),
где в скобках записан побочный эффект операции. Так что консольный вывод даст следующий результат:
in1 = 21
Операциями "
Разный |
/unsafe. На рис. 6.1 показано, как на странице свойств проекта можно включить это свойство:
(рис 6.1) Включение свойства /unsafe
Далее необходимо создать небезопасный блок, например, метод класса, помеченный как unsafe, в котором уже можно вызывать эту функцию (операцию). Приведу пример такого метода, созданного в классе Testing:
/// <summary>
/// определение размеров и типов
/// </summary>
unsafe public static void SizeMethod()
{
Console.WriteLine("Размер типа Boolean = " + sizeof(bool));
Console.WriteLine("Размер типа double = " + sizeof(double));
Console.WriteLine("Размер типа char = " + sizeof(System.Char));
int b1=1;
Type t = b1.GetType();
Console.WriteLine("Тип переменной b1: {0}", t);
//Console.WriteLine("Размер переменной b1: {0}", sizeof(t));
}//SizeMethod
В этом примере операция применяется к трем встроенным типам - bool, double, char. Консольный вывод дает в качестве результата значения: 1, 8 и 2. Обращаю внимание на то, что аргументом операции может быть только имя типа. Попытка применить эту операцию к переменной t типа Type, имеющей значение System.Int32, приводит к ошибке компиляции.
Type. К экземпляру класса применять операцию нельзя, но зато для экземпляра можно вызвать метод GetType, наследуемый всеми классами, и получить тот же результат, что дает int показан в приведенном выше программном фрагменте. А сейчас я приведу фрагмент, где используется вызов
t = typeof(Class1);
Console.WriteLine("Тип класса Class1: {0}", t);
t = typeof(Testing);
Console.WriteLine("Тип класса Testing: {0}", t);
Пожалуй, следует рассказать не только о том, как можно получить переменную типа Type, а и о том, что можно с этой переменной делать.
Этот и последующий раздел прерывают последовательное рассмотрение темы операций языка C#. Полагаю, понимание того, с какой целью выполняются те или иные операции, не менее важно, чем знание самой операции, и я не стал откладывать изложение этого материала на последующие лекции. |
Можно ли, зная тип (класс), получить подробную информацию обо всех методах и полях класса? Ясно, что такая информация может быть весьма полезной, если класс поставлен сторонней фирмой. Оказывается, это сделать нетрудно. Вся необходимая информация содержится в метаданных, поставляемых вместе с классом. Процесс получения метаданных называется отражением ( reflection ). Об отражении и метаданных уже говорилось в первой вводной лекции, и эта тема будет обсуждаться и далее. А сейчас я приведу пример, демонстрирующий получение подробной информации о методах и полях класса. Первым делом следует упростить в проекте использование классов пространства имен Reflection, добавив в начало проекта предложение
using System.Reflection;
В класс Testing я добавил существенно расширенный вариант метода WhoIsWho, который уже появлялся в наших примерах. Вот текст новой версии этой процедуры:
/// <summary>
/// Подробная информация о классе объекта, его значении,
/// методах класса, всех членов класса
/// </summary>
/// <param name="name">имя объекта</param>
/// <param name="any">объект любого типа</param>
public void WhoIsWho(string name,object any)
{
Type t = any.GetType();
Console.WriteLine("Тип {0}: {1} , значение: {2}",
name, any.GetType(), any.ToString());
Console.WriteLine("Методы класса:");
MethodInfo[] ClassMethods = t.GetMethods();
foreach (MethodInfo curMethod in ClassMethods)
{
Console.WriteLine(curMethod);
}
Console.WriteLine("Все члены класса:");
MemberInfo[] ClassMembers = t.GetMembers();
foreach (MemberInfo curMember in ClassMembers)
{
Console.WriteLine(curMember.ToString());
}
}//WhoIsWho
Коротко прокомментирую эту процедуру. Вначале создается переменная t типа Type. Значением этой переменной будет тип аргумента, переданного в процедуру в качестве значения параметра any. Напомню, any имеет базовый тип object и потому метод может быть вызван с аргументом, роль которого может играть t - GetMethods() и GetMembers(). Эти методы соответственно возвращают в качестве значений массивы элементов классов MethodInfo и MemberInfo. Эти классы содержатся в пространстве имен Reflection, они хранят информацию в первом случае о методах класса, во втором - о полях и методах класса, заданного переменной t. В пространстве имен Reflection имеются и другие классы, чьи методы и свойства полезны для получения дополнительной информации об исследуемом классе. Но я не буду сейчас столь подробно развивать эту тему.
В процедуре Main дважды вызывается процедура WhoIsWho. В первом вызове ее аргументом является int, во втором - сам объект ts, вызывающий метод:
ts.WhoIsWho("2+2", 2+2);
ts.WhoIsWho("ts", ts);
И класс int, и созданный в этом проекте класс Testing имеют довольно много методов. Имеют они и свойства. Процедура WhoIsWho выдаст подробную информацию обо всех элементах этих классов. Результаты консольного вывода, полученного при двух вызовах этой процедуры, показаны на рис. 6.2.
(рис 6.2) Информация о классах int и Testing, полученная в процедуре WhoIsWhoРассмотрим выводимую информацию о классах. Для созданного в проекте класса Testing отображается информация о полях и методах как собственных, так и наследуемых от общего родителя - класса Object. Заметьте, отображается информация только об открытых полях и методах класса, а поскольку поля нашего класса закрыты, то и информации о них нет.
Класс Int подробно обсуждался в предыдущей и в этой лекции. Все методы, которые могут вызывать переменные (объекты) класса Int, были уже рассмотрены. Тем не менее, из выводимой информации можно узнать и нечто новое, поскольку выдаются сведения и о статических полях и методах класса.
Все арифметические классы, в том числе класс Int, обладают двумя полезными полями (свойствами) - MinValue и MaxValue. Эти поля возвращают минимальное и максимальное значение, которое могут иметь экземпляры класса. Поля являются статическими и потому недоступны для экземпляров класса и могут быть вызваны только при указании имени класса. Разумно привести пример вызова этих полей для класса Int и, например, для класса Double:
//Min и Max значения типов
Console.WriteLine("Class int");
Console.WriteLine("Мин. значение int = " + int.MinValue);
Console.WriteLine("Макс. значение int = " + int.MaxValue);
Console.WriteLine("Class double");
Console.WriteLine("Мин. значение double = " + double.MinValue);
Console.WriteLine("Макс. значение double = " + double.MaxValue);
Все арифметические классы, в том числе класс Int, обладают перегруженным статическим Int и Double:
/// <summary>
/// Преобразования типа с использованием метода Parse
/// </summary>
public void Parsing()
{
//method Parse
Console.WriteLine("Введите целое");
string strdata = Console.ReadLine();
int intdata = int.Parse(strdata);
Console.WriteLine("Введите число с дробной частью и порядком");
strdata = Console.ReadLine();
double doubdata = double.Parse(strdata);
Console.WriteLine("intdata = {0}; doubdata = {1}",
intdata, doubdata);
}
//Parsing
Как видите, Convert.
На рис. 6.3 можно увидеть консольный вывод, полученный в результате работы процедуры Parsing.
(рис 6.3) Результаты работы процедуры Parsing
Пора вернуться к основной теме - операциям, допустимым в языке C#. Последней из еще не рассмотренных операций высшего уровня new используется в двух контекстах - как модификатор и как операция в инициализирующих
В языке C# имеются обычные для всех языков +, -, *, /, % ". Все они перегружены. Операции " + " и " - " могут быть унарными и бинарными. Операция деления " / " над целыми типами осуществляет деление нацело, для типов с плавающей и фиксированной точкой - обычное деление. Операция " % " определена над всеми арифметическими типами и возвращает остаток от деления нацело. Тип результата зависит от типов операндов. Приведу пример вычислений с различными арифметическими типами:
/// <summary>
/// Арифметические операции
/// </summary>
public void Ariphmetica()
{
int n = 7,m =3, p,q;
p= n/m; q= p*m + n%m;
if (q==n) Console.WriteLine("q=n");
else Console.WriteLine("q!=n");
double x=7, y =3, u,v,w;
u = x/y; v= u*y;
w= x%y;
if (v==x) Console.WriteLine("v=x");
else Console.WriteLine("v!=x");
decimal d1=7, d2 =3, d3,d4,d5;
d3 = d1/d2; d4= d3*d2;
d5= d1%d2;
if (d4==d1) Console.WriteLine("d4=d1");
else Console.WriteLine("d4!=d1");
}//Ariphmetica
При проведении вычислений в первых двух случаях проверяемое условие оказалось истинным, в третьем - ложным. Для целых типов можно исходить из того, что равенство n = n/m*m + n%m истинно. Для типов с плавающей точкой выполнение точного равенства x = x/y*y следует считать скорее случайным, а не закономерным событием. Законно невыполнение этого равенства, как это имеет место при вычислениях с фиксированной точкой.
==, !=, <, >, <=, >= ). Для тех, кто не привык работать с языком C++, стоит обратить внимание на запись операций "равно" и "не равно".
Операции проверки типов is и as будут рассмотрены в последующих лекциях. (см. лекцию 19).
>> " и сдвига влево " << " в обычных вычислениях применяются редко. Они особенно полезны, если данные рассматриваются как строка битов. Результатом операции является сдвиг строки битов влево или вправо на K разрядов. В применении к обычным целым положительным числам сдвиг вправо равносилен делению нацело на 2K, а сдвиг влево - умножению на 2K. Для отрицательных чисел сдвиг влево и деление дают разные результаты, отличающиеся на единицу. В языке C# int, uint, long, ulong. Величина сдвига должна иметь тип int. Вот пример применения этих операций:
/// <summary>
/// операции сдвига
/// </summary>
public void Shift()
{
int n = 17,m =3, p,q;
p= n>>2; q = m<<2;
Console.WriteLine("n= " + n + "; m= " + m + "; p=n>>2 = "+p
+ "; q=m<<2 " + q);
long x=-75, y =-333, u,v,w;
u = x>>2; v = y<<2; w = x/4;
Console.WriteLine("x= " + x + "; y= " + y + "; u=x>>2 = "+u
+ "; v=y<<2 " + v + "; w = x/4 = " + w);
}//Shift
Начну с предупреждения тем, кто привык к языку C++. Правила работы с
int k1 = 7;
if (k1) Console.WriteLine("ok!");
незаконна в программах на C#. На этапе трансляции возникнет ошибка, поскольку вычисляемое условие имеет тип int, а неявное преобразование этого типа к типу bool отсутствует.
В языке C# более строгие правила действуют и для
if(k1 (x>y)),
корректная в языке C++, приводит к ошибке в программах на C#, поскольку операция определена только для операндов типа bool, а в данном int. В языке C# в данных ситуациях следует использовать записи:
if(k1!=0) if((k1>0) (x>y))
После этого важного предупреждения перейду к более систематическому изложению некоторых особенностей выполнения ~ ". Первая из них определена над операндом типа bool, вторая - над операндом целочисленного типа, начиная с типа int и выше ( int, uint, long, ulong ). Результатом операции во втором случае является операнд, в котором каждый бит заменен его дополнением. Приведу пример:
/// <summary>
/// Логические выражения
/// </summary>
public void Logic()
{
//операции отрицания ~,!
bool b1,b2;
b1 = 2*2==4; b2 =!b1;
//b2= ~b1;
uint j1 =7, j2;
j2= ~j1;
//j2 = !j1;
int j4 = 7, j5;
j5 = ~j4;
Console.WriteLine("uint j2 = " + j2 +" int j5 = " + j5);
}//Logic
В этом фрагменте закомментированы операторы, приводящие к ошибкам. В первом случае была сделана попытка применения операции побитового отрицания к bool, во втором - логическое отрицание применялось к целочисленным данным. И то, и другое в C# незаконно. Обратите внимание на разную интерпретацию побитового отрицания для беззнаковых и знаковых целочисленных типов. Для переменных j5 и j2 строка битов, задающая значение - одна и та же, но интерпретируется по-разному. Соответствующий вывод таков:
uint j2 = 4294967288 int j5 = -8
Бинарные - условное И " и " || - условное ИЛИ " определены только над данными типа bool. Операции называются условными или краткими, поскольку, будет ли вычисляться второй операнд, зависит от уже вычисленного значения первого операнда. В операции " ", если первый операнд равен значению false, то второй операнд не вычисляется и результат операции равен false. Аналогично, в операции " || ", если первый операнд равен значению true, то второй операнд не вычисляется и результат операции равен true. Ценность
//Условное And -
int[] ar= {1,2,3};
int search = 7; int i=0;
while ((i < ar.Length) (ar[i]!= search)) i++;
if(i<ar.Length) Console.WriteLine("Образец найден");
else Console.WriteLine("Образец не найден");
Если значение переменной search (образца) не совпадает ни с одним из значений элементов массива ar, то последняя проверка условия цикла while будет выполняться при значении i, равном ar.Length. В этом случае первый операнд получит значение false, и, хотя второй операнд при этом не определен, цикл нормально завершит свою работу. Второй операнд не определен в последней проверке, поскольку индекс элемента массива выходит за допустимые пределы (в C# индексация элементов начинается с нуля). Заметьте, что "нормальная" конъюнкция требует вычисления обеих операндов, поэтому ее применение в данной программе приводило бы к выбросу исключения в случае, когда образца нет в массиве.
Три бинарные побитовые операции - " - AND " , " | - OR ", " ^ - XOR " используются двояко. Они определены как над целыми типами выше int, так и над булевыми типами. В первом случае они используются как побитовые операции, во втором - как обычные
//Логические побитовые операции And, Or, XOR (,|,^)
int k2 = 7, k3 = 5, k4, k5, k6;
k4 = k2 k3; k5 = k2| k3; k6 = k2^k3;
Console.WriteLine("k4 = " + k4 + " k5 = " + k5 + " k6 = " + k6);
Приведу результаты вывода:
k4 = 5 k5 = 7 k6 =2
Приведу пример поиска по образцу с использованием логического AND:
i=0; search = ar[ar.Length - 1];
while ((i < ar.Length) (ar[i]!= search)) i++;
if(i<ar.Length) Console.WriteLine("Образец найден");
else Console.WriteLine("Образец не найден");
В данном фрагменте гарантируется наличие образца поиска в массиве, и фрагмент будет успешно выполнен. В тех же случаях, когда массив не содержит элемента search, будет выброшено исключение. Содержательный смысл такой процедуры - появление исключения - может быть признаком ошибки в данных, что требует специальной обработки ситуации.
В C#, как и в C++, разрешены
//Условное выражение
int a = 7, b= 9, max;
max= (a>b) ? a:b;
Console.WriteLine("a = " + a + "; b= " + b +
"; max(a,b) = " + max);
bool. Если оно истинно, то из пары max получит значение 9.
Осталось рассмотреть еще одну операцию -
(type) <унарное выражение>
Она задает явное преобразование типа, определенного bool. При определении пользовательских типов для них могут быть заданы явные преобразования в другие, в том числе встроенные, типы. О явных преобразованиях говорилось достаточно много, приводились и примеры. Поэтому ограничусь совсем простым примером:
//cast int p; double x=3.14159; p = (int)x; //b = (bool)x;
В данном примере явное преобразование из типа double в тип int выполняется, а преобразование double в тип bool приводит к ошибке, потому и закомментировано.
Программист, записывающий
Большинство операций в языке C#, их checked и unchecked, применимые к
Как это обычно делается, приведем
| | Категория | Операции | Порядок |
|---|---|---|---|
| 0 | Первичные | (expr) x.y f(x) a[x] x++ x-- new sizeof(t) typeof(t) checked(expr) unchecked(expr) |
Слева направо |
| 1 | Унарные | + - ! ~ ++x --x (T)x |
Слева направо |
| 2 | Мультипликативные (Умножение) | * / % |
Слева направо |
| 3 | Аддитивные (Сложение) | + - |
Слева направо |
| 4 | << >> |
Слева направо | |
| 5 | < > <= >= is as |
Слева направо | |
| 6 | Эквивалентность | == != |
Слева направо |
| 7 | Логическое И | |
Слева направо |
| 8 | Логическое исключающее ИЛИ (XOR) | ^ |
Слева направо |
| 9 | Логическое ИЛИ (OR) | | |
Слева направо |
| 10 | Условное И | |
Слева направо |
| 11 | || |
Слева направо | |
| 12 | Условное выражение | ?: |
Справа налево |
| 13 | Присваивание | = *= /= %= += -= <<= >>= = ^= |= |
Справа налево |
Под Express, предназначенной для анализа
/// <summary>
/// Анализ выражений
/// </summary>
public void Express()
{
//перегрузка операций
byte b1 =1, b2 =2, b3;
short sh1;
int in1;
//b3 = b1 + b2; //ошибка: результат типа int
b3 = (byte)(b1+b2);
//sh1 = b1 + b2; //ошибка: результат типа int
sh1 = (short)(b1+b2);
in1 = b1+ b2 + sh1;
Console.WriteLine("b3= " + b3 + " sh1= "+ sh1 +" in1= " + in1);
}//Express
Разберем этот фрагмент. Начнем с первого закомментированного оператора присваивания b3 = b1+b2;. byte, казалось бы, и результат должен быть типа byte и без помех присвоен переменной b3. Однако это не так. Для данных типа byte нет перегруженной реализации сложения. Ближайшей операцией является сложение целых типа int. Поэтому оба операнда преобразуются к типу int, выполняется операция сложения, результат имеет тип int и не может быть неявно преобразован в тип byte, - возникает ошибка еще на этапе компиляции. Корректная запись показана в следующем операторе. Аналогичная ситуация возникает, когда в левой части оператора стоит переменная типа short, - и здесь необходимо явное int.
Давайте разберем, как в данном примере организован вывод в методе WriteLine. До сих пор при вызове метода задавалось несколько параметров и использовалась форма вывода данных с подстановкой значений параметров в строку, заданную первым параметром. Здесь же есть только один параметр - это строка, заданная сложным + ". Операнды сложения имеют разный тип: левый операнд имеет тип string, правый - арифметический ( byte, short, int ). В этом случае арифметический тип преобразуется к типу string и выполняется string вызывается метод ToString(), определенный для всех встроенных типов. Результатом этого WriteLine.
Полагаю, что разбор данного примера и материалы предыдущей лекции, где приводилась иерархия преобразований внутри арифметического типа и обсуждались вопросы выбора реализации перегруженного метода, дают необходимое представление о том, как работает
Вычисление (expr), определяются значения полей объекта - x.y, вычисляются функции - f(x), переменные с индексами - a[i]. Выполнение этих операций достаточно понятно и не нуждается в комментировании. Операции checked и unchecked включают и выключают проверку преобразований арифметического типа в
Операции x++ и x--. Префиксные операции имеют на единицу меньший x увеличивается ( ++ ) или уменьшается ( -- ) на единицу. Для префиксных ( ++x, --x ) операций результатом их выполнения является измененное значение x, постфиксные операции возвращают в качестве результата значение x до изменения. Приведу пример применения этих операций, дополнив метод Express новым фрагментом:
//операции increment и decrement
//Следующее выражение допустимо, но писать подобное никогда не следует
in1 = ++in1 +in1+ in1++;
//in2 = ++in1 + in1++ + in1;
Console.WriteLine(" in1= " + in1);
Обратите внимание, что хотя у постфиксной операции высший in1++, затем ++in1, и только потом будет проводиться сложение. Нет, вычисления проводятся в том порядке, в котором они написаны. Поскольку на входе значение in1 было равно 6, то
7(7) + 7 + 7(8),
где в скобках записан побочный эффект операции. Так что консольный вывод даст следующий результат:
in1 = 21
Операциями "
Разный |
/unsafe. На рис. 6.1 показано, как на странице свойств проекта можно включить это свойство:
(рис 6.1) Включение свойства /unsafe
Далее необходимо создать небезопасный блок, например, метод класса, помеченный как unsafe, в котором уже можно вызывать эту функцию (операцию). Приведу пример такого метода, созданного в классе Testing:
/// <summary>
/// определение размеров и типов
/// </summary>
unsafe public static void SizeMethod()
{
Console.WriteLine("Размер типа Boolean = " + sizeof(bool));
Console.WriteLine("Размер типа double = " + sizeof(double));
Console.WriteLine("Размер типа char = " + sizeof(System.Char));
int b1=1;
Type t = b1.GetType();
Console.WriteLine("Тип переменной b1: {0}", t);
//Console.WriteLine("Размер переменной b1: {0}", sizeof(t));
}//SizeMethod
В этом примере операция применяется к трем встроенным типам - bool, double, char. Консольный вывод дает в качестве результата значения: 1, 8 и 2. Обращаю внимание на то, что аргументом операции может быть только имя типа. Попытка применить эту операцию к переменной t типа Type, имеющей значение System.Int32, приводит к ошибке компиляции.
Type. К экземпляру класса применять операцию нельзя, но зато для экземпляра можно вызвать метод GetType, наследуемый всеми классами, и получить тот же результат, что дает int показан в приведенном выше программном фрагменте. А сейчас я приведу фрагмент, где используется вызов
t = typeof(Class1);
Console.WriteLine("Тип класса Class1: {0}", t);
t = typeof(Testing);
Console.WriteLine("Тип класса Testing: {0}", t);
Пожалуй, следует рассказать не только о том, как можно получить переменную типа Type, а и о том, что можно с этой переменной делать.
Этот и последующий раздел прерывают последовательное рассмотрение темы операций языка C#. Полагаю, понимание того, с какой целью выполняются те или иные операции, не менее важно, чем знание самой операции, и я не стал откладывать изложение этого материала на последующие лекции. |
Можно ли, зная тип (класс), получить подробную информацию обо всех методах и полях класса? Ясно, что такая информация может быть весьма полезной, если класс поставлен сторонней фирмой. Оказывается, это сделать нетрудно. Вся необходимая информация содержится в метаданных, поставляемых вместе с классом. Процесс получения метаданных называется отражением ( reflection ). Об отражении и метаданных уже говорилось в первой вводной лекции, и эта тема будет обсуждаться и далее. А сейчас я приведу пример, демонстрирующий получение подробной информации о методах и полях класса. Первым делом следует упростить в проекте использование классов пространства имен Reflection, добавив в начало проекта предложение
using System.Reflection;
В класс Testing я добавил существенно расширенный вариант метода WhoIsWho, который уже появлялся в наших примерах. Вот текст новой версии этой процедуры:
/// <summary>
/// Подробная информация о классе объекта, его значении,
/// методах класса, всех членов класса
/// </summary>
/// <param name="name">имя объекта</param>
/// <param name="any">объект любого типа</param>
public void WhoIsWho(string name,object any)
{
Type t = any.GetType();
Console.WriteLine("Тип {0}: {1} , значение: {2}",
name, any.GetType(), any.ToString());
Console.WriteLine("Методы класса:");
MethodInfo[] ClassMethods = t.GetMethods();
foreach (MethodInfo curMethod in ClassMethods)
{
Console.WriteLine(curMethod);
}
Console.WriteLine("Все члены класса:");
MemberInfo[] ClassMembers = t.GetMembers();
foreach (MemberInfo curMember in ClassMembers)
{
Console.WriteLine(curMember.ToString());
}
}//WhoIsWho
Коротко прокомментирую эту процедуру. Вначале создается переменная t типа Type. Значением этой переменной будет тип аргумента, переданного в процедуру в качестве значения параметра any. Напомню, any имеет базовый тип object и потому метод может быть вызван с аргументом, роль которого может играть t - GetMethods() и GetMembers(). Эти методы соответственно возвращают в качестве значений массивы элементов классов MethodInfo и MemberInfo. Эти классы содержатся в пространстве имен Reflection, они хранят информацию в первом случае о методах класса, во втором - о полях и методах класса, заданного переменной t. В пространстве имен Reflection имеются и другие классы, чьи методы и свойства полезны для получения дополнительной информации об исследуемом классе. Но я не буду сейчас столь подробно развивать эту тему.
В процедуре Main дважды вызывается процедура WhoIsWho. В первом вызове ее аргументом является int, во втором - сам объект ts, вызывающий метод:
ts.WhoIsWho("2+2", 2+2);
ts.WhoIsWho("ts", ts);
И класс int, и созданный в этом проекте класс Testing имеют довольно много методов. Имеют они и свойства. Процедура WhoIsWho выдаст подробную информацию обо всех элементах этих классов. Результаты консольного вывода, полученного при двух вызовах этой процедуры, показаны на рис. 6.2.
(рис 6.2) Информация о классах int и Testing, полученная в процедуре WhoIsWhoРассмотрим выводимую информацию о классах. Для созданного в проекте класса Testing отображается информация о полях и методах как собственных, так и наследуемых от общего родителя - класса Object. Заметьте, отображается информация только об открытых полях и методах класса, а поскольку поля нашего класса закрыты, то и информации о них нет.
Класс Int подробно обсуждался в предыдущей и в этой лекции. Все методы, которые могут вызывать переменные (объекты) класса Int, были уже рассмотрены. Тем не менее, из выводимой информации можно узнать и нечто новое, поскольку выдаются сведения и о статических полях и методах класса.
Все арифметические классы, в том числе класс Int, обладают двумя полезными полями (свойствами) - MinValue и MaxValue. Эти поля возвращают минимальное и максимальное значение, которое могут иметь экземпляры класса. Поля являются статическими и потому недоступны для экземпляров класса и могут быть вызваны только при указании имени класса. Разумно привести пример вызова этих полей для класса Int и, например, для класса Double:
//Min и Max значения типов
Console.WriteLine("Class int");
Console.WriteLine("Мин. значение int = " + int.MinValue);
Console.WriteLine("Макс. значение int = " + int.MaxValue);
Console.WriteLine("Class double");
Console.WriteLine("Мин. значение double = " + double.MinValue);
Console.WriteLine("Макс. значение double = " + double.MaxValue);
Все арифметические классы, в том числе класс Int, обладают перегруженным статическим Int и Double:
/// <summary>
/// Преобразования типа с использованием метода Parse
/// </summary>
public void Parsing()
{
//method Parse
Console.WriteLine("Введите целое");
string strdata = Console.ReadLine();
int intdata = int.Parse(strdata);
Console.WriteLine("Введите число с дробной частью и порядком");
strdata = Console.ReadLine();
double doubdata = double.Parse(strdata);
Console.WriteLine("intdata = {0}; doubdata = {1}",
intdata, doubdata);
}
//Parsing
Как видите, Convert.
На рис. 6.3 можно увидеть консольный вывод, полученный в результате работы процедуры Parsing.
(рис 6.3) Результаты работы процедуры Parsing
Пора вернуться к основной теме - операциям, допустимым в языке C#. Последней из еще не рассмотренных операций высшего уровня new используется в двух контекстах - как модификатор и как операция в инициализирующих
В языке C# имеются обычные для всех языков +, -, *, /, % ". Все они перегружены. Операции " + " и " - " могут быть унарными и бинарными. Операция деления " / " над целыми типами осуществляет деление нацело, для типов с плавающей и фиксированной точкой - обычное деление. Операция " % " определена над всеми арифметическими типами и возвращает остаток от деления нацело. Тип результата зависит от типов операндов. Приведу пример вычислений с различными арифметическими типами:
/// <summary>
/// Арифметические операции
/// </summary>
public void Ariphmetica()
{
int n = 7,m =3, p,q;
p= n/m; q= p*m + n%m;
if (q==n) Console.WriteLine("q=n");
else Console.WriteLine("q!=n");
double x=7, y =3, u,v,w;
u = x/y; v= u*y;
w= x%y;
if (v==x) Console.WriteLine("v=x");
else Console.WriteLine("v!=x");
decimal d1=7, d2 =3, d3,d4,d5;
d3 = d1/d2; d4= d3*d2;
d5= d1%d2;
if (d4==d1) Console.WriteLine("d4=d1");
else Console.WriteLine("d4!=d1");
}//Ariphmetica
При проведении вычислений в первых двух случаях проверяемое условие оказалось истинным, в третьем - ложным. Для целых типов можно исходить из того, что равенство n = n/m*m + n%m истинно. Для типов с плавающей точкой выполнение точного равенства x = x/y*y следует считать скорее случайным, а не закономерным событием. Законно невыполнение этого равенства, как это имеет место при вычислениях с фиксированной точкой.
==, !=, <, >, <=, >= ). Для тех, кто не привык работать с языком C++, стоит обратить внимание на запись операций "равно" и "не равно".
Операции проверки типов is и as будут рассмотрены в последующих лекциях. (см. лекцию 19).
>> " и сдвига влево " << " в обычных вычислениях применяются редко. Они особенно полезны, если данные рассматриваются как строка битов. Результатом операции является сдвиг строки битов влево или вправо на K разрядов. В применении к обычным целым положительным числам сдвиг вправо равносилен делению нацело на 2K, а сдвиг влево - умножению на 2K. Для отрицательных чисел сдвиг влево и деление дают разные результаты, отличающиеся на единицу. В языке C# int, uint, long, ulong. Величина сдвига должна иметь тип int. Вот пример применения этих операций:
/// <summary>
/// операции сдвига
/// </summary>
public void Shift()
{
int n = 17,m =3, p,q;
p= n>>2; q = m<<2;
Console.WriteLine("n= " + n + "; m= " + m + "; p=n>>2 = "+p
+ "; q=m<<2 " + q);
long x=-75, y =-333, u,v,w;
u = x>>2; v = y<<2; w = x/4;
Console.WriteLine("x= " + x + "; y= " + y + "; u=x>>2 = "+u
+ "; v=y<<2 " + v + "; w = x/4 = " + w);
}//Shift
Начну с предупреждения тем, кто привык к языку C++. Правила работы с
int k1 = 7;
if (k1) Console.WriteLine("ok!");
незаконна в программах на C#. На этапе трансляции возникнет ошибка, поскольку вычисляемое условие имеет тип int, а неявное преобразование этого типа к типу bool отсутствует.
В языке C# более строгие правила действуют и для
if(k1 (x>y)),
корректная в языке C++, приводит к ошибке в программах на C#, поскольку операция определена только для операндов типа bool, а в данном int. В языке C# в данных ситуациях следует использовать записи:
if(k1!=0) if((k1>0) (x>y))
После этого важного предупреждения перейду к более систематическому изложению некоторых особенностей выполнения ~ ". Первая из них определена над операндом типа bool, вторая - над операндом целочисленного типа, начиная с типа int и выше ( int, uint, long, ulong ). Результатом операции во втором случае является операнд, в котором каждый бит заменен его дополнением. Приведу пример:
/// <summary>
/// Логические выражения
/// </summary>
public void Logic()
{
//операции отрицания ~,!
bool b1,b2;
b1 = 2*2==4; b2 =!b1;
//b2= ~b1;
uint j1 =7, j2;
j2= ~j1;
//j2 = !j1;
int j4 = 7, j5;
j5 = ~j4;
Console.WriteLine("uint j2 = " + j2 +" int j5 = " + j5);
}//Logic
В этом фрагменте закомментированы операторы, приводящие к ошибкам. В первом случае была сделана попытка применения операции побитового отрицания к bool, во втором - логическое отрицание применялось к целочисленным данным. И то, и другое в C# незаконно. Обратите внимание на разную интерпретацию побитового отрицания для беззнаковых и знаковых целочисленных типов. Для переменных j5 и j2 строка битов, задающая значение - одна и та же, но интерпретируется по-разному. Соответствующий вывод таков:
uint j2 = 4294967288 int j5 = -8
Бинарные - условное И " и " || - условное ИЛИ " определены только над данными типа bool. Операции называются условными или краткими, поскольку, будет ли вычисляться второй операнд, зависит от уже вычисленного значения первого операнда. В операции " ", если первый операнд равен значению false, то второй операнд не вычисляется и результат операции равен false. Аналогично, в операции " || ", если первый операнд равен значению true, то второй операнд не вычисляется и результат операции равен true. Ценность
//Условное And -
int[] ar= {1,2,3};
int search = 7; int i=0;
while ((i < ar.Length) (ar[i]!= search)) i++;
if(i<ar.Length) Console.WriteLine("Образец найден");
else Console.WriteLine("Образец не найден");
Если значение переменной search (образца) не совпадает ни с одним из значений элементов массива ar, то последняя проверка условия цикла while будет выполняться при значении i, равном ar.Length. В этом случае первый операнд получит значение false, и, хотя второй операнд при этом не определен, цикл нормально завершит свою работу. Второй операнд не определен в последней проверке, поскольку индекс элемента массива выходит за допустимые пределы (в C# индексация элементов начинается с нуля). Заметьте, что "нормальная" конъюнкция требует вычисления обеих операндов, поэтому ее применение в данной программе приводило бы к выбросу исключения в случае, когда образца нет в массиве.
Три бинарные побитовые операции - " - AND " , " | - OR ", " ^ - XOR " используются двояко. Они определены как над целыми типами выше int, так и над булевыми типами. В первом случае они используются как побитовые операции, во втором - как обычные
//Логические побитовые операции And, Or, XOR (,|,^)
int k2 = 7, k3 = 5, k4, k5, k6;
k4 = k2 k3; k5 = k2| k3; k6 = k2^k3;
Console.WriteLine("k4 = " + k4 + " k5 = " + k5 + " k6 = " + k6);
Приведу результаты вывода:
k4 = 5 k5 = 7 k6 =2
Приведу пример поиска по образцу с использованием логического AND:
i=0; search = ar[ar.Length - 1];
while ((i < ar.Length) (ar[i]!= search)) i++;
if(i<ar.Length) Console.WriteLine("Образец найден");
else Console.WriteLine("Образец не найден");
В данном фрагменте гарантируется наличие образца поиска в массиве, и фрагмент будет успешно выполнен. В тех же случаях, когда массив не содержит элемента search, будет выброшено исключение. Содержательный смысл такой процедуры - появление исключения - может быть признаком ошибки в данных, что требует специальной обработки ситуации.
В C#, как и в C++, разрешены
//Условное выражение
int a = 7, b= 9, max;
max= (a>b) ? a:b;
Console.WriteLine("a = " + a + "; b= " + b +
"; max(a,b) = " + max);
bool. Если оно истинно, то из пары max получит значение 9.
Осталось рассмотреть еще одну операцию -
(type) <унарное выражение>
Она задает явное преобразование типа, определенного bool. При определении пользовательских типов для них могут быть заданы явные преобразования в другие, в том числе встроенные, типы. О явных преобразованиях говорилось достаточно много, приводились и примеры. Поэтому ограничусь совсем простым примером:
//cast int p; double x=3.14159; p = (int)x; //b = (bool)x;
В данном примере явное преобразование из типа double в тип int выполняется, а преобразование double в тип bool приводит к ошибке, потому и закомментировано.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.