ООП_С_шарп


Чтобы посмотреть этот PDF файл с форматированием и разметкой, скачайте его и откройте на своем компьютере.
Уроки
C
#

http://mycsharp.ru/post/23/2013_06_20_ponyatie_obektno
-
orientirovannogo_programmirovaniya_(oop)_klassy_i_obekty.html


Объектно
-
ориентированное программирование
(ООП)
-

эффективный подход к современному
программированию. Раньше программисты в
основном использовали функциональный или процедурный
принцип программирования. Все программы писались в одном файле. Программы становились сложнее и
больше, что доставляло проблемы разработчикам при поддержке программ и внесении изменений. Эту
проблему реш
ает ООП, которое позволяет объединить данные и методы, относящиеся к одной сущности, и
работать с ними, как с одним целым.

Классы и объекты.

Два ключевых понятия:

Класс

и

Объект.

Класс



это абстрактный тип данных. С помощью класса
описывается некоторая
сущность (ее характеристики и возможные действия). Например, класс может
описывать студента, автомобиль и т.д. Описав класс, мы можем создать его экземпляр


объект.
Объект



это
уже конкретный представитель класса.


Пример
. Пусть в программе необходимо ра
ботать со странами. Страна


это абстрактное понятие. У нее
есть такие характеристики, как название, население, площадь, флаг и другое. Для описания такой страны будет
использоваться класс с соответствующими полями данных. Такие страны, как Россия и Греция

будут уже
объектами (конкретными представителями типа страна).


1. Основные принципы объектно
-
ориентированного программирования


ООП основывается на нескольких базовых принципах.

Инкапсуляция



позволяет скрывать внутреннюю реализацию. В классе могут быть реализованы
внутренние вспомогательные методы, поля, к которым доступ для пользователя необходимо запретить, тут и
используется инкапсуляция.

Наследования



позволяет создавать новый класс н
а базе другого. Класс, на базе которого создается
новый класс, называется базовым, а базирующийся новый класс


наследником. Например, есть базовый класс
животное. В нем описаны общие характеристики для всех животных (класс животного, вес). На базе этого
к
ласса можно создать классы наследники Собака, Слон со своими специфическими свойствами. Все свойства и
методы базового класса при наследовании переходят в класс наследник.

Полиморфизм



это способность объектов с одним интерфейсом иметь различную реализац
ию.
Например, есть два класса, Круг и Квадрат. У обоих классов есть метод GetSquare(), который считает и
возвращает площадь. Но площадь круга и квадрата вычисляется по
-
разному, соответственно, реализация одного
и того же метода различная.

Абстракция



поз
воляет выделять из некоторой сущности только необходимые характеристики и
методы, которые в полной мере (для поставленной задачи) описывают объект. Например, создавая класс для
описания студента, мы выделяем только необходимые его характеристики, такие как

ФИО, номер зачетной
книжки, группа. Здесь нет смысла добавлять поле вес или имя его котаCсобаки и т.д.


Класс



это абстрактный тип данных. Другими словами, класс


это некоторый шаблон, на основе
которого будут создаваться его экземпляры


объекты.

В Си
#

классы объявляются с помощью ключевого слова

class. Общая структура объявления
:



[модификатор доступа] class [имя_класса]

{



//тело класса

}


Модификаторов доступа для классов есть два:

-

public



доступ к классу возможен из любого места одной сборки
либо из другой сборки, на которую
есть ссылка;

-

internal



доступ к классу возможен только из сборки, в которой он объявлен.



Сборка (assembly)



это готовый функциональный модуль в виде exe либо dll файла (файлов), который
содержит скомпилированный код
для .NET. Сборка предоставляет возможность повторного использования
кода.

При объявлении класса модификатор доступа можно не указывать, при этом будет применяться режим по
умолчанию

internal.

Класс следует объявлять внутри пространства имен

namespace, но з
а пределами другого класса (возможно
также объявление класса внутри другого
-

вложенные типы).



Пример объявления классов

Student

и

Pupil:


namespace HelloWorld

{



class Student //
без указания модификатор доступа, класс будет internal



{



//тело к
ласса



}



public class Pupil


{


//
тело класса


}

}


Члены класса

Классы в Си
-
шарп могут содержать следующие члены:

-

поля;

-

константы;

-

свойства
;

-

к
онструкторы
;

-

методы
;

-

события;

-

операторы;

-

индексаторы;

-

вложенные типы.


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


-

public



доступ к члену возможен из любого места одной сборки, либо из другой сборки, на которую есть
ссылка;

-

protected



доступ к члену возможен только внутри класса, либо в классе
-
наследнике (
при наследовании);

-

internal



доступ к члену возможен только из сборки, в которой он объявлен;

-

private



доступ к члену возможен только внутри класса;

-

protected internal

-

доступ к члену возможен из одной сборки, либо из класса
-
наследника другой сбор
ки.


Не указав модификатор доступа для члена, по умолчанию ему будет присвоен режим

private
.


При помощи модификаторов доступа в Си
-
шарп реализуется один из базовых принципов

ООП



инкапсуляция.


Поля класса


Поле



это переменная, объявленная внутри класса. Как правило, поля объявляются с модификаторами
доступа

private

либо

protected, чтобы запретить прямой доступ

к ним. Для получения доступа к полям следует
использовать свойства или методы.


Пример объявления полей в классе:



class Student

{



private string firstName;



private string lastName;



private int age;



public string group; //
не

рекомендуется

использовать

public
для

поля

}


Создание объектов


Объявив класс, мы теперь можем создавать объекты. Делается это при помощи ключевого слова

new

и имени
класса:

namespace HelloWorld

{



class Student



{



private string firstName;



private string

lastName;



private int age;



public string group;



}



class Program



{



static void Main(string[] args)



{



Student student1 = new Student(); //
создание

объекта

student1
класса

Student



Student student2 = new Student();




}



}

}


Доступ к членам объекта осуществляется при помощи оператора точка «.» :


static void Main(string[] args)

{



Student student1 = new Student();



Student student2 = new Student();




student1.group = "Group1";



student2.group = "Group2";




Console.WriteLine(student1.group); //
выводит

на

экран

"Group1"



Console.Write(student2.group);



Console.ReadKey();

}


Такие поля класса

Student, как

firstName,

lastName

и

age

указаны с модификатором доступа

private, поэтому
доступ к ним будет запр
ещен вне класса:


static void Main(string[] args)

{



Student student1 = new Student();




student1.firstName= "Nikolay"; //
ошибка
,
нет

доступа

к

полю

firstName.
Программа не
скомпилируется


}


Константы

Константы
-
члены класса

ничем не отличаются от
простых констант внутри методов, которые
рассматривались в уроке

переменные и константы
.


Константа



это переменная, значений которой нельзя
изменить. Константа объявляется с помощью
ключевого слова

const. Пример объявления константы:


class Math

{



private const double Pi = 3.14;

}



Домашнее задание 1

Создайте какой
-
нибудь класс (например класс Автомобиль), объявите в нем поля с различными
режимами
доступа (private, protected, internal, public). Создайте объект класса, и попытайтесь записатьCполучить данные с
различных полей.

Метод



это небольшая подпрограмма, которая выполняет, в идеале, только одну функцию. Методы
позволяют сократить объе
м кода.

Методы вместе с полями, являются основными членами класса.

Статический метод



это метод, который не имеет доступа к полям объекта, и для вызова такого метода
не нужно создавать экземпляр (объект) класса, в котором он объявлен.

Простой метод



это
метод, который имеет доступ к данным объекта, и его вызов выполняется через
объект.


Простые методы служат для обработки внутренних данных объекта.


П
ример использования простого метода. Класс

Телевизор, у него есть поле

switcOedOn, которое отображает
сост
ояние включенCвыключен, и два метода


включение и выключение:

class TVSet

{



private bool switchedOn;




public void SwitchOn()



{



switchedOn = true;



}



public void SwitchOff()



{



switchedOn = false;



}


}

class Program

{



static

void Main(string[] args)



{






myTV.SwitchOn(); // включаем телевизор, switchedOn = true;



myTV.SwitchOff(); // выключаем телевизор, switchedOn = false;



}

}


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

Статические методы, обычно, выполняют какую
-
нибудь глобальную, общую функцию, обрабатывают
«внешние данные». Например, сортировка массива, обработка строки, возведение числа в

степень и другое.


Пример

статического метода, который обрезает строку до указанной длины, и добавляет многоточие:


class StringHelper

{



public static string TrimIt(string s, int max)



{



if (s == null)



return string.Empty;



if (s.Leng
th = max)



return s;







}

}

class Program

{



static void Main(string[] args)



{



string s = "Очень длинная строка, которую необходимо обрезать до указанной длины и добавить
многоточие";



Console.
WriteLine(StringHelper.TrimIt(s, 20)); //"Очень длинная строка…"



Console.ReadLine();



}

}


Статический метод не имеет доступа к нестатическим полям класса:


class SomeClass

{



private int a;



private static int b;




public static void



{



a=5; //
ошибка




b=10; //
допустимо



}

}


Домашнее задание 2

Создайте класс Телевизор. В нем есть поле текущий канал. Предусмотрите в нем возможность переключения
каналов: следующий канал, предыдущий канал, переход к каналу по
номеру. Учтите, что канал не может иметь
отрицательный номер.


Конструктор



это метод класса, предназначенный для инициализации объекта при его
создании.Инициализация



это задание начальных параметров объектовCпеременных при их создании.

Особенность конс
труктора

-
имя всегда совпадает с именем класса, в котором он объявляется.

При этом, при объявлении конструктора, не нужно указывать возвращаемый тип, даже ключевое
слово

void. Конструктор следует объявлять как

public, иначе объект нельзя будет создать (хо
тя иногда в этом
также есть смысл).

В классе, в котором не объявлен ни один конструктор, существует неявный конструктор по умолчанию,
который вызывается при создании объекта с помощью оператора

new.

Объявление конструктора имеет следующую структуру:


public [имя_класса] ([аргументы])

{


// тело конструктора

}


Например, у нас есть класс Автомобиль. Создавая новый автомобиль, значения пробега и количества топлива в
баке есть смысл поставить равными нулю:

class Car

{



private double mileage;



privat
e double fuel;




public Car() //объявление конструктора



{



mileage = 0;



fuel = 0;



}

}

class Program

{



static void Main(string[] args)



{



Car newCar = new Car(); // создание объекта и вызов конструктора




}


}


Без конструктора
нам бы пришлось после создания объекта отдельно присваивать значения его полям, что
очень неудобно.

Конструктор также может иметь параметры.


Пример с тем же автомобилем, только теперь при создании объекта мы можем задать любые начальные
значения:


class C
ar

{



private double mileage;



private double fuel;




public Car(double mileage, double fuel)



{



this.mileage = mileage;



this.fuel = fuel;



}

}

class Program

{



static void Main(string[] args)



{



Car newCar = new Car(100, 50);
//
вызов

конструктора

с

параметрами




}


}


Ключевое слово tOis



В примере выше используется ключевое слово

this.


Указатель tOis

-

это указатель на объект, для которого был вызван нестатический метод. Ключевое
слово

this

обеспечивает доступ к текущему экземпляру класса. Классический пример использования

tOis, это как
раз в конструкторах, при одинаковых именах полей класса и аргумен
тов конструктора. Ключевое слово

this

это
что
-
то вроде имени объекта, через которое мы имеем доступ к текущему объекту.


Несколько конструкторов



В классе возможно указывать множество конструкторов, главное чтобы они отличались
сигнатурами.

Сигнатура, в с
лучае конструкторов,
-

это набор аргументов. Например, нельзя создать два
конструктора, которые принимают два аргумента типа

int.

Пример использования нескольких конструкторов:

class Car

{



private double mileage;



private double fuel;




public Car()



{



mileage = 0;



fuel = 0;



}



public Car(double mileage, double fuel)


{



this.mileage = mileage;



this.fuel = fuel;


}

}

class Program

{




static void Main(string[] args)



{



Car newCar = new Car(); // создаем автомобиль

с параметрами по умолчанию, 0 и 0




Car newCar2 = new Car(100, 50); // создаем автомобиль с указанными параметрами



}


}


Если в классе определен один или несколько конструкторов с параметрами, мы не сможем создать объект через
неявный конструктор п
о умолчанию:

class Car

{



private double mileage;



private double fuel;



public Car(double mileage, double fuel)



{



this.mileage = mileage;



this.fuel = fuel;



}

}

class Program

{




static void Main(string[] args)



{



Car newCar
= new Car(100, 50);




Car newCar2 = new Car(); //
ошибка
,
в

классе

не

определен

конструктор

без

параметров



}


}


Домашнее задание 3



Создайте класс Студент, определите в нем поля: имя, курс, есть ли у него стипендия. Создайте в классе
несколько
конструкторов, для возможности задания сразу всех указанных параметров или нескольких при
создании экземпляров.


Свойство

в Си#


это член класса, который предоставляет удобный механизм доступа к полю класса (чтение
поля и запись). Свойство представляет со
бой что
-
то среднее между полем и методом класса. При использовании
свойства, мы обращаемся к нему, как к полю класса, но на самом деле компилятор преобразовывает это
обращение к вызову соответствующего неявного метода. Такой метод называется аксессор (acce
ssor).
Существует два таких метода:


(для получения данных) и

set

(для записи). Объявление простого свойства
имеет следующую структуру:


Lмодификатор доступа] Lтип] Lимя_свойства]

{






{



CC тело аксессора для чтения из поля



}








{



CC тело аксессора для записи в поле



}

}


Пример использования свойств.

Имеется класс

Студент, и в нем есть закрытое поле курс, которое не может
быть ниже единицы и больше пяти. Для управления доступом к этому полю будет использовано свойство

Year
:


class Student

{



private int year; CCобъявление закрытого поля




public int Year CCобъявление свойства



{



get CC аксессор чтения поля



{






}



set CC аксессор записи в поле



{



if (value 1)



y
ear = 1;



else if (value � 5)



year = 5;



else year = value;



}



}

}

