Продвинутое программирование.
Поможем в ✍️ написании учебной работы
Поможем с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой

Структуры, индексаторы, тип var, шаблоны,условная компиляция,обработка исключительных ситуаций (блоки try… except и try … finally ), делегаты.

В этой лекции собраны конструкции языка, о которых хорошо бы знать. Их использование может существенно улучшить программу. Однако нехватка времени и объема прочитанного материала не позволяет долго останавливаться на этих конструкциях. Может быть при первом знакомстве с C# этот материалследует пропустить. Более подробно о некоторых из них планируется рассказать в третьей части пособия.

Структуры

Структура – это тип языка C#, в котором под одним именем объединены несколько данных разных типов. Может показаться, что структура дублирует класс, только без методов, тем более, что к элементам структуры можно применять уровни доступа и у нее есть конструктор. С одной стороны это так, а с другой стороны возможно ине так. Дело в том, что этот тип может работать и как ссылочный тип (инициализация с помощью оператора new и с вызовом конструктора), и как значимый тип (без инициализации с помощью оператора new и без вызова конструктора). В чем разница. Первое – память под структурубудет захватываться в разных местах ОП. Если структура используется как значимый тип, то память захватывается в стеке и элементы не получают никаких значений. Здесь можно вспомнить такое сообщение при попытке компиляции как «Возможно использование неназначенного поля». Если же используется оператор new, то значения всех элементов структуры получают либо нулевые значения, либо пустые ссылки (null), либо значения заданные в конструкторе, который вызывается при инициализации. Кроме этого память при такой инициализации захватывается в области ОП, которая называется динамическая память. Стек и динамическая память – это разные места ОП. Они отличаются по объему, по способу доступа. Объем стека существенно меньше, но скорость доступа к нему выше, чем к динамической памяти. Объем динамической памяти – это, по сути, весь объем доступной ОП компьютера, но скорость доступа к ней меньше. Какой способ применить, зависит от задачи и от объема данных, с которыми планируется работать, используя структуры.

Ну а сейчас нужен пример работы со структурой. Ну допустим так: создадим структуру, в которой будут храниться сведения о студенте:фамилия, имя, отчество (ФИО), год рождения, пол. Представляется, что поля структуры Stud из примера 11.1 не требуют пояснений. В Main()-е приведены различные варианты работы с этой структурой.

 

ClassProgram

{

 

structStud

   {

publicstring FIO;

publicint gr;

publicchar sex;

   }

 

staticvoid Main(string[] args)

{

Studst; // структура без инициализации

st.FIO=»Вася»;

Console.WriteLine(«Без инициализации FIO=*»+st.FIO+»*»);

// Console.WriteLine(“*” + st.FIO+”*”+st.gr+”*”+st.sex);

//Ошибка компиляции «Использование поля gr, которому, возможно, не присвоено значение «

//Ошибка компиляции «Использование поля sex, которому, возможно, не присвоено значение «

Stud st1; //структура с инициализацией

       st1 = newStud();

Console.WriteLine(“Синициализацией FIO=*”+st1.FIO+”*”);

Console.WriteLine(“*” + st1.FIO+”*”+st1.gr+”*”+st1.sex+”*”);

 

Console.ReadLine();

   }

}

 

Пример 11.1. Работа со структурами.

 

 

 

Рис.11.1

Демонстрация работы со структурой.

 

 

Индексаторы

Индексатор это свойство (имеет get{} и set{}, см. лекцию 6, пункт 6.3 первой части пособия), но с параметрами, которые записываются в квадратных скобках. Наличие квадратных скобок делает работу с индексаторами похожей на работу с элементами массива. Это удобно, если данные в составе класса организованы или в виде массива, или в каком-либо другом виде, главное, чтобы обратиться к каждому элементу или вычислить его значение можно было с использованием этих самых параметров.

У индексатора должно быть имя this и никакого другого.

В чем отличие индексатора от свойства, которое отвечает за закрытое поле-массив. Соответствующее свойство оперирует с массивом как с единым целым (см. описание поля int[] masи свойства int[] Masиз пример 8.2). Очевидно, что это только «посредник» между закрытым полем и внешним миром и ни о каком контроле за значениями элементов массива говорить не приходится. В аксессорах же (напоминаем, что так называютсяметоды get{} и set{} свойства) индексатораможно организовать контроль за параметрами и значениями поля. Кроме этого, использование индексатора позволяет работать с любым объектом как с массивом, что бывает иногда просто удобнее.

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

namespace ConsoleApplication1