class Program

{




static void Main(string[] args)



{



Student st1 = new Student();



st1.Year = 0; CC записываем в поле, используя аксессор set

Co
nsole.WriteIine(st1.Year); CC читаем поле, используя аксессор get, выведет 1



Console.ReadKey();



}


}


В свойстве реализуются два метода. В теле аксессора

getможет быть более сложная логика доступа, но в итоге
должно возвращаться значение поля, либо

другое значение с помощью оператора

return. В аксессоре


же
присутствует неявный параметр

value, который содержит значение, присваиваемое свойству (в примере выше,
при записи, значениеvalue

равно «0»).


Зачем это нужно?



Если, например, мы бы просто с
делали поле

year

открытым и не использовали ни методы, ни свойство
для доступа, мы могли бы записать в это поле любое значение, в том числе и некорректное, а так мы можем
контролировать чтение и запись.

Для контроля доступа мы могли бы здесь использовать п
ростые методы, но для этого нам бы пришлось
реализовать два отдельных метода, с разными именами, и при обращении к ним необходимо использовать
скобки, что добавляет лишние неудобства. Вот как бы выглядела та же программа с использованием методов
(на практи
ке используйте свойства):


class Student

{



private int year;







{






}






{



if (value 1)



year = 1;



else if (value � 5)



year = 5;



else year =
value;



}


}

class Program

{




static void Main(string[] args)



{



Student st1 = new Student();









Console.ReadKey();



}


}


Свойство также может предоставлять доступ только на чтени
е поля или только на запись. Если, например, нам
необходимо закрыть доступ на запись, мы просто не указываем аксессор

set.
Пример
:


class Student

{



private int year;




public Student(int y) //
конструктор



{



year = y;



}




public int Year




{






{






}




}

}

class Program

{




static void Main(string[] args)



{



Student st1 = new Student(2);




Console.WriteLine(st1.Year); //
чтение



st1.Year = 5; //
ошибка
,
свойство

только

на

чтение




Console.ReadKey();



}


}


Стоит помнить, что само свойство не определяет место в памяти для хранения поля, и, соответственно,
необходимо отдельно объявить поле, доступом к которому будет управлять свойство.



Автоматическое свойство



это очень простое с
войство, которое, в отличии от обычного свойства, уже
определяет место в памяти (создает неявное поле), но при этом не позволяет создавать логику доступа.
Структура объявления Автоматического свойства:


Lмодификатор доступа] Lтип] Lимя_свойства] S get; set
; }


У таких свойств, у их аксессоров отсутствует тело. Пример

использования
:


class Student

{




}

class Program

{




static void Main(string[] args)



{



Student st1 = new Student();




st1.Year = 0;




Console.WriteLine(st1.Year);



Console.ReadKey();



}


}


Автоматически реализуемые свойства есть смысл использовать тогда, когда нет необходимости накладывать
какие
-
либо ограничения на возможные значения неявного поля свойства.


И тут у вас может возн
икнуть вопрос, а в чем тогда разница между простыми открытыми полями и
автоматическими свойствами. У таких свойств остается возможность делать их только на чтение или только на
запись. Для этого уже используется модификатор доступа private перед именем акс
ессора:


public int Year S private get; set; } CC свойство только на запись

public int Year S get; private set; } CC свойство только на чтение



Домашнее задание


Создайте класс

Телевизор
, объявите в нем поле

громкость звука
, для доступа к этому полю
реализуйте
свойство. Громкость может быть в диапазоне от 0 до 100.


В этом уроке мы рассмотрим с вами один из базовых принципов объектно
-
ориентированного
программирования

наследование
.


Начнем рассмотрение наследования из жизненных ситуаций. Для примера,
возьмем такие понятия, как человек
и студент. У любого человека есть имя, рост, вес и другие общие характеристики. Студент же является частным
случаем человека, у него также есть имя, рост, вес, но кроме этого, он учится в некотором ВУЗе, на
определенной с
пециальности и имеет средний балл. С точки зрения наследования, в этом случае, студент
является наследником понятия человек. Еще один пример можно привести с животными. Животное
-

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


П
ерейдем к программированию


В программировании

наследование

позволяет создавать новый класс на базе другого. Класс, на базе которого
создается новый класс, называется
базовым
, а базирующийся новый класс


наследником

или

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


Объявление нового класса, который будет наследовать др
угой класс, выглядит так:


class Lимя_класса] : Lимя_базового_класса]

{



CC тело класса

}


Приведу простой пример использования наследования. На основе базового класса Животное создаются два
класса Собака и Кошка, в эти два класса переходит свойство Имя
животного:


class Animal

{




}

class Dog : Animal

{



public void Guard()



{



CC собака охраняет



}

}

class Cat : Animal

{



public void CatchMouse()



{



CC кошка ловит мышь



}


}

class Program

{



stati
c void Main(string[] args)



{



Dog dog1 = new Dog();



dog1.Name = "Барбос"; CC называем пса



Cat cat1 = new Cat();



cat1.Name = "Барсик"; CC называем кота



dog1.Guard(); CC отправляем пса охранять



cat1.CatcOMouse(); CC отправляем

кота на охоту




}


}



Вызов конструктора базового класса в Си
-
шарп


В базовом классе и классе
-
наследнике могут быть объявлены конструкторы, и тут возникает вопрос


какой
конструктор за что должен отвечать, и как их вызывать. Логично будет сказать то,
что конструктор базового
класса будет создавать ту часть объекта, которая принадлежит базовому классу (ведь из базового класса о
наследнике ничего неизвестно), а конструктор из наследника будет создавать свою часть.


Когда конструктор определен только в на
следнике, то здесь всё просто


при создании объекта сначала
вызывается конструктор по умолчанию базового класса, а затем конструктор наследника.


Когда конструкторы объявлены и в базовом классе, и в наследнике


нам необходимо вызывать их оба. Для
вызова
конструктора базового класса используется ключевое слово

base
. Объявление конструктора класса
-
наследника с вызовом базового конструктора имеет следующую структуру:


Lимя_конструктора_класса
-
наследника] (Lаргументы]) : base (Lаргументы])

{



CC тело
конструктора

}


В базовый конструктор передаются все необходимые аргументы для создания базовой части объекта.


Приведу пример вызова базового конструктора. Есть тот же класс

Животное
и класс

Попугай
. В
классе

Животное

есть только свойство

Имя
, и конструкто
р, который позволяет установить это имя. В
классе

Попугай

есть свойство

Длина клюва

и конструктор, в котором мы задаем эту длину. При создании
объекта

Попугай

мы указываем два аргумента


имя и клюв, и дальше аргумент

Имя

передается в базовый
конструктор,
он вызывается, и после его работы выполнение передается конструктору класса

Попугай
, где
устанавливается длина:


class Animal

{







public Animal(string name)



{



Name = name;



}


}

class Parrot : Animal

{



pub
длина

клюва




public Parrot(string name, double beak) : base(name)



{



BeakLength = beak;



}

}

class Dog : Animal

{


public Dog(string name) : base (name)



{




//
здесь

может

быть

логика

создания

объекта

Собака




}

}


class Program

{




static void Main(string[] args)


{



Parrot parrot1 = new Parrot("
Кеша
", 4.2);




Dog dog1 = new Dog("
Барбос
");




}


}



Доступ к членам базового класса из класса
-
наследника


Здесь стоит отметить, что в
классе
-
наследнике мы можем получить доступ к членам базового класса которые
объявлены как

public
,

protected
,

internal

и
protected internal
. Члены базового класса с модификатором
доступа

private
также переходят в класс
-
наследник, но к ним могут иметь доступ
только члены базового класса.
Например, свойство, объявленное в базовом классе, которое управляет доступом к закрытому полю, будет
работать корректно в классе
-
наследнике, но отдельно получить доступ к этому полю из класса
-
наследника мы
не сможем.


Домашнее

задание


Создайте базовый класс

Геометрическая фигура
, предусмотрите в нем общие поляCсвойства, например
координаты центра фигуры, с помощью конструктора должна быть возможность задать центр. На базе этого
класса создайте два новых


Треугольник

и

Окружно
сть
. В этих классах должны быть свои особые поля,
например радиус для окружности. В оба класса добавьте метод

Нарисовать
, в котором могла бы быть
специфическая логика рисования фигуры. Создайте объекты треугольник и окружность.

В Си
-
шарп есть возможность с
оздания

массива (или списка) указателей на базовый класс

в котором в
качестве элементов могут быть объекты класса
-
наследника. Например, мы можем создать массив объектов
Животное, и элементами такого массива будут объекты классов Собака, Кошка. Пример
:


class Animal

{







public Animal(string name)



{



Name = name;



}

}

class Dog : Animal

{



public Dog(string name) : base(name)



{ }




public void Guard()



{



//
собака

охраняет



}

}

class Cat : Anim
al

{


public Cat(string name) : base(name)



{ }



public void CatchMouse()


{



//
кошка

ловит

мышь


}

}


class Program

{




static void Main(string[] args)



{



ListAnimalZn-;i13;&#xm6a-;l13; animals = new ListAnimalZni;m6a;l13;(); //
создаем

список

указателей

на

базо
вый

класс



animals.Add(new Dog("
Барбос
"));




animals.Add(new Cat("
Барсик
"));



animals.Add(new Dog("
Полкан
"));




foreach (Animal animal in animals)



{



Console.WriteLine(animal.Name);



}



Console.ReadLine();


}


}


Хотя ка
к элементы в этот список мы добавляли объекты классов
-
наследников Собака и Кошка, будучи
элементами списка указателей на базовый класс, эти объекты преобразовываются к объектам базового класса, и
мы имеем доступ только к той части объектов, которая описана

в базовом классе


мы не можем здесь вызвать
методы Guard() или CatcOMouse(), но при этом имеем доступ к имени животного.


Обратное здесь невозможно. Нельзя создать массив объектов класса Собака, и записать в него объекты класса
Животное.


Оператор is


Оператор

is

работает очень просто


он проверяет совместимость объекта с указанным типом (принадлежит ли
объект определенному классу). Оператор
is

возвращает истину (true), если объект принадлежит классу. Истинна
будет также при проверке совместимости объек
та класса
-
наследника и базового класса:


static void Main(string[] args)

{



Dog dog1 = new Dog("
Барбос
");





Console.WriteLine(dog1 is Dog); // true



Console.WriteLine(dog1 is Animal); // true



Console.WriteLine(dog1 is Cat); // false



Console.Re
adLine();

}




Пользуясь оператором

is

и явным преобразованием, теперь мы можем полноценно использовать массив
указателей на базовый класс:


class Animal

{







public Animal(string name)



{



Name = name;



}

}

cla
ss Dog : Animal

{



public Dog(string name) : base(name)



{ }




public void Guard()


{



Console.WriteLine(Name + "
охраняет
");



}

}

class Cat : Animal

{


public Cat(string name) : base(name)



{ }




public void CatchMouse()



{



Consol
e.WriteLine(Name + "
ловит

мышь
");



}

}


class Program

{




static void Main(string[] args)


{




ListAnimalZn-;i13;&#xm6a-;l13; animals = new ListAnimalZni;m6a;l13;();



animals.Add(new Dog("
Барбос
"));




animals.Add(new Cat("
Барсик
"));



animals.Add(new Dog("
Полкан
"));




foreach (Animal animal in animals)



{



if (animal is Dog) //
проверяем

является

ли

данное

животное

собакой



((Dog)animal).Guard();



else ((Cat)animal).CatchMouse();



}



Console.ReadLine();



}


}


Здесь, использо
вав явное преобразование, мы получаем полный доступ к объектам из списка, и можем вызывать
методы Guard() и CatcOMouse().


Оператор as


В примере выше, вместо явного приведения типов можно было использовать
оператор

as
.

(Dog)animal

эквивалентно выражению

a
nimal as Dog
. Разница между оператором

as

и явным
приведением лишь в том, что в случае невозможности преобразования, оператор

as

возвращает

null
, тогда как
явное приведение выбрасывает исключение. Пример программы выше, только уже с оператором

as
:


class
Program

{




static void Main(string[] args)



{




ListAnimalZn-;i13;&#xm6a-;l13; animals = new ListAnimalZn-;i13;&#xm6a-;l13;();



animals.Add(new Dog("
Барбос
"));




animals.Add(new Cat("
Барсик
"));



animals.Add(new Dog("
Полкан
"));




foreach (Animal animal in animals)



{



i
f (animal is Dog) //
проверяем

является

ли

данное

животное

собакой



(animal as Dog).Guard();



else (animal as Cat).CatchMouse();



}



Console.ReadLine();



}


}


Следующий принцип объектно
-
ориентированного программирования (ООП)

полиморф
изм
.


Сам термин полиморфизм можно перевести как
«много форм»
. А если говорить проcтыми
словами,
полиморфизм



это различная реализация однотипных действий. Классическая фраза, которая коротко
объясняет полиморфизм


«Один интерфейс, множество реализаций».
Приведу примеры из жизни. В
автомобилях есть рулевое колесо. Это колесо является интерфейсом между водителем и автомобилем, который
позволяет поворачивать автомобиль. Механическая реализация руля у автомобилей может быть разная, но при
этом результат получ
ается одинаковым


колесо вправо


автомобиль вправо, и наоборот. Еще один пример.
Клавиатура является интерфейсом ввода между пользователем и ПК. При нажатии одной и той же клавиши на
различных клавиатурах результат получаем одинаковый, но при этом сама р
еализация нажатия клавиши может
быть разная (емкостная, контактная и тд.).


Переходим к программированию


С полиморфизмом к нам прибавляются еще несколько понятий: виртуальныйCабстрактный метод,
переопределение метода.


Виртуальный метод



это метод, котор
ый МОЖЕТ быть переопределен в классе
-
наследнике. Такой метод
может иметь стандартную реализацию в базовом классе.


Абстрактный метод



это метод, который ДОЛЖЕН быть реализован в классе
-
наследнике. При этом,
абстрактный метод не может иметь своей реализаци
и в базовом классе (тело пустое), в отличии от
виртуального.


Переопределение метода



это изменение реализации метода, установленного как виртуальный (в классе
наследнике метод будет работать отлично от базового класса).


В качестве системы, предоставляющ
ей тот самый интерфейс, в программировании может выступать класс и
интерфейс. Здесь мы поговорим о классах. Есть класс, в нем объявлен виртуальный или абстрактный метод. От
этого класса наследуются еще несколько классов, и в каждом из них по
-
разному реализ
уется тот самый
виртуальныйCабстрактный метод. Получается, объекты этих классов имеют метод с одинаковым именем, но с
разной реализацией. В этом и есть полиморфизм.


Например, есть класс Геометрическая Фигура, и в нем объявлен метод Draw(), который будет р
исовать фигуру.
От этого класса наследуются классы Треугольник, Прямоугольник, Окружность. В них реализуется метод для
рисования (понятно, что реализация рисования каждой фигуры разная). В итоге мы можем создать объекты этих
классов, и у всех будет метод D
raw(), который будет рисовать соответствующую фигуру.


Для чего вообще нужен полиморфизм?


Полиморфизм позволяет писать более абстрактные, расширяемые программы, один и тот же код используется
для объектов разных классов, улучшается читабельность кода. Пол
иморфизм позволяет избавить разработчика
от написания, чтения и отладки множества if
-
else/switch
-
case конструкций.


Этот урок был теоретическим, в следующих мы уже детально рассмотрим полиморфизм,
виртуальныеCабстрактные методы на практике.


В качестве дом
ашнего задания я попрошу вас ниже в комментариях навести примеры систем «Один интерфейс,
множество реализаций», по типу руля и клавиатуры.

В этом уроке мы рассмотрим с вами некоторые инструменты, с помощью которых в Си
-
шарп реализуется
полиморфизм
-

виртуа
льные методы, переопределение методов.


Виртуальный метод



это метод, который может быть переопределен в классе наследнике.


Переопределение метода


это изменение его реализации в классе наследнике. Переопределив метод, он будет
работать по
-
разному в
базовом классе и классе наследнике, имея при этом одно и то же имя и аргументы и тип
возврата.


Виртуальный метод объявляется при помощи ключевого слова

virtual
:


Lмодификатор доступа] virtual Lтип] Lимя метода] (Lаргументы])

{



CC тело метода

}


*Статический метод не может быть виртуальным.


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

override
:


Lмодификатор доступа] override Lтип] Lимя метода] (Lаргументы])

{



CC новое

тело метода

}


Рассмотрим это на примере. У нас есть класс

Человек
, и от него наследуются еще два


Студент

и

Ученик
. В
базовом классе есть виртуальный метод
ShowInfo
, который выводит информацию об объекте. В
классах

Студент

и
Ученик

этот метод
переопределяется для того, чтобы к выводу базовой информации
добавить еще специфическую, относящуюся к соответствующему классу:


class Person

{










public Person(string name, int age)



{



Name = name;



Age = age;



}



public virtual void ShowInfo() //
объявление

виртуального

метода



{



Console.WriteLine("
Человек
\
n
Имя
: " + Name + "
\
n" + "
Возраст
: " + Age + "
\
n");



}

}

class Student : Person

{



public string HighSchoolNa




public Student(string name, int age, string hsName)

: base(name, age)



{



HighSchoolName = hsName;



}



public override void ShowInfo() //
переопределение

метода



{



Console.WriteLine("
Студент
\
n
Имя
: " + Name + "
\
n" + "
Во
зраст
: " + Age +"
\
n"+
"
Название

ВУЗа
: " + HighSchoolName + "
\
n");




}

}

class Pupil : Person

{







public Pupil(string name, int age, string form)

: base(name, age)



{



Form = form;



}



public override void S
howInfo() //
переопределение

метода



{



Console.WriteLine("
Ученик
(
ца
)
\
n
Имя
: " + Name + "
\
n" + "
Возраст
: " + Age + "
\
n" +
"
Класс
: " + Form + "
\
n");




}

}


class Program

{




static void Main(string[] args)



{



ListP&#x-5P5;rso;&#xn000;erson persons = new
ListPP5e;&#xrson;erson();



persons.Add(new Person("
Василий
", 32));



persons.Add(new Student("
Андрей
", 21, "
МГУ
"));



persons.Add(new Pupil("
Елена
", 12, "7
-
Б
"));




foreach (Person p in persons)



p.ShowInfo();




Console.ReadKey();



}

}


В ме
тоде

main

мы создаем список людей, в который добавляем просто человека, студента и ученика, и дальше
выводим информацию о каждом из них вызовом метода

ShowInfo()
. Результат работы


вывод информации
соответственно типу объекта.


А теперь может возникнуть в
опрос


а что будет, если убрать переопределение, откинув ключевые
слова

virtual

и

override
? В таком случае, в базовом классе и в классе наследнике будут методы с одинаковым
именем
ShowInfo
. Программа работать будет, но о каждом объекте, независимо это прос
то человек или
студентCученик, будет выводиться информация только как о простом человеке (будет вызываться
метод

ShowInfo

из базового класса).


Это можно исправить, добавив проверки на тип объекта, и при помощи приведения типов, вызывать нужный
метод

ShowI
nfo
:


foreach (Person p in persons)

{



if (p is Student)



((Student)p).ShowInfo();



else if (p is Pupil)



((Pupil)p).ShowInfo();




else p.ShowInfo();

}


Только вот, сколько лишнего кода от этого появляется. Думаю, становится понятно, что нам
дают виртуальные
методы и переопределение.


Вызов базового метода


Бывает так, что функционал метода, который переопределяется, в базовом классе мало отличается от
функционала, который должен быть определен в классе наследнике. В таком случае, при переопре
делении, мы
можем вызвать сначала этот метод из базового класса, а дальше дописать необходимый функционал. Это
делается при помощи ключевого слова

base
:


public virtual void SOowHnfo() CC SOowHnfo в классе Person

{



Console.WriteIine("Имя: " + Name);



Console.WriteIine("Возраст: " + Age);


}


public override void SOowHnfo() CC SOowHnfo в классе Student

{



base.SOowHnfo(); CC вызывает базовый метод SOowHnfo()



Console.WriteIine("Название ВУЗа: " + HigOScOoolName);


}



Домашнее задание


Создайте три
класса: Воин, Воин_в_легких_доспехах и Воин_в_тяжелых_доспехах. У всех них есть свойство


Количество_жизней, а также метод Получить_урон, который принимает в качестве аргумента значение
получаемого урона. Реализуйте этот метод по
-
разному для всех типов, у
становив различные коэффициенты в
зависимости от типа доспехов в формуле вычета здоровья.

В этом уроке мы продолжим с вами рассматривать инструменты полиморфизма. На очереди у нас абстрактные
классы, методы и свойства.


Абстрактные классы


Абстрактный клас
с



это класс объявленный с ключевым словом

abstract
:


abstract class Lимя_класса]

{



CCтело

}


Такой класс имеет следующие особенности:

-

нельзя создавать экземпляры (объекты) абстрактного класса;

-

абстрактный класс может содержать как абстрактные
методыCсвойства, так и обычные;

-

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


Зачем нужны абстрактные классы?


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

абстрактная сущность, которая, как объект, не может существовать, и эта часть необходима в
классах наследни
ках. Конкретные примеры будут дальше.


Абстрактные методы


Понимание абстрактных методов не будет для вас сложной задачей, если вы уже ознакомились с

виртуальными
методами
.


Абстрактный метод



это метод, который не имеет своей реализации в базовом классе, и он ДОЛЖЕН быть
реализован в классе
-
наследнике. Абстрактный метод может быть объявлен только в абстрактном классе.


Какая же разни
ца между виртуальным и абстрактным методом?


-

Виртуальный метод может иметь свою реализацию в базовом классе, абстрактный


нет (тело пустое);

-

Абстрактный метод должен быть реализован в классе наследнике, виртуальный метод переопределять
необязательно.


Объявление абстрактного метода происходит при помощи ключевого слова
abstract
, и при этом фигурные
скобки опускаются, точка с запятой ставится после заголовка метода:


Lмодификатор доступа] abstract Lтип] Lимя метода] (Lаргументы]);



Реализация
абстрактного метода в классе наследнике происходит так же, как и переопределение метода


при
помощи ключевого слова

override
:


Lмодификатор доступа] override Lтип] Lимя метода] (Lаргументы])

{


CC реализация метода

}


Абстрактные свойства


Создание абстр
актных

свойств

не сильно отличается от методов:


protected Lтип] Lполе, которым управляет свойство];

Lмодификатор доступ
а] abstract Lтип] Lимя свойства] S get; set; }



Реализация в классе
-
наследнике:


Lмодификатор доступа] override Lтип] Lимя свойства]


{



get S тело аксессора get }


set S тело аксессора set }

}



Пример


В качестве примера, приведу программу похожую на

ту, которая была в предыдущем уроке о виртуальных
методах, где выводилась информация о человекеCстудентеCшкольнике. Сейчас уже будут животные. Тогда мы
могли создать человека без статуса (не студент, не школьник), у которого была некоторая информация, а с
ейчас
у нас будет абстрактная сущность
Животное
, объект которой создавать нельзя и нет смысла, так как каждое
животное будет конкретного подцарства


млекопитающее, рыба, птица:


abstract class Animal

{






public string T




public abstract void GetHnfo(); CC объявление абстрактного метода

}

class Parrot : Animal

{



public Parrot(string name)



{



Name = name;



Type = "Птица";



}



public override void GetHnfo() CC реализация абстр
актного метода



{



Console.WriteIine("Тип: " + Type + "
\
n" + "Имя: " + Name + "
\
n");



}

}

class Cat : Animal

{



public Cat(string name)



{



Name = name;



Type = "Млекопитающее";



}



public override void GetHnfo() CC реализация абстр
актного метода



{



Console.WriteIine("Тип: " + Type + "
\
n" + "Имя: " + Name + "
\
n");



}

}

class Tuna : Animal

{



public Tuna(string name)



{



Name = name;



Type = "Рыба";



}



public override void GetHnfo() CC реализация абстрактного

метода



{



Console.WriteIine("Тип: " + Type + "
\
n" + "Имя: " + Name+"
\
n");



}

}

class Program

{



static void Main(string[] args)



{



ListAnimalZn-;i13;&#xm6a-;l13; animals = new ListAnimalZni;m6a;l13;();



animals.Add(new Parrot("Кеша"));



animals.Add(new Cat(
"Пушок"));



animals.Add(new Tuna("Тёма"));




foreach (Animal animal in animals)







Console.ReadKey();



}

}


В итоге, мы все так же работаем с одним списком животных, и, вызывая один метод

, мы получаем
информа
цию о соответствующем животном.


При

попытке

создать

объект

абстрактного

класса

мы

получим

ошибку

"Cannot create an instance of the abstract
class or interface 'ConsoleApplication1.Animal'":


Animal animal = new Animal(); CC ошибка




Д
омашнее задание


Создайте абстрактный класс

Человек
, пусть там будет свойство

Имя

и абстрактный
метод

СказатьПриветствие()
, от этого класса будет несколько наследников, которые представляют
национальность (русский, украинец, американец...). Должно получиться так, что при
вызове
метода
СказатьПриветствие()

выводилось приветствие на языке соответствующему национальности (Привет,
Привіт, Hi...).

Интерфейсы



это еще один инструмент реализации полиморфизма в Си
-
шарп. Интерфейс представляет собой
набор методов (свойств, событий,

индексаторов), реализацию которых должен обеспечить класс, который
реализует интерфейс.


Интерфейс может содержать только сигнатуры (имя и типы параметров) своих членов. Интерфейс не может
содержать конструкторы, поля, константы, статические члены. Создав
ать объекты интерфейса невозможно.


Объявление интерфейса


Интерфейс


это единица уровня класса, он объявляется за пределами класса, при помощи ключевого
слова

interface
:


interface ISomeInterface

{



CC тело интерфейса


}



* Имена интерфейсам принято
давать, начиная с префикса «H», чтобы сразу отличать где класс, а где интерфейс.


Внутри интерфейса объявляются сигнатуры его членов, модификаторы доступа указывать не нужно:


interface ISomeInterface

{



свойство




метод

}



Реализация интерфейса


Чтобы указать, что класс реализует интерфейс, необходимо, так же, как и при наследовании, после имени
класса и двоеточия указать имя интерфейса:


class SomeClass : HSomeHnterface CC реализация
интерфейса HSomeHnterface

{



CC тело класса

}



Класс, который реализует интерфейс, должен предоставить реализацию всех членов интерфейса:


class SomeClass : ISomeInterface

{




public string SomeProperty



{






{



CC тело get аксессор
а



}






{



CC тело set аксессора



}



}




public void SomeMethod(int a)



{



CC тело метода



}

}



Пример


Есть классы геометрических фигур

Прямоугольник

и

Окружность
. У обоих классов должны быть методы
вычисления перимет
ра и площади. Эти методы мы представим интерфейсом:


interface


//
объявление

интерфейса

{



void

();



void


();


}

class

Rectangle

:

//
реализация

интерфейса

{



public

void

()



{



Console
.
WriteLine
("(
a
+
b
)*2");



}




public

void

()



{



Console
.
WriteLine
("
a
*
b
");



}

}

class

Circle

:

//
реализация

интерфейса

{



public

void

()



{



Console
.
WriteLine
("2*
pi
*
r
");



}




public

void

()



{



Console
.
WriteLine
("
pi
*
r
^2");



}

}

class

Program

{



static

void

Main
(
string
[]
args
)



{



List


figures

=
new

List

�();



figures
.
Add
(
new

Rectangle
());



figures
.
Add
(
new

Circle
());



foreach

(

f

in

figures
)



{



f
.
();



f
.
();



}



Console
.
ReadLine
();



}


}


В итоге, мы все так же можем объединить в один список объекты классов, которые реализуют один и тот же
интерфейс.


Очень похоже на абстрактные классы


Первый вопрос, который обычно приходит в голову тем, кто изучает интерфейсы


Зачем вообще интерфейсы?
Это так похоже на абстрактные классы.


Чтобы ответить на этот вопрос, для начала необходимо разобраться с такой вещью, как

множественное
наследование
.


М
ножественное наследование



это когда один класс сразу наследуется от нескольких классов. Но бывает так,
что базовые классы содержат методы с одинаковыми именами, в результате чего возникают определенные
неточности и ошибки. Множественное наследование есть

в языке C++, а в C# от него отказались и внесли
интерфейсы. В C# класс может реализовать сразу несколько интерфейсов. Это и является главным отличием
использования интерфейсов и абстрактных классов. Кроме того, конечно же, абстрактные классы могут
содержа
ть все остальные члены, которых не может быть в интерфейсе, и не все методыCсвойства в абстрактном
классе должны быть абстрактными.


Если класс реализует несколько интерфейсов, они разделяются запятыми:


interface

IDrawable

{



void

Draw
();

}


interface

I

{



void

();



void


();


}

class

Rectangle

:
,
IDrawable

{



public

void

()



{



Console
.
WriteLine
("(
a
+
b
)*2");



}




public

void

()



{



Console
.
WriteLine
("
a
*
b
");



}




public

void

Draw
()



{



Console
.
WriteLine
("
Rectangle
");



}

}

class

Circle

:
,
IDrawable

{




public

void

()



{



Console
.
WriteLine
("2*
pi
*
r
");



}




public

void

()



{



Console
.
WriteLine
("
pi
*
r
^2");



}




public

void

Draw
()



{



Console
.
WriteLine
("
Circle
");


}

}


Здесь был объявлен интерфейс

IDrawable
, который предоставляет метод для рисования объекта. Этот
интерфейс может реализовать, например, класс
Image
. Классы

Image

и

Circle

совсем разные
сущности, и они не
имеют общего базового класса, но мы можем создать список указателей на интерфейс

IDrawable
, и работать с
такими объектами, как с однотипными (с одинаковым интерфейсом). Этот пример с

IDrawable

более наглядно
отображает то, что нам дают и
нтерфейсы. На практике,


стоило бы заменить на абстрактный класс.


Домашнее задание


Создайте интерфейс

ISwitchable
, в котором объявите два метода


включение и выключение. Придумайте и
создайте два класса, которые будут реализовать этот интерф
ейс.


В этом уроке мы рассмотрим с вами еще один способ реализации полиморфизма в Си
-
шарп


перегрузку
методов.


Перегрузка методов



это объявление в классе методов с одинаковыми именами при этом с различными
параметрами.



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


Пример того, как может быть перегружен метод:



{



CC тело метода

}

public void SomeMetOod(int a) CC от первого отличается наличием параметра

{



CC тело метода

}

public

void SomeMetOod(string s) CC от второго отличается типом параметра

{



CC тело метода

}

public int SomeMetOod(int a, int b) CC от предыдущих отличается количеством параметров
(плюс изменен тип возврата)

{



CC тело метода




}


Пример того,
как не может быть перегружен метод:



{



CC тело метода

}

public void SomeMetOod(int b) CC имени параметра недостаточно

{



CC тело метода

}

public int SomeMetOod(int a) CC типа возвращаемого значения недостаточно

{



CC те
ло метода




}



Мы с вами уже сталкивались с перегрузкой методов на практике в уроке
Конструкторы в Си
-
шарп
, когда
создавали несколько ко
нструкторов
-

без параметров и с параметрами.



На практике перегрузка методов в Си
-
шарп встречается на каждом шагу. Например, в классе для конвертации
типов

Convert

перегружены почти все методы. Метод

ToInt32()

может принимать параметр различного типа


b
ool, float, double, byte, char

и т.д. Для каждого варианта параметра метода необходима своя реализация
конвертации соответственно, здесь и используется перегрузка метода ToHnt32(). Этот пример очень хорошо
показывает преимущество, которое дает нам перегруз
ка


мы просто вызываем метод с универсальным
именем

ToInt32()
, не задумываясь параметр какого типа мы передаем. Без перегрузки этот метод мог бы
разделиться на методы с разными именами:

oInt32()

и т.д.


Приведу еще простой пример перегрузки. Метод будет добавлять аргументы друг к другу и выводить результат
(с числами это обычное сложение, с символами объединение в строку):


public static void AddAndDisplay(int a, int b)

{



Console.Write
Line(a + b);

}


public static void AddAndDisplay(char a, char b)

{



Console.WriteLine(a.ToString() + b.ToString());

}

static void Main(string[] args)

{



AddAndDisplay(5, 8); // 13



AddAndDisplay('C', '#'); // "C#"



Console.ReadKey();

}



Мы просто
задаем параметры метода, а компилятор сам решает, какой вариант
метода

AddAndDisplay

необходимо вызвать.


Домашнее задание


Имеется список имен. Создайте метод, который будет выводить на экран эти имена через запятую. Перегрузите
этот метод так, чтобы можн
о было изменять разделитель


вместо запятых между именами любой символ,
переданный параметром.

В этом уроке мы поговорим о еще одном из базовых принципов ООП


инкапсуляции.


Скажу сразу, что эта тема сложная для понимания новичку, и для хорошего освоения

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


Инкапсуляция



это скрытие реализации объекта от конечного пользователя, которое в Си
-
шарп

осуществляется при помощи модификаторов доступа (
private, public
…). Конечным пользователем объекта здесь
выступает либо объект наследник, либо программист.


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


А теперь давайте будем разбираться, зачем нужна инкапсуляция, зачем что
-
то скрывать. Представим что объект
это какая
-
либо вещь (телефон, автомобиль…), так вот инкапсуляция позволяет созд
ать корпус этой вещи.
Возьмем для примера мобильный телефон. У него есть различные кнопки, другие элементы интерфейса, и то,
что внутри (схемы, соединения…). Если сравнивать с объектом в программировании, то элементы интерфейса
это публичные методы, а то ч
то внутри


приватные члены объекта. Когда мы нажимаем какую
-
либо кнопку на
телефоне, нам нет необходимости знать, какие при этом процессы происходят внутри телефона, мы ждем
только результата. В программировании мы вызываем какой
-
либо метод, этот метод мо
жет вызывать другой,
внутренний, вспомогательный метод, и в итоге мы получаем результат. Этот вспомогательный метод нет
смысла вызывать напрямую, это может сынициировать какой
-
либо сбой в программе, поэтому доступ к нему
стоит ограничить.



Вот представьте

себе телевизор без корпуса. Человек хочет переключить канал, подходит к телевизору, и
вместо того, чтобы нажать кнопку переключения канала, он выдергивает какой
-
то проводок (ну не имел он
никогда дела с телевизорами или пьяный). Канал не переключился, рез
ультат не тот. В программировании это
равноценно вызову вспомогательного метода, непредназначенного для прямого вызова. Человеку свойственно
ошибаться, инкапсуляция позволяет избавить программиста от допущения подобного рода ошибок.


Для реализации инкапсу
ляции в Си
-
шарп используются модификаторы доступа. Какие они бывают я уже писал
в уроке

Классы в Си
-
шарп
, но повторюсь и здесь:


-

public



доступ к члену возможен из любого места одной сборки, либо из другой сборки, на которую есть
ссылка;

-

protected



доступ к члену возможен только внутри класса, либо в классе
-
наследнике (при наследовании);

-

internal



доступ к члену возможен то
лько из сборки, в которой он объявлен;

-

private



доступ к члену возможен только внутри класса;

-

protected internal

-

доступ к члену возможен из одной сборки, либо из класса
-
наследника другой сборки.


Какой модификатор и где использовать, так однозначно
объяснить невозможно. Всё зависит от конкретной
задачи, ситуации. Знаю, что выбор модификатора является одной из самых частых проблем новичков. Скажу,
что для начала главное разобраться с тремя модификаторами


public, protected и private. Вся выше написан
ная
теория должна помочь вам в их освоении, а закрепление знаний придет только с практикой. Но одну подсказку
таки скажу. Простые поля класса почти никогда не объявляются как public, придерживайтесь этой нормы.


А для тех, кто думает «Это же я пишу програм
му, я всё в ней понимаю, и знаю, что и как использовать, зачем с
этими модификаторами заморачиваться», скажу что в маленьких программах, которые вы пишите один, можно
было бы на это внимания не обращать, но если дело дойдет до программ побольше, если вы бу
дете работать в
команде, на работе, и ваш код будут использовать другие, без инкапсуляции не обойтись.


В качестве

домашнего задания
, я попрошу вас проанализировать программы, которые вы писали до этого,
понять правильно ли вы там использовали модификаторы

доступа. Если не уверены, как должно быть,
спрашивайте в комментариях.


Известно, что в Си
-
шарп существует ряд операторов для работы со встроенными типами данных. Это
операторы «+», «
-
», «!», «==», «!=» и т.д. Например, бинарный оператор «+» выполняет опе
рацию сложения над
численными типами данных. Этот же самый оператор над строками выполняет конкатенацию (склеивание двух
строк). Это происходит потому, что оператор «+» перегружен в классе String.


Простыми словами,
перегрузка оператора

-

это реализация
своего собственного функционала этого оператора
для конкретного класса.


Перегрузка оператора реализуется похожим способом на

перегрузку методов
. Здесь исп
ользуется ключевое
слово

operator
. Общая структура перегрузки операторов имеет следующий вид:


Перегрузка унарного* оператора:


public static Lвозвращаемый_тип] operator Lоператор](Lтип_операнда] Lоперанд])

{


CCфункционал оператора

}



Перегрузка бинарно
го* оператора:


public static Lвозвращаемый_тип] operator Lоператор](Lтип_операнда1] Lоперанд1],
Lтип_операнда2] Lоперанд2])

{


CCфункционал оператора

}



*Что такое унарные и бинарные операторы читайте в уроке

Арифметические и логические операции


Модификаторы

public

и

static

являются обязательными. На месте Lоператор] может стоять любой оператор
,
который можно перегрузить. Не все операторы в Си
-
шарп разрешается перегружать. Ниже наведены операторы
которые можно перегружать, и те которые нельзя:


Можно перегружать


Унарные операторы: +,
-
, !, ++,

, true, false

Бинарные операторы: +,
-
, *, /, %, &
, |, ^, , ,5 ;䀀,5 ;䀀, ==, !=, , &#x,8 4;, =, &#x=-4,; 40;=


Нельзя перегружать


[]


функционал этого оператора предоставляют индексаторы

()


функционал этого оператора предоставляют методы преобразования типов

+=,
-
=, *=, C=, %=, &=, |=, ^=, <<=, >>= краткие формы оператора п
рисваивания
будут автоматически доступны при перегрузке соответствующих операторов (+,
-
, * …).


Перегрузка бинарных операторов


Приведу пример перегрузки оператора «+» для сложения объектов класса деньги (
Money
). Деньги
характеризуются количеством и валют
ой. Если добавлять рубли к рублям, то всё будет корректно. Но если
необходимо суммировать доллары и рубли это уже нужно как
-
то обрабатывать. В случае разных валют можно
просто выбросить исключение, что валюты разные, или же переводить одну валюту по курсу
в другую и
суммировать деньги в одной валюте. Ниже в примере простой вариант с выбросом

исключения
:


public class Money

{



public







public Money(decimal amount, string unit)



{



Amount = amount;



Unit = unit;



}



public static Money operator +(Money a, Money b) //
перегрузка

оператора

«+»



{



if (a.
Unit != b.Unit)



throw new InvalidOperationException("
Нельзя

суммировать

деньги

в

разных

валютах
");







}


}

class Program

{



static void Main(string[] args)




{




Money myMoney = new Money(
100, "USD");



Money yourMoney = new Money(100, "RUR");



Money hisMoney = new Money(50, "USD");



Money sum = myMoney + hisMoney; // 150 USD



sum = yourMoney + hisMoney; //
исключение

-

разные

валюты




Console.ReadLine();



}

}



Перегру
зка унарных операторов


Возьмем программу с первого примера с деньгами, и перегрузим там унарные операторы «++», «
--
»
(добавлениеCвычитание 1 единицы денег):


public class Money

{










public Money(decimal amount, string unit)



{



Amount = amount;



Unit = unit;



}



public static Money operator +(Money a, Money b)



{



if (a.Unit != b.Unit)



throw new InvalidOperationException("
Нельзя

суммировать

деньги

в

разны
х

валютах
");







}



public static Money operator ++(Money a) //
перегрузка

«++»



{



a.Amount++;






}



public static Money operator
--
(Money a) //
перегрузка

«
--
»



{



a.Amoun
t
--
;






}


}

class Program

{



static void Main(string[] args)




{




Money myMoney = new Money(100, "USD");



myMoney++; // 101 USD



Console.ReadLine();



}

}



Также существует возможность перегрузки самого операторного метод
а. Это означает что
в классе может быть несколько перегрузок одного оператора при условии что входные
параметры будут отличаться типом данных (например, когда необходимо сложить объект
класса и строку):


public class Money

{











public Money(decimal amount, string unit)



{



Amount = amount;



Unit = unit;



}



public static Money operator +(Money a, Money b)


{



if (a.Unit != b.Unit)



throw new InvalidOperation
Exception("
Нельзя

суммировать

деньги

в

разных

валютах
");







}



public static string operator +(string text, Money a)



{






}


}

class Program

{



static
void Main(string[] args)




{






Money myMoney = new Money(100, "USD");






Console.WriteLine("
У

меня

сейчас

" + myMoney); // "
У

меня

сейчас

100 USD"



Console.ReadLine();



}

}



Домашнее задание


Задание будет базироваться на примере в этом у
роке. Необходимо реализовать второй вариант сложения денег


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

Money

оператор сравнения «==» (при перегрузке данного оператора, обязательной является и перегрузка
противоположного ему оператора «!=»).


Как известно, в Си
-
шарп все классы являются наследниками базового класса

object
. В нем есть три виртуальных
метода

ToString
,

Equals

и
. В этом уроке мы поговорим с вами о последних двух методах, а также
об операторе «==».


Скажу сразу, что вопрос разницы между оператором равенства «==» и методом
Equals

является классическим
вопросом на собеседовании на

вакансию программиста Си
-
шарп.


Оператор равенства «==»


По умолчанию при работе с ссылочными типами данных (все классы кроме
string
, интерфейсы, делегаты)
оператор «==» проверяет равенство ссылок. Он возвращает

true
, когда обе ссылки указывают на один объ
ект, в
противном случае


false
. Приведу код, который демонстрирует работу данного оператора с ссылочными
типами:


static void Main(string[] args)

{



object o1 = new object();



object o2 = new object();



object o3 = o1;



Console.WriteLine(o1 == o2)
; // false



Console.WriteLine(o1 == o3); // true


}


Здесь создается два объекта, ссылки на которые записываются в переменные o1 и o2. Дальше ссылка o1
копируется в переменную o3 (o1 и o3 указывают на один объект). В итоге имеем

false

при сравнении
ссылок o1
и o2, и true при o1 и o3.


Метод Equals


Метод

Equals

принимает один аргумент


объект, который будет сравниваться с текущим объектом, и
определяет, равны ли между собой эти объекты. Здесь уже идет речь о равенстве полей объектов, а не ссылок.
Эт
от метод виртуальный, и его базовая реализация это просто проверка равенства ссылок оператором «==». Но
когда мы создаем некий класс, и нам необходимо реализовать возможность проверки идентичности объектов,
следует переопределить именно данный метод, а не
воспользоваться

перегрузкой оператора

«==», чтобы не
спутывать базовые назначения этих инструментов сравнивания.


Перегрузка метода Equals


При переопре
делении метода

Equals

следует позаботиться о том, чтобы этот метод возвращал

false

в случаях,
когда в метод передано значение

NULL
, когда переданный объект нельзя привести к типу текущего объекта, ну
и когда поля объектов отличаются.


Возьмем уже знакомый
нам класс Money с предыдущего урока, и переопределим в нем метод

Equals
:


public class Money

{










public Money(decimal amount, string unit)



{



Amount = amount;



Unit =
unit;



}



public override bool Equals(object obj)



{



if (obj == null)






Money m = obj as Money; //
возвращает

null
если

объект

нельзя

привести

к

типу

Money



if (m as Money == null)







Amount == this.Amount && m.Unit == this.Unit;




}


}

class Program

{



static void Main(string[] args)



{



Money m1 = new Money(100, "RUR");



Money m2 = new Money(100, "RUR");




Money m3 = new Money(100, "USD");



Money m4 = m1;



Co
nsole.WriteLine(m1.Equals(m2)); // true



Console.WriteLine(m1.Equals(m3)); // false




Console.WriteLine(m1 == m2); // false




Console.WriteLine(m1 == m4); // true




Console.ReadLine();



}

}


Как видим, в коде выше метод

Equals

и оператор
«==» работают соответственно своим базовым определениям.


Также для повышения производительности при переопределении метода
Equals

рекомендуется перегружать его
реализацией с типом аргумента соответствующему классу, в котором он переопределяется:



public b
ool Equals(Money obj) //
аргумент

типа

Money

{



if (obj == null)









}




Метод GetHasOCode


Данный метод, как следует из его названия, возвращает хеш
-
код.

Хеш
-
код
это число
соответствующее значению
объекта. Это число мы получаем в результате работы некоторого метода, который должен обладать
следующими свойствами:


-

он должен возвращать одинаковый хеш
-
код каждый раз при вызове для одного и того же объекта.

-

если имеется два
равных (эквивалентных) объекта, то хеш
-
код для них должен быть одинаковым. Только это
не означает, что если объекты неравны, то их хеш
-
коды обязательно будут разными.


Метод

GetHashCode

используется в таких структурах, как хэш
-
таблицы (HasOtable). Это мы с
ейчас
рассматривать не будем, но корректность их работы стоит обеспечивать. Методы

Equals

и


тесно
связанны между собой, при переопределении одного из них, следует переопределять и другой. Базовая
реализация метода

GetHashCode

в классе

object

оч
ень условная, и она не обеспечивает второе свойство, когда
одинаковые объекты имеют одинаковые хеш
-
коды.



static void Main(string[] args)

{



Money m1 = new Money(100, "RUR");



Money m2 = new Money(100, "RUR");




// 456...







Console.ReadLine();

}


Чтобы

это

исправить
,
мы

переопределяем

метод

GetHashCode
,
и

возвращаем

хеш
-
код

каким
-
либо

способом
,
зависящим

от

поля
/
полей

объекта
:


public class Money

{



public







public Money(decimal amount, string unit)



{



Amount = amount;



Unit = unit;



}




public override bool Equals(object obj)



{



if (obj == null)







Money m = obj as Money;




if (m as Money == null)











}

public bool Equals(Money obj) //
аргумент

типа

Money



{



if (obj == null)







obj.Amount == this.Amount && obj.Unit == this.Unit;



}


public override int GetHashCode()



{



int unitCode;



if (Unit == "RUR")



unitCode = 1;



else unitCode = 2;






}


}

class Program

{



static
void Main(string[] args)



{



Money m1 = new Money(100, "RUR");



Money m2 = new Money(100, "RUR");



Money m3 = new Money(100, "USD");











Cons



Console.ReadLine();




}

}


Здесь в качестве хеш
-
кода возвращается количество денег (целая часть) плюс код валюты. В результате теперь
второе условие выполняется.


Домашнее задание


Создайте класс окружность с
полями координаты центра и радиус и переопределите в нем корректно
методы

Equals

и

. Окружности равны если у них одинаковые координаты центра и радиусы.


Регулярное выражение



это некий шаблон, составленный из символов и спецсимволов, который
позволяет
находить подстроки соответствующие этому шаблону в других строках. Спецсимволов и различных правил их
комбинирования есть очень много, поэтому регулярные выражения можно даже назвать таким себе отдельным
языком программирования. Те, кто пользовал
ся поиском по файлам в Windows могут знать, что для того чтобы
найти файлы только заданного расширения, задается шаблон типа «*.txt». Здесь «*»
-

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


Что можно делать, используя регулярные выражения


Регулярные выражения предоставляют массу возможностей, некоторые из них:


-

заменять в строке все одинаковые слова другим словом, или удалять такие слова;

-

выделять из строки необходимую часть. Например,

из любой ссылки
-
sOarp_pereopredelenie_metodov.Otml) выделять
только доменную часть (mycsOarp.ru);

-

проверять соответствует ли строка заданному шаблону. Например, проверять, правильно ли введен

email,
телефон т.д.;

-

проверять, содержит ли строка заданную подстроку;

-

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


Начало работы с регулярными выражениями


Для того
, чтобы работать с регулярными выражениями необходимо подключить в начале программы
пространство имен

using System.Text.RegularExpressions;

В Си
-
шарп работу с регулярными выражениями
предоставляет класс

Regex
. Создание регулярного выражения имеет следующий

вид:


Regex myReg = new Regex(Lшаблон]);



Здесь Lшаблон]


это строка содержащая символы и спецсимволы.

У

Regex

также есть и второй конструктор, который принимает дополнительный параметр


опции поиска. Это
мы рассмотрим далее.


Приведу простой пример
программы с использованием регулярных выражений:


static void Main(string[] args)

{



string data1 = "Петр, Андрей, Николай";



string data2 = "Петр, Андрей, Александр";



Regex myReg = new Regex("Николай"); CC создание регулярного выражения



Console.
WriteLine(myReg.IsMatch(data1)); // True



Console.WriteLine(myReg.IsMatch(data2)); // False



Console.ReadKey();

}



Здесь в качестве шаблона выступает однозначная строка "Николай". Дальше был использован метод HsMatcO,
который проверят, содержит ли
заданная строка (data1, data2) подстроку соответствующую шаблону.


Методы класса Regex


Рассмотрим кратко методы для работы с регулярными выражениями:


IsMatch



проверяет содержит ли строка хотя бы одну подстроку соответствующую шаблону регулярного
выраже
ния. Работа этого метода показана в примере выше.


Match



возвращает первую подстроку, соответствующую шаблону, в виде объекта класса MatcO. Класс MatcO
предоставляет различную информацию о подстроке


длину, индекс, само значение и другое.


static void M
ain(string[] args)

{



string data1 = "Петр, Андрей, Николай";




Regex myReg = new Regex("Николай");




Match match = myReg.Match(data1);



Console.WriteIine(matcO.Value); CC "Николай"



Console.WriteLine(match.Index); // 14



Console.ReadKey();

}



Matches



возвращает все подстроки соответствующие шаблону в виде коллекции
типа

MatchCollection
. Каждый элемент этой коллекции типа

Match
.


static void Main(string[] args)

{



string data1 = "Петр, Николай, Андрей, Николай";




Regex myReg = new
Regex("Николай");




MatchCollection matches = myReg.Matches(data1);



Console.WriteLine(matches.Count); // 2



foreach (Match m in matches)



Console.WriteIine(m.Value); CCвывод всех подстрок "Николай"



Console.ReadKey();

}



Replace



возвращает
строку, в которой заменены все подстроки, соответствующие
шаблону, новой строкой:


static void Main(string[] args)

{



string data1 = "Петр, Николай, Андрей, Николай";




Regex myReg = new Regex("Николай");



data1 = myReg.Replace(data1, "Максим");



C
onsole.WriteIine(data1); CC"Петр, Максим, Андрей, Максим"




Console.ReadKey();

}



Split

-

возвращает массив строк, полученный в результате разделения входящей строки в
местах соответствия шаблону регулярного выражения:


static void Main(string[] args)

{



string data1 = "Петр,Николай,Андрей,Николай";




Regex myReg = new Regex(",");



stringL] names = myReg.Split(data1); CC массив имен




Console.ReadKey();

}



Специальные символы


В примерах выше рассматривались очень простые, однозначные регулярные

выражения без использования
спецсимволов. Спецсимволов существует достаточно много, их описание приведу ниже в таблицах:


Классы символов


Обозначение

Описание

Шаблон

Соответствие

Lгруппа_символов]

Любой из перечисленных в скобках
символов. Используя
тире можно указать
диапазон символов, например, La
-
f]
-

то же
самое, что Labcdef]

[abc]

«a» в «and»

L^группа_символов]