{

classMasIndthis

{

privateint[] a;//массив

publicint Length;//длинамассива

publicboolErrFl; //результат последней операции

 

publicMasIndthis(int size) // конструктор. Инициализациямассива

   { a = newint[size];

       Length = size;

}

Private bool Ok(intindex)// возвращает true если индекс в границах массива

{ boolfl;

if (index >= 0 && index < Length)

          fl=true;

else

           fl=false;

returnfl;

   }

 

]// это и есть индексатор класса MasIndthis

Public int this[int index]

   { get//акссесор

       { if (Ok(index))

           { ErrFl = false;

               return a[index];

           }

           else

           { ErrFl = true;

               Return int.MinValue;

           }

       }

       set

       { if (Ok(index))

           {

               ErrFl = false;

               a[index]=value;

           }

           else

           {

               ErrFl = true;

           }

       }

   }

}

 

classProgram

{

staticvoid Main(string[] args)

   {

MasIndthis mit = new MasIndthis(5);

Console.WriteLine("Скрытыйсбой");

for (inti = 0; i<mit.Length * 2; i++)

mit[i] = i * 10; // использованиеиндексатора(this[])

 

for (inti = 0; i<mit.Length * 2; i++)

Console.Write(mit[i] + " ");

Console.WriteLine();

Console.WriteLine("Сбой с уведомлением");

Console.WriteLine("Уведомление при формировании");

for (inti = 0; i<mit.Length * 2; i++)

{

mit[i] = i * 10; // использование индексатора(this[])

if (mit.ErrFl)

Console.WriteLine("a["+i+"]= внеграниц ");

}

Console.WriteLine("Уведомление при чтении");

for (inti = 0; i<mit.Length * 2; i++)

       {

int x=mit[i];

if (!mit.ErrFl)

Console.Write(x+" ");

else

Console.WriteLine("a[" + i + "]= внеграниц ");

}

Console.ReadLine();

   }

}

}

 

Пример 11.2

Рис.11.2. Результаты работы программы из примера 11.2.

Использование индексатора для работы с массивом

 

Код индексатора просит пояснений.

Индексатор начинается со строки

publicintthis[intindex].

 

public–это чтобы к индексаторы можно было обратиться извне;

int–тип элемента массива;

this[intindex] – служебноесловоthis. При обращении к индексатору это слово будет заменяться на имя текущего объекта; index– параметр индексатора.

В теле индексатора определены два аксессора. Акссесоры вызываются автоматически при использовании индексатора и оба получают параметрindex. Если индексатор стоит в левой части оператора присваивания, то вызывается аксессорset(), в иных случаях вызывается get().

Ну а проверка index-а на границы массива в индексаторе реализуется с использованием метода Ok(), ав поле ErrFl лежит результат последней операции с массивом в индексаторе. Кажется, что этих пояснений к примеру 11.2 достаточно.

Индексаторы можно перегружать. В этом случае они должны отличаться списком параметров. Далее, можно организовывать индексаторы с несколькими параметрами. Если в качестве параметров передать два индекса, то можно реализовать работу с двумерным массивом. Более того, иногда целесообразно использовать индексаторы даже в том случае, если данные даже не лежат в массиве, но в ним удобно обращаться как к элементам массива.

 

Тип var

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

vara=10;

vars=”строка”;

varv[] = {10,20,30};

 

Кажется, что в программировании такое уже было. В языках типа Basic можно было вообще не описывать переменные. Их типы определялись при первом использование переменной в программе, а это, конечно же, присваивание.

Увлекаться таким типом не надо.

Однако хорошим примером может служить использование переменной типа varв перечислении.

foreach (var x in массив)

x.ToString;

Шаблоны

Шаблоны – это очень мощное средство, которое спасает программистов от написания бессмысленного кода в случае, когда надо выполнять одни и те же операции над разными типами данных. Рассмотрим пример,в котором создадим шаблон простого метода.

Static string sum<T>(T value1, T value2)

{

return value1.ToString() + value2.ToString();

}

 

Поясним: static string sum это как в обычном методе. А дальше в треугольных скобках пишется какая-нибудь строка. Как правило используется буква <T>. При вызове этого метода с треугольных скобках вместо T будет записан тот тип данных, который будет использоваться в работе метода. Далее в описании метода в круглых скобках пишутся параметрыс использованием какого-то типа T, который будет определен на этапевызова метода.

Почему T ? Скорее всего от английского Template (шаблон). В принципе можно и по-другому, но так уж принято.

Использование этого метода демонстрируется в примере 11.3.

 

namespaceTemplateProject2