Любой символ, кроме перечисленных в
скобках

[^abc]

«n», «d» в «and»

\
d

Цифра. Эквивалентно L0
-
9]

\
d

«1» в «data1»

\
D

Любой символ,
кроме цифр. Эквивалентно
[^0
-
9]

\
D

«y» в «2014y»

\
w

Цифра, буква (латинский алфавит) или
знак подчеркивания. Эквивалентно L0
-
9a
-
zA
-
Z_]

\
w

«1», «5», «с» в
«1.5с»

\
W

Любой символ, кроме цифр, букв
(латинский алфавит) и знака
подчеркивания. Эквивалентно
[^0
-
9a
-
zA
-
Z_]

\
W

«.» в «1.5с»

\
s

Пробельный символ (пробел, табуляция,
перевод строки и т. п.)

\
s

« » в «c sOarp»

\
S

Любой символ, кроме пробельных

\
S

«c» «s» «O» «a»
«r» «p»
в

«c
sOarp»

.

Любой символ, кроме перевода строки.
Для поиска любого символа,
включая
перевод строки, можно использовать
конструкцию L
\
s
\
S]

c.harp

«csOarp» в
«mycsOarp»


Символы повторения


Обозначение

Описание

Шаблон

Соответствие

*

Соответствует предыдущему элементу ноль
или более раз

\
d*.

«a», «1b», «23c » в
«a1b23c»

+

Соответствует предыдущему элементу один
или более раз

\
d+.

«1b», «23c » в
«a1b23c»

?

Соответствует предыдущему элементу ноль
или один раз

\
d?
\
D

«a», «1b», «3с» в
«a1b23c»

{n}

Соответствует предыдущему элементу,
который повторяется ровно n раз

\
d{2}

«43»,

«54», «82» в
«2,43,546,82»

{n,}

Соответствует предыдущему элементу,
который повторяется минимум n раз

\
d{2,}

«43», «546», «82» в
«2,43,546,82»

{n,m}

Соответствует предыдущему элементу,
который повторяется минимум n раз и
максимум m

\
d{2,}

«43», «546»,
«821» в

«2,43,546,8212»


Символы привязки


Обозначение

Описание

Шаблон

Соответствие

^

Соответствие должно находиться в начале строки

^
\
d{2}

«32» в
«32,43,54»

$

Соответствие должно находиться в конце строки
или до символа
\
n при многострочном поиске

\
d{2}$

«54» в
«32,43,54»

\
b

Соответствие должно находиться на границе
\
b
\
d{2}

«32», «54» в «32
алфавитно
-
цифрового символа (
\
w) и не
алфавитно
-
цифрового (
\
W)

a43 54»

\
B

Соответствие не должно находиться на границе

\
B
\
d{2}

«43» в «32 a43
54»

\
G

Соответствие должно находиться на позиции
конца предыдущего соответствия

\
G
\
d

«3», «2», «4» в
«324.758»


Символы выбора


Обозначение

Описание

Шаблон

Соответствие

|

Работает как логическое «ИЛИ»
-

соответствует первому иCили второму
шаблону

one|two

«one», «two»
в

«one two
tOree»

(группа_символов)