{

Class Program

{

staticvoid Main(string[] args)

   {

String int sum = sum<int>(10, 20);

Console.WriteLine(intsum);

 

String str sum = sum<string>("Hello ", "world");

Console.WriteLine(strsum);

 

Console.ReadLine();

   }

 

staticstring sum<T>(T value1, T value2)

   {

return value1.ToString() + value2.ToString();

}

}

}

Пример 11.3.

Поясняем. Первый раз метода вызывается как sum<int>.В треугольных скобках указан типint. Значит в шаблоне везде, где была буква T будет подразумеваться тип int. Потому качестве параметров в метод передаются числа. Во второй раз метод вызывается как sum<string>, указывая в качестве шаблона типstring. Соответственно, фактические параметры метода в этом случае должны быть типаstring.Если параметры будут другими, то произойдет ошибка.

 

Рис.11.3. Результат работы программы из примера 11.3.

Использование шаблона в методе.

Мощь шаблонов более наглядна видна на классах. В примере 11.4 написан класс TemplateTest для работы со статическим массивом. За счет шаблонов использование этого класса становится универсальным.

При объявлении класса, использующего шаблон, буква шаблона указывается в треугольных скобках после имени класса. Теперь внутри класса букву T следует воспринимать как тип данных – объявлять переменные, получать параметры, возвращать значения типа T. При инициализации объекта класса TemplateTestследует указать с каким типом данных будет работать объект.

namespaceTemplateProject

{

Public class TemplateTest<T>

{

   T[] array = new T[10];

int index = 0;

 

publicbool Add(T value)

   {

if (index >= 10)

return false;

 

       array[index++] = value;

return true;

   }

 

public T Get(int index)

   {

if (index <this.index&& index >=0 )

return array[index];

else

return default(T);

   }

 

publicint Count()

   {

return index;

}

}

 

publicstaticclassClass1

{

staticvoid Main()

   {

TemplateTest<int>testarray = newTemplateTest<int>();

 

testarray.Add(10);

testarray.Add(1);

testarray.Add(3);

testarray.Add(14);

 

for (inti = 0; i<testarray.Count(); i++)

       {

Console.WriteLine(testarray.Get(i));

}

Console.ReadLine();

   }

}

 

 

}

Пример 11.4

 

Рис. 11.4. Результат работы программы из примера 11.4.

Использование шаблона в классе.

 

Для того, чтобы создать такой же массив из строк, достаточно изменить объявление

TemplateTest<string>testarray = newTemplateTest<string>();

 

Получаетчто, писать новый класс, который будет делать тоже, но для данных типа stringне надо. Благодаря шаблону все уже готово!

Условная компиляция

Условная компиляция – это процесс, в котором условные конструкции препроцессора позволяют компилировать или пропускать часть программы в зависимости от выполнения некоторого условия. Т.е. простыми словами перед компиляцией наш файл может подставлять нужный нам вариант кода.

Например:
Пусть у нас есть такое простейшее консольное приложение

 

  usingSystem;   namespaceConsoleApplication1 { classProgram { staticvoidMain(string[] args) { Console.WriteLine("this is standard version configuration"); Console.Read(); } } }

И нам необходимо сделать версию программы, в которой будет меняться только вывод сообщения, для этого добавляем определение имени через директиву #define и добавляем директивы условия#if #else #endif

 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #define SpecialVersion usingSystem;   namespaceConsoleApplication1 { classProgram { staticvoidMain(string[] args) { #if SpecialVersion Console.WriteLine("this is special version cofiguration"); #else Console.WriteLine("this is standard version cofiguration"); #endif Console.Read(); } } }

В данном случае у нас внутри функции Main скомпилируется только код

1 2 Console.WriteLine("this is special version cofiguration"); Console.Read();

Т.е. итоговый код, который будет компилироваться получится таким:

1 2 3 4 5 6 7 8 9 10 11 12 13 usingSystem;   namespaceConsoleApplication1 { classProgram { staticvoidMain(string[] args) { Console.WriteLine("this is special version cofiguration"); Console.Read(); } } }

Если необходимо применить имя условия в рамках всего проекта, то в свойствах проекта укажите имя области в текстовом поле Conditionalcompilationsymbols (Символы условной компиляции), расположенным на вкладке Построение(Build) подпунктаСвойства (Properties)пункта Проект (Build) главного меню проекта:

 

Рис.11.5. Установка директивы условной компиляции в свойствах проекта.

 

Если мы указываем имя условия в свойстве проекта, то в коде программы определять имена через директивы #define не надо.



Дата: 2019-11-01, просмотров: 187.