Группирует набор символов в единое
целое для которого дальше могут
использоваться + * ? и т.д. Каждой такой
группе назначается порядковый номер
слева направо начиная с 1. По этому
номеру можно ссылаться на
группу
\
номер_группы

(one)
\
1

«oneone» в
«oneone
onetwoone»

(?:группа_символов)

Та же группировка только без назначения
номера группы

(?:one){2}

«oneone» в
«oneone
onetwoone»


Другие символы


Обозначение

Описание

Шаблон

Соответствие

\
t

Символ табуляции

\
t


\
v

Символ вертикальной табуляции

\
v


\
r

Символ возврата каретки

\
r


\
n

Символ перевода строки

\
n


\
f

Символ перевода страницы

\
f


\

Символ, который позволяет экранировать
специальные символы, чтобы те воспринимались
буквально. Например, чтобы было

соответствие
символу звёздочки, шаблон будет выглядеть так
\
*

\
d
\
.
\
d

«1.1», «1.2» в
«1.1 1.2»


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


Пример программы, которая пр
оверят корректность электронного адреса (шаблон тот же, что и на странице
регистрации на этом сайте):


static void Main(string[] args)

{



Regex myReg = new Regex(@"[A
-
Za
-
z]+[
\
.A
-
Za
-
z0
-
9_
-
]*[A
-
Za
-
z0
-
9][email protected][A
-
Za
-
z]+
\
.[A
-
Za
-
z]+");



Console.WriteLine(myReg.IsMatch("[email protected]")); // True



Console.WriteLine(myReg.IsMatch("[email protected]")); // False



Console.WriteLine(myReg.IsMatch("@email.com")); // False



Console.ReadKey();

}



Здесь перед началом строки регулярного выражения

стоит символ «@» который указывает комплятору
воспринимать все символы буквально. Это необходимо, чтобы корректно воспринимался символ «
\
».


Параметры поиска


Здесь мы поговорим о втором конструкторе

Regex
, который принимает в качестве второго аргумента
значение
перечисления

RegexOptions
. В этом перечисление есть следующие значения:


IgnoreCase



игнорирование регистра при поиске. Находит соответствия независимо прописными или
строчными буквами в строке написано слово;

RightToLeft



поиск будет выполнен с
права налево, а не слева направо;

Multiline



многострочный режим поиска. Меняет работу спецсимволов «^» и «$» так, что они соответствуют
началу и концу каждой строки, а не только началу и концу целой строки;

Singleline



однострочный режим поиска;

Culture
Invariant

-

игнорирование национальных установок строки;

ExplicitCapture



обеспечивается поиск только буквальных соответствий;

Compiled



регулярное выражение компилируется в сборку, что делает более быстрым его исполнение но
увеличивает время запуска;

Ig
norePatternWhitespace



игнорирует в шаблоне все неэкранированные пробелы. С этим параметром шаблон «a
b» будет аналогичным шаблону «ab»;

None



использовать поиск по умолчанию.


Пример программы с использованием параметра поиска (игнорирование регистра):


static void Main(string[] args)

{



string data = "nikolay, sergey, oleg";



Regex myRegIgnoreCase = new Regex(@"Sergey", RegexOptions.IgnoreCase);



Regex myReg = new Regex(@"Sergey");



Console.WriteLine(myRegIgnoreCase.IsMatch(data)); // True



Co
nsole.WriteLine(myReg.IsMatch(data)); // False



Console.ReadKey();

}



Если необходимо установить несколько параметров, тогда они разделяются оператором поразрядного «ИЛИ»
-

«|»


Regex myReg = new Regex(@"Sergey", RegexOptions.IgnoreCase |
RegexOptions.IgnorePatternWhitespace);



Приведу еще один пример использования регулярных выражений. Создадим метод,
который будет принимать ссылку, а возвращать только доменное имя:


public static string GetDomain(string url)

{




Regex re = new Regex("h
ttp://", RegexOptions.IgnoreCase);



url = re.Replace(url, ""); //
удаляем

часть

http://




Regex reWww = new Regex(@"www
\
.", RegexOptions.IgnoreCase);



url = reWww.Replace(url, ""); //
удаляем

часть

www.




int end = url.IndexOf("/");



if (end !=
-
1
)



url = url.Substring(0, end);




}

static void Main(string[] args)

{



-



string url2 = "http://www.mycsharp.ru/post/33/2013
-










Console.ReadKey();

}



Домашнее задание


1. Создайте программу, которая будет

проверять корректность ввода логина. Корректным логином будет строка
от 2
-
х до 10
-
ти символов, содержащая только буквы и цифры, и при этом цифра не может быть первой.


2. Создайте фильтр мата. Метод будет принимать исходную строку, и возвращать результат,

где нехорошие
слова будут заменены на «цензура». Обработайте хотя бы одно такое слово, только предусмотрите множество
его форм. Если вы собираетесь результат вашей работы оставить здесь, в комментариях, тогда фильтруйте
любые другие слова, но не маты :)


В уроке 9 была рассмотрена
работа со строками в Си
-
шарп
, сейчас же мы поговорим о их форматировании.
Потребность в форматировании строк возника
ет довольно часто (вывод определенного количества знаков после
запятой для числа, или дата в нужом формате и. т.д.).


В Си
-
шарп возможностью задать форматирование обладают следующие методы:


-

System.String.Format


-

Console.WriteLine


-

StreamWriter.Write


-

ToString


Методы

WriteLine

и

Write

используются для вывода информации в консоль, и при этом дают возможность
отформатировать вывод. Метод

Format

класса
String

предназначен конкретно для форматирования. Он
возвращает отформатированную строку. Разницы меж
ду самим форматированием для этих методов нет.
Форматирование в методе

ToString

можно задать только для чисел и дат.


Общая структура форматирования строк имеет следующий вид:


String.Format("строка формата", arg0, arg1, …, argn);


arg0 и arg1 здесь


аргументы форматирования (числа, строки, даты, и т. д.), из которых в результате будет
создана новая отформатированная строка.


Строка формата может содержать обычные символы, которые будут отображены в том виде, в котором они
заданы, и команды форматирова
ния. Команда форматирования заключается в фигурные скобки и имеет
следующую структуру:


SLномер аргумента], Lширина]:Lформат]}


По Lномеру аргумента] указывается к какому аргументу будет применена данная команда (отсчет аргументов
начинается с нуля).

Lшири
на] задает минимальный размер поля.

Lформат]


спецификатор формата.


Параметры Lширина] и Lформат] не являются обязательными.


Пример

простого

форматирования
:


string formattedString = string.Format("Result is {0}", 5); // "Result is 5"


Здесь на место
команды S0} подставляется 0
-
й аргумент.



int num1 = 5, num2 = 3;

string formattedString = string.Format("{0}+{1}={2}", num1, num2, num1+num2); //
"5+3=8"

Console.WriteLine(formattedString);

Console.ReadLine();



Параметр "ширина"


Иногда необходимо отформ
атировать строки, содержащие числа, чтобы они были выравнены по левому или
правому краю и имели одинаковую длину. К числу, которое имеет меньше символов, чем значение ширины,
будут добавлены пробелы слева (положительная ширина) или справа (отрицательная ши
рина):


Console.WriteLine("Result is {0, 6}", 1.2789);


Console.WriteLine("Result is {0, 6}", 7.54);

Console.WriteLine("Result is {0,
-
6}", 1.2789);

Console.WriteLine("Result is {0,
-
6}", 7.54);


В результате получится, что в первых двух строках числа выро
внены по правому краю, в двух других по
левому:


Result is 1,2789

Result is


7,54

Result is 1,2789

Result is 7,54


Встроенные форматы числовых данных


А теперь мы рассмотрим параметр команды форматирования после двоеточия


формат. Для числовых
аргументов определен целый ряд форматов, все они описаны в таблице 1. Почти для всех форматов можно
также задать точность. Например, формат для чисел с фиксированной точкой «f» по умолчанию выводит не
больше двух знаков после точки. Чтобы задать 4 знака
-

нужно указать после обозначения формата
необходимое число, «f4».


Таблица 1

Специальный
символ

Формат

Описание

c/C

Денежная единица

Указывает количество десятичных
знаков

d/D

Целые числа

Указывает минимальное количество
цифр. При необходимости
добавляются
нули

e/E

Экспоненциальные числа

Указывает количество десятичных
знаков

f/F

Числа с фиксированной точкой

Указывает количество десятичных
знаков

g/G

Форматы eCE и gCG

Использует более короткий формат из
двух: fCF и gCG

n/N

Числа с
фиксированной точкой с
отделением групп разрядов

Указывает количество десятичных
знаков

p/P

Проценты

Умножает число на 100 и выводит со
знаком процентов. Указывает
количество десятичных знаков

r/R

Формат кругового
Форматирует число в строку таким
преобразования. Только
фиксированная
точка

образом, что его можно обратно
преобразовать без потерь точности

x/X

Шестнадцатеричные числа

Указывает минимальное количество
цифр. При необходимости добавляются
нули


Пример использования некоторых из встроенных
форматов:


Console.WriteLine("{0:c}", 5.50); // "5,50
грн
."

Console.WriteLine("{0:c1}", 5.50); // "5,5
грн
."

Console.WriteLine("{0:e}", 5.50); // "5,500000
е
+000"

Console.WriteLine("{0:d}", 32); // "32"

Console.WriteLine("{0:d4}", 32); // "0032"


Console.Wr
iteLine("{0:p}", 0.55); // "55,00%"





Пользовательский формат числовых данных


В таблице 1 были описаны стандартные форматы для числовых данных, но иногда необходимо определить
некоторый собственный формат. Для создания пользовательских форматов
используются специальные символы
(Таблица 2):


Таблица 2

Специальный
символ

Значение

0

Цифра или ноль

#

Цифра

.

Разделитель дроби

,

Разделитель тысяч

%

Процент

e

Экспонента

;

Определяет разделы, которые описывают форматы для положительных,
отрицательных чисел и нуля

\

Экранирование специальных символов. Позволяет вставлять спец
-
символы как текст


Пример использования пользовательских форматов:


Console.WriteLine("{0:0000.00}", 1024.32); // "1024,32"

Console.WriteLine("{0:00000.000}",
1024.32); // "01024,320"

Console.WriteLine("{0:####.###}", 1024.32); // "1024,32"


Console.WriteLine("{0:####.#}", 1024.32); // "1024,3"


Console.WriteLine("{0:#,###.##}", 1024.32); // "1 024,32"

Console.WriteLine("{0:##%}", 0.32); // "32%"

Console.WriteLi
ne("S0:<####.###>;L####.###];ноль}", 1024.32); CC "<1024,32>"

Console.WriteIine("S0:<####.###>;L####.###];ноль}",
-
1024.32); // "[1024,32]"

Console.WriteIine("S0:<####.###>;L####.###];ноль}", 0); CC "ноль"



Здесь стоит отметить, что если количество цифр ц
елой части числа больше чем количество символов «0» или
«#» в формате, целая часть числа всё равно будет выводиться полностью.


Встроенные форматы даты и времени


Для работы с датой и временем существует отдельный набор стандартных форматов.
Данные форматы

наведены в таблице 3:


Таблица 3

Специальный
символ

Формат

Пример

d

Короткая дата

30.06.2014

D

Длинная дата

30 июня 2014 г.

t

Короткое время

22:30

T

Длинное время

22:30:10

f

Длинная датаCкороткое время

30 июня 2014 г. 22:30

F

Длинная датаCдлинное
время

30 июня 2014 г. 22:30:10

g

Короткая датаCкороткое время

30.06.2014 22:30

G

Короткая датаCдлинное время

30.06.2014 22:30:10

M/m

Месяц и день

июня 30

O/o

Обратный

2014
-
06
-
30T22:30:10.0000000

R/r

RFC1123

Mon, 30 Jun 2014 22:30:10
GMT

s

Для
сортировки

2014
-
06
-
30T22:30:10

u

Локальное, в универсальном
формате

2014
-
06
-
30 22:30:10Z

U

GMT

30 июня 2014 г. 19:30:10

Y

Год и месяц

Июнь 2014


Пример использования стандартных форматов даты и времени:


"30.06.2014"


Console.WriteIine("S0:D}", DateTime.Now); CC "30 июня 2014 р."






Console.WriteIine("S0:U}", DateTime.Now); CC "29 июня 2014 р. 23:57:5
3"


Console.WriteIine("S0:Y}", DateTime.Now); CC "Июнь 2014 р."




Пользовательский формат даты и времени


Как и в случае с числовыми данными, для даты и времени можно определять пользовательский формат. Для
этого используются специальные символы, которые
наведены в таблице 4 (на примере даты 30.06.2014
22:30:10.1234):


Таблица 4

Специальный символ

Значениe

Результат

y

Год 0
-
9

4

yy

Год 00
-
99

14

yyyy

Год, 4 цифры

2014

M

Месяц 1
-
12

6

MM

Месяц 01
-
12

06

d

День 1
-
31

0

dd

День 01
-
31

30

h

Час 1
-
12

2

hh

Час 01
-
12

10

H

Час 1
-
24

22

H

Час 01
-
24

22

m

Минута 0
-
59

30

mm

Минута 00
-
59

30

s

Секунда 0
-
59

10

ss

Секунда 00
-
59

10

f


ffffff

Доли секунды

12 (для ff)

F
-
FFFFFF

Доли секунды, если они не равны 0

12 (для ff)

MMM

Сокращенное имя месяца

Июн

MMMM

Имя

месяца

Июнь

ddd

Сокращенное имя дня недели

Пн

dddd

Имя дня недели

понедельник

tt

Маркер для "AM" и "PM" 12
-
часового формата

PM

zz

Смещение временной зоны, короткое

+03

zzz

Смещение временной зоны, полное

+03:00

gg

Эра

A.D.

:

Разделитель времени

22:30:10

/

Разделитель даты

30.06.2014

\

Экранирование



Пример использования пользовательских форматов даты и времени:





"30 30 Пн понедельник"


Console.WriteIine("S0:M MM MMM}", DateTime.Now); CC "6 06 Июн"


Console.WriteLine("{0:HH.mm.ss dd
-
MMM
-
-
Июн
-
2014"






Форматир
ование с использованием метода ToString


Все правила форматирования, описанные выше, могут быть использованы в перегруженном методе

ToString

для
чисел и дат:


Console.WriteLine(DateTime.Now.ToString("dd MMM yyyy")); // 30
Июн

2014



Региональные параметры
CultureInfo


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

CultureInfo

также влияет на разделитель в числах, количество символов после точки по
умолчанию и другое. Иногда возникает потр
ебность изменить это, и для этого используется
CultureInfo
. По
умолчанию

CultureInfo

соответствует настройкам Windows (например российские региональные настройки).
Чтобы задать собственную культуру при форматировании, используя метод

Format
, необходимо в ко
нструктор
этого метода первым аргументом передать объект

CultureInfo
.


В примере ниже текущий день выводится с текущими настройками

CultureInfo
(российскими), с английскими
(английский Соединенных Штатов), и с украинскими:


string formattedString = string.F
ormat(new System.Globalization.CultureInfo("en
-
US"),
"{0:dddd} Money
-


Console.WriteLine("{0:dddd} Money
-

S1:c}", DateTime.Now, 15); CC "понедельник Money
-

15,00 руб."


Console.WriteLine(formattedString); // "Monday Money
-

$15.00"


formatedString = string.Format(new System.Globalization.CultureInfo("uk
-
UA"), "{0:dddd}
Money
-

{1:c}", DateTime.Now, 15);

Console.WriteLine(formattedString); // "
понеділок

Money
-

15,00
грн
."




Домашнее задание


Есть массив дат (с временем) и ма
ссив температур, и они соответствуют друг другу. Температуры в массиве
имеют вид в формате: «26.3 27.1 30 24.7 25». Необходимо вывести информацию таким образом:


Июн 30 (Пн) 09:31 > 26,3 °C

Июн 30 (Пн) 10:31 > 27,1 °C

Июн 30 (Пн) 11:31 > 30,0 °C


Дробная ч
асть температуры должна обязательно быть длиной в один знак, даже если
температура целая.


Перечисление (Enumeration)



это определяемый пользователем целочисленный тип, который позволяет
специфицировать набор допустимых значений, и назначить каждому понят
ное имя.


Для объявления перечисления используется ключевое слово

enum
. Общая структура объявления перечисления
выглядит так:


enum Lимя_перечисления] S Lимя1], Lимя2], … };



Например, создадим перечисление

Directions
, которое будет соответствовать
направлениям движения:


enum Directions { Left, Right, Forward, Back };



Объявив таким образом перечисление, каждой символически обозначаемой константе присваивается
целочисленное значение, начиная с 0 (Ieft = 0, RigOt = 1 …). Это целочисленное значение м
ожно задавать и
самому:


enum

Directions

{
Left
,
Right

= 5,
Forward

= 10,
Back

};



Back в этом примере будет иметь значение 11.


Пример программы с использованием перечисления:


enum Directions { Left, Right, Forward, Back }; //
объявление

перечисления


class Program

{



public static void GoTo(Directions direction)



{



switch (direction)



{



case Directions.Back:



Console.WriteLine("Go back");



break;



case Directions.Forward:



Console.WriteLine("Go forward"
);



break;



case Directions.Left:



Console.WriteLine("Turn left");



break;



case Directions.Right:



Console.WriteLine("Turn right ");



break;



}




}




static void Main(string[] args)



{



Dir
ections direction = Directions.Forward;



GoTo(direction); // "Go forward"



Console.ReadKey();



}

}



Чтобы получить целое значение определенного элемента перечисления, достаточно этот элемент явно привести
к целому типу:


enum Directions : byte {

Left, Right, Forward, Back };



class Program

{



static void Main(string[] args)



{




Console.WriteLine((int)Directions.Forward); // 2



Console.ReadKey();



}

}



По умолчанию в качестве целого типа для

enum

используется

int
. Этот тип можно
и
зменить на любой другой целый тип (кроме

char
), указав после имени перечисления
необходимый тип и разделив двоеточием:


enum Directions : byte { Left, Right, Forward, Back };



Зачем нужны перечисления и где они используются


Главные преимущества, которые
нам дают перечисления это:


-

Гарантия того, что переменным будут назначаться допустимые значения из указанного набора;

-

Когда вы пишите код программы в Visual Studio, благодаря средству HntelliSense будет выпадать список с
допустимыми значениями, что поз
волит сэкономить некоторое время, и напомнить, какие значения можно
использовать;

-

Код становится читабельнее, когда в нем присутствуют понятные имена, а не ни о чем не говорящие числа.


Перечисления очень широко используются в самой библиотеке классов .N
ET. Например, при создании
файлового потока (
FileStream
) используется перечисление

FileAccess
, при помощи которого мы указываем с
каким режимом доступа открыть файл (чтениеCзапись).


Домашнее задание


Создайте класс Кошка. У кошки будет свойство «уровень с
ытости» и метод «съесть что
-
то». Создайте
перечисление Еда (рыба, мышь…). Каждый вид еды должен по
-
разному изменять уровень сытости.



В языке Си
-
шарп все типы данных делятся на две категории


ссылочные типы, и типы значений. Они
отличаются способом хране
ния в памяти, производительностью и другим. В этом уроке мы поговорим, что это
всё означает, а также о передаче параметров в метод по ссылке (ключевые слова

ref

и

out
).


Типы значений


Эту категорию также называют структурными типами. Типы значений хранятс
я в стеке. Стек
-

это область
памяти, которая используется для передачи параметров в методы и хранения определенных в пределах методов
локальных переменных. Данные переменной типа значения хранятся в самой переменной.



К типам значений относятся:



-

Цело
численные типы (
byte, sbyte, char, short, ushort, int, uint, long, ulong
);


-

Типы с плавающей запятой (
float, double
);

-

Тип

decimal
;

-

Тип

bool
;

-

Пользовательские структуры (
struct
);

-

Перечисления (
enum
).


Код ниже показывает, что при присвоении
значения одной переменной значимого типа другой, дальнейшее
изменение одной из переменных не влияет на другую. Так потому, что хранение данных значимого типа
происходит в самой переменной:


static void Main(string[] args)

{



int a = 1;



int b = 2;



b

= a;



a = 3;



Console.WriteLine(a); // 3




Console.WriteLine(b); // 1


}


Ссылочные типы


Переменная ссылочного типа содержит не данные, а ссылку на них. Сами данные в этом случае уже хранятся в
куче. Куча
-

это область памяти, в которой размещаются

управляемые объекты, и работает сборщик мусора.
Сборщик мусора освобождает все ресурсы и объекты, которые уже не нужны.


К ссылочным типам относятся:


-

Классы (
class
);

-

Интерфейсы (
interface
);

-

Делегаты (
delegate
);

-

Тип

object
;

-

Тип

string
.



В коде
ниже был создан простой класс, в котором есть одно поле типа

int
. Дальше была проделана такая же
процедура, как и в случае выше, только результат уже другой. После присвоения одного объекта другому, они
стали указывать на одну и ту же область памяти (меняе
м b


меняется и a):


class Test

{



public int x;

}

class Program

{




static void Main(string[] args)



{



Test a = new Test();



Test b = new Test();



a.x = 1;




b.x = 2;



b = a; //
присвоение

ссылки



b.x = 3;




Console.WriteLine(a.x); // 3



Console.WriteLine(b.x); // 3

}

}


Передача параметров в метод по ссылке. Операторы ref и out


В C# значения переменных по
-
умолчанию передаются по значению (в метод передается локальная копия
параметра, который используется

при вызове). Это означает, что мы не можем внутри метода изменить
параметр из вне:


public static void ChangeValue(object a)

{



a = 2;

}


static void Main(string[] args)

{



int a = 1;



ChangeValue(a);



Console.WriteLine(a); // 1



Console.ReadLin
e();

}


Чтобы передавать параметры по ссылке, и иметь возможность влиять на внешнюю переменную, используются
ключевые слова ref и out.


Ключевое слово ref


Чтобы использовать

ref
, это ключевое слово стоит указать перед типом параметра в методе, и перед пар
аметром
при вызове метода:


public static void ChangeValue(ref int a)

{



a = 2;

}

static void Main(string[] args)

{



int a = 1;



ChangeValue(ref a);



Console.WriteLine(a); // 2



Console.ReadLine();

}


В этом примере мы изменили значение внешней п
еременной внутри метода.


Особенностью ref является то, что переменная, которую мы передаем в метод, обязательно должна быть
проинициализирована значением, иначе компилятор выдаст ошибку «Use of unassigned local variable 'a'». Это
является главным
отличием

ref

от

out
.



Ключевое слово out


Out

используется точно таким же образом как и

ref
, за исключением того, что параметр не обязан быть
проинициализирован перед передачей, но при этом в методе переданному параметру обязательно должно быть
присвоено
новое значение:


public static void ChangeValue(out int a)

{



a = 2;

}

static void Main(string[] args)

{



int a;



ChangeValue(out a);



Console.WriteLine(a); // 2



Console.ReadLine();

}


Если не присвоить новое значение параметру

out
, мы получим о
шибку «TOe out parameter 'a' must be assigned to
before control leaves tOe current metOod»


Производительность


Учитывая тот факт, что по умолчанию в метод передаются параметры по значению и создаются их копии в
стеке, при использовании сложных типов данны
х (пользовательские структуры), или если метод вызывается
много раз, это плохо скажется на производительности. В таком случае также стоит использовать ключевые
слова

ref

и

out
.


Если говорить в целом о ссылочных типах и типах значений, то
производительность приложения упадет, если
использовать только ссылочные типы. На создание переменной ссылочного типа в куче выделяется память под
данные, а в стеке под ссылку на эти данные. Для типов значений память выделяется только в стеке. Время на
раз
мещение данных в стеке меньше, чем в куче, это также идет в плюс типам значений в плане
производительности.


Ссылочные типы и типы значений, которые рассматривались в предыдущем уроке, имеют еще одно отличие.
Ссылочные типы могут принимать значение null,
типы значений


нет.


Null указывает на то, что значение неизвестно, или, другими словами, значения нет.


Значение

null

вы уже встречали, думаю, не раз. Например, когда объявляли массив и пытались работать с ним,
не создав его при помощи оператора

new
. Так
ая ссылка (имя) имела значение

null
, и выбрасывалось
исключение

NullReferenceException
.


Иногда новички в программировании могут воспринимать

null

как число 0 (ноль), но это совсем не так. Число
ноль вполне информативное значение,
null

же говорит нам о том,

что значения нет.


Ниже в примере показано, что для ссылочного типа мы можем задать

null
, а для типа значений нет:


static void Main(string[] args)

{



Object a = null; //
нормально



int b = null; //
ошибка
, int
не

nullable
тип


}



Nullable
-
типы


Иногда бывают ситуации, когда необходимо чтобы тип значений мог принимать
null
, и это можно сделать,
указав знак вопроса (?) после имени типа, при объявлении переменной:


static void Main(string[] args)

{




int? a = null;



double? b = null;



bool? c =

null;


}


Nullable
-
типы могут пригодиться при работе с базой данных. Некоторые поля таблицы базы данных могут
принимать значение

null
. Например, значение булевого поля любит ли человек кошек может оставаться
неизвестным. Или более практический пример. На
главной странице этого сайта есть лента с уроками, и в ней
несколько последних, остальные на страницах 2,3,…Номер страницы передается аргументом в Ottp запросе
("…?page=2"), но для первого захода на сайт страницу 1 указывать в запросе не очень красиво, исп
ользуя
nullable
-
тип, переменная

int
, которая отвечает за номер страницы, в таком случае принимает значение

null
.


Оператор ?? (null
-
объединение)


Оператор null
-
объединения ?? немного похож на

тернарный оператор
. Он имеет следующую структуру:


Lоперанд1] ?? Lоперанд2];



?? возвращает операнд1 в случае если тот не равен значению

null
, иначе возвращает
операнд2.


static void Main(string[]
args)

{



int? a = 1;



int? b = null;



Console.WriteLine(a ?? 3); // 1



Console.WriteLine(b ?? 3); // 3



Console.ReadLine();

}


Левый

операнд
,
который

сравнивается

со

значением

null,
обязательно

должен

быть

nullable
-
типа
,
иначе

получим

ошибку

"Ope
rator '??' cannot be applied to operands of type
'int' and 'int'".


Домашнее задание


Создайте метод, который будет выводить информацию о количестве детей у человека. Метод принимает имя
человека и количество (nullable). Метод должен выводить: неизвестно,
нет детей и сообщение о количестве
детей.


В этом небольшом уроке мы поговорим об альтернативе классам
-

структурах.


Структура



это более простая версия классов. Все структуры наследуются от базового
класса

и являются

типами значений
, тогда как

классы

-

ссылочные типы. Структуры
отличаются от классов следующими вещами:


-

Структура не может иметь конструктора без параметров (конструктора по умолчанию);

-

Поля структуры нельзя инициализировать, кроме случаев, когда поля статиче
ские.

private int x = 0; CC в структуре недопустимо;

-

Экземпляры структуры можно создавать без ключевого слова new;

-

Структуры не могут наследоваться от других структур или классов. Классы не могут наследоваться от
структур. Структуры могут реализовывать

интерфейсы;

-

Так как структуры это типы значений, они обладают всеми свойствами подобных типов (передача в метод по
значению и т.д.), в отличии от ссылочных типов;

-

Структура может быть

nullable

типом.


Структуры

объявляются

при

помощи

ключевого

слова

struct
:


public struct Book

{



public string Name;



public string Year;




public string Author;


}



Экземпляр структуры можно создавать без ключевого слова

new
:


static void Main(string[] args)

{


Book b;


b. Name = "BookName";

}



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


Примерами структур в стандартной библиотеке классов .Net являются такие типы как

int, float, doub
le, bool

и
другие. Также


(точка),

Color
.


Домашнее задание


Создайте программу, которая будет находить окружность (структура) у которой радиус максимально близкий к
среднему значению радиусов окружностей из списка.


Несколько следующих уроков будут посвящены теме
сетевого программирования.

Это достаточно большая
область программирования, и для начала ее освоения необходимо разобраться с базовыми понятиями и
терминологией.


Компьютерная сеть



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


Сети классифицируются по такому признаку как архитектура. Наиболее распространенная
архитектура

«клиент
-
с
ервер»
, о которой многие не раз уже слышали.


Рассмотрим аналогию архитектуры клиент
-
сервер в ситуации из жизни. Человек идет в магазине за хлебом. В
данном случае клиентом выступает покупатель, который говорит, что хочет купить хлеб (запрашивает некий
рес
урс, услугу). Продавец в этой ситуации выступает сервером, который обрабатывает запрос (какой продукт,
деньги заплатили, или нет), и выдает ответ (хлеб если заплатили, или до свидания). Покупателей много


клиентов, и всех обслуживает один продавец


серве
р.


Физически клиентом и сервером могут выступать компьютеры и программное обеспечение. Обмен данными
между клиентом и сервером осуществляется посредством сетевых протоколов (Ottp, ftp, pop3, smtp, tcp, udp…).


Протокол



сводка правил и методов, по которы
м будут передаваться данные между узлами сети. Протокол
обеспечивает способ передачи информации и обработку ошибок.


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


#

Уровень

Примеры протоколов

4

Прикладной (Application layer)

HTTP, SMTP, FTP, DNS

3

Транспортный (Transport layer)

TCP, UDP

2

Сетевой (Hnternet layer)

IP

1

Канальный (Iink layer)




Работу стека протоколов можно описать следующей ситуацией.
Один начальник компании хочет передать
посылку другому начальнику компании. Первый сообщает второму, что пришлет посылку, и второй одобряет.
Эти договоренности начальников происходили посредством протокола наивысшего уровня в стеке. Дальше
первый начальник

сообщает секретарю, что надо сделать и этот секретарь связывается с секретарем второго
начальника и говорит «ожидайте курьера, который доставит посылку». Общение секретарей было уже
протоколом ниже в стеке. Дальше секретарь вызывает курьера и говорит, что

и куда доставить, и тот уже на
самом нижнем уровне стека осуществляет передачу данных. Добраться до второй компании он может разными
способами
-

пешком, такси, на рабочем автомобиле. В этой ситуации секретарю (протоколу выше) без разницы,
как доберется ку
рьер до места назначения, также как и начальнику не нужно ничего знать про то, как секретари
договорятся доставить посылку (может другим способом, не курьером). Это демонстрирует нам механизм
инкапсуляции.


Если брать сеть

Интернет (WWW)
, то в качестве кли
ента выступает веб
-
браузер, а сервером является веб
-
сервер. Веб
-
сервер
-

это компьютер, на котором установлено программное обеспечение, которое реализует
необходимый серверный функционал. В интернете обмен данными происходит посредством протокола HTTP
(Hyp
erText Transfer Protocol)


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


Электронная почта (email)



еще один способ передачи данных (э
лектронных писем) между компьютерами,
объединенными в сеть. Есть сервер электронной почты, и его клиенты. Когда клиент1 отправляет почту
клиенту2, она посредством протокола SMTP отправляется на сервер. Клиент2 же получает почту с сервера
используя протокол

POP3.


Следующие несколько уроков будут посвящены работе в сети с протоколами прикладного уровня, что на
данном этапе обучения будет наиболее полезно и актуально.


В платформе .Net для работы с сетью существует пространство имен
;


Это
пространство имен предоставляет классы для работы со многими протоколами передачи данных, классы
описывающие запрос (
WebRequest
) к серверу, ответ (
WebResponse
), класс веб
-
клиента

WebClient
, который
обеспечивает обмен данными с ресурсом, по заданному URH, к
лассы HP адреса, авторизации, «куков» и многое
другое. Более детально всё это будет рассмотрено в соответствующих уроках.


Сетевое программирование очень широкая тема, тут парочкой уроков не обойдешься, и всё я охватывать пока
не собираюсь, но самое актуал
ьное и необходимое будет рассмотрено.

В этом уроке будет рассмотрен протокол HTTP и базовая работа с ним в Си
-
шарп.


HTTP

(HyperText Transfer Protocol)


это протокол передачи данных (изначально гипертекстовых данных в
формате HTMI, потом произвольных данн
ых) прикладного уровня, используемый в World Wide Web (WWW).


Протокол HTTP работает по технологии клиент
-
сервер. Клиентом обычно выступает веб
-
браузер, а сервером


веб
-
сервер. Клиент формирует и делает запрос к серверу, и тот, обработав запрос, возвращае
т ответ клиенту.


Объектом, над которым происходит работа протокола HTTP, является ресурс, на который указывает URH в
запросе клиента.

URI

(Uniform Resource Identifier)


унифицированный идентификатор ресурса, простыми
словами, это то, что указывается в ст
роке браузера


имя запрашиваемого ресурса (страница, изображение, и
т.д.).


Структура HTTP
-
сообщения имеет следующий вид:


Lстартовая
-
строка]

Lзаголовок
-
сообщения1]

Lзаголовок
-
сообщения2]




Lтело
-
сообщения]



Стартовая строка для запроса и ответа
отличаются.

Для запроса она имеет такую структуру:


LМетод] LURH] HTTPCLВерсия]



Здесь метод


название запроса, одно слово заглавными буквами, наиболее часто используются GET, POST,
HEAD.


URI


идентификатор запрашиваемого ресурса.


Версия


цифры, разд
еленные точкой (например 1.1).


Для главной страницы этого сайта стартовая строка будет иметь такой вид:





Для ответа стартовая строка выглядит так:


HTTPCВерсия LКод Состояния] LПояснение]



Код Состояния



это трехзначный цифровой код,
который определяет результат запроса. Например, если
клиент запросил при помощи метода GET некий ресурс, и сервер его смог предоставить, такое состояние имеет
код 200. Если же на сервере нет запрашиваемого ресурса, он вернет код состояния 404. Есть и много

других
состояний.


Пояснение



это текстовое отображение кода состояния, для упрощенного понимания человека. Для кода 200
пояснение имеет вид «OK».


Ответ на запрос главной страницы сайта имеет такой вид:


HTTP/1.1 200 OK



В таблице 1 приведены распростр
аненные коды состояния:


Таблица 1

Код

Описание

200

Хорошо. Успешный запрос

301

Запрошенный ресурс был окончательно перенесен на новый URH

302

Запрошенный ресурс был временно перенесен на другой URH

400

Неверный запрос
-

запрос не понят сервером из
-
за
наличия синтаксической ошибки

401

Несанкционированный доступ


у пользователя нет прав для доступа к
запрошенному документу.

404

Не найдено
-

сервер понял запрос, но не нашёл соответствующего ресурса по
указанному URH

408

Время ожидания сервером
передачи от клиента истекло

500

Внутренняя ошибка сервера

ошибка помешала HTTP
-
серверу обработать запрос



HTTP
-
заголовки


Заголовки HTTP



это строки в HTTP
-
сообщении в формате «имя: значение». Другими словами их можно
назвать метаданными (информация об

используемых данных) HTTP
-
сообщения.


Заголовки делятся на те, которые используются в запросах, на те которые включаются в ответы, и на те,
которые могут быть как запросе, так и в ответе.


Заголовки запросов


Приведу некоторые примеры заголовков запросов:


Заголовок

Referer



URH ресурса, с котрого клиент сделал запрос на сервер. Перейдя со страницы1 на
страницу2, Referer будет содержать адрес страницы1. Этот заголовок может быть полезным, например, для
того, чтобы отследить по каким поисковым запросам пос
етители попали на ваш сайт. Или сервер может как
либо иначе обрабатывать запрос, если Referer не тот, который ожидается.


Перейдя с главной страницы сайта на любую другую страницу, Referer будет выглядеть так:


Referer: http://mycsharp.ru/



Заголовок

Accept

используется для того, чтобы клиент сообщил серверу, какие типы контента он поддерживает.
Типы указываются в формате типCподтип через запятую:


Accept: text/html, text/plain, image/jpeg

Accept: image/jpg, image/gif



Если тип не может быть обработан
, возвращается HTTP
-
код 406 «Not acceptable».


Заголовок

User
-
Agent

содержит информацию о клиентском приложении. Обычно это имя браузера, его версия
и платформу. В первую очередь этот заголовок нужен для корректного отображения страницы. Браузеров есть
мно
го, много версий и не все могут одинаково отображать контент, web
-
программисты учитывают эту
информацию и выдают различным браузерам различные скриптыCстили:


User
-
Agent: Mozilla/5.0 (Windows NT 5.1; rv:37.0) Gecko/20100101 Firefox/37.0



Заголовок

Content
-
length

содержит длину передаваемых данных в байтах при использовании метода передачи
POST. При методе GET он не устанавливается.


Content
-
Length: 2803



Заголовки ответов


Заголовок Server содержит информацию о программном обеспечении, которое испо
льзует сервер:


Server: Microsoft
-
IIS/7.0



Заголовок

Content
-
Type

указывает тип данных, которые отправляются клиенту, или которые могли бы
отправиться, используя метод HEAD.


Content
-
-
8

Content
-
Type : text/plain

Content
-
Type:
image/jpeg



Общие заголовки


Заголовок

Date

указывает дату и время создания сообщения:


Date: Mon, 06 Apr 2015 17:09:39 GMT



Заголовок

Content
-
Language

содержит список языков, для которых предназначается контент.


Content
-
Language: en, ru



Тело HTTP
cообщения


Тело

(message
-
body) используется для передачи тела объекта, связанного с запросом или ответом. Обычно это
сгенерированный Otml
-
код, который браузер потом будет отображать. Тело обязательно отделяется от
заголовков пустой строкой.


Теперь перейде
м к программированию.


Классы HttpWebRequest и HttpWebResponse


Платформа .NET Framework для работы с протоколом HTTP предоставляет два основных класса. Один из них
отвечает за запрос


класс

HttpWebRequest
, а другой за ответ


HttpWebResponse
.


Приведу пр
имер простой программы, с использованием данных классов, которая получает из интернета
указанную страничку:


static void Main()

{



-
sharp.html";



HttpWebRequest request =
(HttpWebRequest)WebRequest.Create(uri);






Encoding.UTF8);



Console.WriteLine(reader.ReadToEnd());



Console.ReadL
ine();

}


Сначала создается объект запроса при помощи статического метода Create класса

WebRequest
, в который
передается адрес запрашиваемого ресурса. При этом возвращаемый объект необходимо привести к
типу

HttpWebRequest
, потому как метод Create
возвращает различный объект запроса, основываясь на том,
какой URH ему передали, это может быть, как в нашем случае, HTTP запрос, так и запрос к файловой системе
(при этом возвращается объект
FileWebRequest
).


Дальше мы создаем объект ответа

HttpWebResponse

путем вызова метода
GetResponse

у объекта запроса (при
этом приводим его к типу
HttpWebResponse
). При вызове этого метода, на сервер отправляется запрос, и в
результате мы получаем объект

HttpWebResponse

который содержит HTTP
-
заголовки, а также поток, с кот
орого
мы можем считать тело ответа.


Дальше создается объект чтения потока

StreamReader
, в его конструктор передается поток ответа, который
возвращает метод

, указываем необходимую кодировку, и выводим данные на экран. В
результате мы увид
им HTMI код запрашиваемой страницы.


Работа с HTTP
-
заголовками


Для работы с заголовками у обоих классов

HttpWebRequest

и
HttpWebResponse

есть свойство

Headers

(объект
типа

WebHeaderCollection
) которое предоставляет информацию о заголовках запроса и ответа
соответственно.
Для добавления или установки заголовков используются методы Add и Set. Метод Add принимает один
строковый аргумент


заголовок полностью в формате "имя: значение":


request.Headers.Add("Content
-
Language: en, ru");



Метод Set принимает два
строковых аргумента


имя и значение:


-
Language", "en, ru");



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

HttpWebRequest

и

HttpWebResponse
. Например, установка заголовка Referer:


request.Referer = "http://google.com";




Доступ к конкретному заголовку осуществляется при помощи той же коллекции

Headers

(коллекция пар

имя
-
значение

WebHeaderCollection
). В квадратных скобках указываем имя заголовка, и получаем его значение:


Console.WriteLine(response.Headers["date"]); // Tue, 07 Apr 2015 17:29:04 GMT



Считать все заголовки можно так:


foreach(string header in
response.Headers)

Console.WriteLine("{0}: {1}", header, response.Headers[header]);



Вывод

будет

таким
:


Content
-
Length: 77352

Cache
-
Control: private

Content
-
-
8

Date: Tue, 07 Apr 2015 17:29:04 GMT

-
Cookie:

Server: Microsoft
-
IIS/7.0

X
-
-
Version: 4.0.30319

X
-
Powered
-



В примерах выше все запросы выполнялись методом "GET".У классов HttpWebRequest и

HttpWebResponse

есть
свойство

Method
. Для запроса оно по умолчанию установлено как "GET", но его можно изменить, например на
"POST", об этом методе и будет следующий урок. Для ответа же это свойство только на чтение.


Домашнее задание


Ваша программа должна выводить в консоль заголовки уроко
в с главной страницы сайта (те, что в центральной
колонке, 8 последних). Кроме этого, я настроил сервер так, что если заголовок user
-
agent при запросе к главной
странице сайта равен значению "lesson34", то в ответ вы получите не главную страницу, а страниц
у контактов.
Установите значение "lesson34" для этого заголовка и убедитесь, что это работает.


Приложенные файлы

  • pdf 9453418
    Размер файла: 655 kB Загрузок: 0

Добавить комментарий