- •Введение Обзор .Net. Основные понятия
- •Программа на c#
- •Основы языка Пространство имён
- •Система типов
- •Класс и Структура. Первое приближение
- •Литералы. Представление значений
- •Арифметические литералы
- •Логические литералы
- •Символьные литералы
- •Символьные escape-последовательности
- •Строковые литералы
- •Операции и выражения
- •Приоритет операций
- •Приведение типов
- •Особенности выполнения арифметических операций
- •Особенности арифметики с плавающей точкой
- •Константное выражение
- •Переменные элементарных типов. Объявление и инициализация
- •Константы
- •Перечисления
- •Объявление переменных. Область видимости и время жизни
- •Управляющие операторы
- •Синтаксис объявления метода
- •Вызов метода
- •Перегрузка методов
- •Способы передачи параметров при вызове метода
- •Передача параметров. Ссылка и ссылка на ссылку как параметры
- •Сравнение значений ссылок
- •This в нестатическом методе
- •Свойства
- •Обработка исключений
- •Массив. Объявление
- •Инициализация массивов
- •Примеры инициализации массивов
- •Два типа массивов: Value Type and Reference Type
- •Встроенный сервис по обслуживанию простых массивов
- •Реализация сортировки в массиве стандартными методами
- •Подробнее о массивах массивов (jagged array)
- •Массивы как параметры
- •Спецификатор params
- •Main в классе. Точка входа
- •Создание объекта. Конструктор
- •Операция new
- •В управляемой памяти нет ничего, что бы создавалось без конструктора
- •Кто строит конструктор умолчания
- •This в контексте конструктора
- •Перегрузка операций
- •Синтаксис объявления операторной функции
- •Унарные операции. Пример объявления и вызова
- •Бинарные операции
- •Определение операций конъюнкция и дизъюнкции
- •И вот результат…
- •Пример. Свойства и индексаторы
- •Explicit и implicit. Преобразования явные и неявные
- •Наследование
- •Наследование и проблемы доступа
- •Явное обращение к конструктору базового класса
- •Кто строит базовый элемент
- •Переопределение членов базового класса
- •Наследование и new модификатор
- •Полное квалифицированное имя. Примеры использования
- •Прекращение наследования. Sealed спецификатор
- •Абстрактные функции и абстрактные классы
- •Ссылка на объект базового класса
- •Операции is и as
- •Виртуальные функции. Принцип полиморфизма
- •Интерфейсы
- •Делегаты
- •События
- •События и делегаты. Различия
- •Атрибуты, сборки, рефлексия Рефлексия (отражение) типов
- •Реализация отражения. Type, InvokeMember, BindingFlags
- •Атрибуты
- •Сборка. Класс Assembly
- •Класс сборки в действии
- •Разбор полётов
- •Класс System.Activator
- •Версия сборки
- •Файл конфигурации приложения
- •Общедоступная сборка
- •Игры со сборками из gac
- •Динамические сборки
- •Динамическая сборка: создание, сохранение, загрузка, выполнение
- •Ввод-вывод Базовые операции
- •Потоки: байтовые, символьные, двоичные
- •Предопределённые потоки ввода-вывода
- •Функция ToString()
- •Консольный ввод-вывод. Функции-члены класса Console
- •Консольный вывод. Форматирование
- •Функции вывода. Нестандартное (custom) форматирование значений.
- •Консольный ввод. Преобразование значений
- •Файловый ввод-вывод
- •Потоки Процесс, поток, домен
- •Домен приложения
- •Обзор пространства имён System.Threading
- •Многопоточность
- •Виды многопоточности
- •А кто в домене живёт…
- •Класс Thread. Общая характеристика
- •Именование потока
- •Игры с потоками
- •Характеристики точки входа дополнительного потока
- •Запуск вторичных потоков
- •Приостановка выполнения потока
- •Отстранение потока от выполнения
- •Завершение потоков
- •Метод Join()
- •Состояния потока (перечисление ThreadState)
- •Одновременное пребывание потока в различных состояниях
- •Фоновый поток
- •Приоритет потока
- •Передача данных во вторичный поток
- •Извлечение значений (данных) с помощью Callback методов
- •Организация взаимодействия потоков
- •1. Посредством общедоступных (public) данных
- •2. Посредством общедоступных (public) свойств
- •3. Посредством общедоступных очередей
- •Состязание потоков
- •Блокировки и тупики
- •Очереди. Основа интерфейса взаимодействия
- •Безопасность данных и критические секции кода
- •Пример организации многопоточного приложения
- •Очередь как объект синхронизации
- •Синхронизация работы потоков при работе с общими ресурсами
- •1. Организация критических секций
- •2. Специальные возможности мониторов
- •Рекомендации по недопущению блокировок потоков
- •Форма Класс Form
- •Форма: управление и события жизненного цикла
- •Форма: контейнер как элемент управления
- •Разница между элементами управления и компонентами.
- •Свойства элементов управления. Anchor и Dock
- •Extender providers. Провайдеры дополнительных свойств
- •Validating и Validated элементов управления
- •Управление посредством сообщений
- •Стандартный делегат
- •Делегат EventHandler
- •Класс Application
- •События класса Application
- •Примеры перехвата сообщений
- •Метод WndProc
- •Пример переопределения WndProc
- •Контекст приложения
- •Применение классов GraphicsPath и Region. Круглая форма
- •Собственные элементы управления
- •Литература
Виртуальные функции. Принцип полиморфизма
Основа реализации принципа полиморфизма – наследование. Ссылка на объект базового класса, настроенная на объект производного может обеспечить выполнение методов ПРОИЗВОДНОГО класса, которые НЕ БЫЛИ ОБЪЯВЛЕНЫ В БАЗОВОМ КЛАССЕ. При реализации принципа полиморфизма происходят вещи, которые не укладываются в ранее описанную схему.
Одноимённые функции с одинаковой сигнатурой в базовом и производном классах: между ними может быть установлено отношение замещения. Замещаемая функция базового класса должна при этом дополнительно специфицироваться спецификатором virtual.
Отношение замещения между функциями базового и производного класса устанавливается, если соответствующая (одноименная функция с соответствующей сигнатурой) функция производного класса специфицируется дополнительным спецификатором override.
Введём следующие обозначения и построим схему наследования.
Спецификация и заголовок функции- члена |
Уровень наследования |
Обозначение |
public void F(){ } |
0 |
|
public virtual void F() { } |
0 |
|
new public virtual void F() { } |
1..N |
|
new public void F() { } |
1..N |
|
public override void F() { } |
1..N |
|
Схема возможных вариантов объявления методов в иерархии наследования трёх уровней.
Наконец, замечательный пример,
using System;
namespace Implementing
{
class A
{
public virtual void F() { Console.WriteLine(“A”);}
}
class B:A
{
public override void F() { Console.WriteLine(“B”); }
}
class C:B
{
new public virtual void F() { Console.WriteLine(“C”); }
}
class D:C
{
public override void F() { Console.WriteLine(“D”); }
}
class Starter
{
static void Main(string[] args)
{
D d = new D();
C c = d;
B b = c;
A a = b;
d.F(); /* D */
c.F(); /* D */
b.F(); /* B */
a.F(); /* B */
}
}
}
в котором становится ВСЁ понятно, если ПОДРОБНО нарисовать схемы классов, структуру объекта-представителя класса D и посмотреть, КАКАЯ ссылка ЗА КАКОЕ место этот самый объект удерживает.
Интерфейсы
Фактически это те же самые абстрактные классы, НЕ СОДЕРЖАЩИЕ объявлений данных – членов и объявлений ОБЫЧНЫХ функций.
Все без исключения функции-члены интерфейса – абстрактные. Поэтому интерфейс объявляется с особым ключевым словом interface, а функции интерфейса, несмотря на свою “абстрактность” объявляются без ключевого слова abstract.
Основное отличие интерфейса от абстрактного класса заключается в том, что производный класс может наследовать одновременно несколько интерфейсов.
using System;
namespace Interface01
{
// Интерфейсы.
// Этот интерфейс характеризуется уникальными
// именами объявленных в нём методов.
interface Ix
{
void IxF0(int xKey);
void IxF1();
}
// Пара интерфейсов, содержащих объявления одноимённых методов
// с одной и той же сигнатурой.
interface Iy
{
void F0(int xKey);
void F1();
}
interface Iz
{
void F0(int xKey);
void F1();
}
// А этому интерфейсу уделим особое внимание.
// Он содержит тот же набор методов, но в производном классе этот интерфейс
// будет реализован явным образом.
interface Iw
{
void F0(int xKey);
void F1();
}
// В классе TestClass наследуются интерфейсы...
class TestClass:Ix
,Iy
,Iz
,Iw
{
public int xVal;
// Конструкторы.
public TestClass()
{
xVal = 125;
}
public TestClass(int key)
{
xVal = key;
}
// Реализация функций интерфейса Ix.
// Этот интерфейс имеет специфические названия функций.
// В данном пространстве имён его реализация неявная и однозначная.
public void IxF0(int key)
{
xVal = key*5;
Console.WriteLine("IxF0({0})...", xVal);
}
public void IxF1()
{
xVal = xVal*5;
Console.WriteLine("IxF1({0})...", xVal);
}
// Реализация интерфейсов Iy и Iz в классе TestClass неразличима.
// Это неявная неоднозначная реализация интерфейсов.
// Однако, неважно, чью конкретно функцию реализуем. Оба интерфейса довольны...
public void F0(int xKey)
{
xVal = (int)xKey/5;
Console.WriteLine("(Iy/Iz)F0({0})...", xVal);
}
public void F1()
{
xVal = xVal/5;
Console.WriteLine("(Iy/Iz)F1({0})...", xVal);
}
// А это явная непосредственная реализация интерфейса Iw.
// Таким образом, класс TestClass содержит ТРИ варианта реализации функций интерфейсов
// с одной и той же сигнатутой. Два варианта реализации неразличимы. Третий
// (фактически второй) вариант реализации отличается квалифицированными именами.
void Iw.F0(int xKey)
{
xVal = xKey+5;
Console.WriteLine("Iw.F0({0})...", xVal);
}
void Iw.F1()
{
xVal = xVal-5;
Console.WriteLine("Iw.F1({0})...", xVal);
}
public void bF0()
{
Console.WriteLine("bF0()...");
}
}
class Class1
{
static void Main(string[] args)
{
TestClass x0 = new TestClass();
TestClass x1 = new TestClass(5);
x0.bF0();
// Эти методы представляют собой неявную ОДНОЗНАЧНУЮ реализацию
// интерфейса Ix.
x0.IxF0(10);
x1.IxF1();
// Эти методы представляют собой неявную НЕОДНОЗНАЧНУЮ реализацию
// интерфейсов Iy и Iz.
x0.F0(5);
x1.F1();
// А вот вызов функций с явным приведением к типу интерфейса.
// Собственный метод класса bF0() при подобных преобразованиях
// не виден.
(x0 as Iy).F0(7);
(x1 as Iz).F1();
// А теперь настраиваем ссылки различных типов интерфейсов
// на ОДИН И ТОТ ЖЕ объект-представитель класса TestClass.
// И через "призму" интерфейса всякий раз объект будет
// выглядеть по-разному.
Console.WriteLine("==========Prism test==========");
Console.WriteLine("==========Ix==========");
Ix ix = x1;
ix.IxF0(5);
ix.IxF1();
Console.WriteLine("==========Iy==========");
Iy iy = x1;
iy.F0(5);
iy.F1();
Console.WriteLine("==========Iz==========");
Iz iz = x1;
iz.F0(5);
iz.F1();
Console.WriteLine("==========Iw==========");
Iw iw = x1;
iw.F0(10);
iw.F1();
}
}
}
Преимущества программирования с использованием интерфейсов проявляются в том случае, когда ОДНИ И ТЕ ЖЕ ИНТЕРФЕЙСЫ наследуются РАЗНЫМИ классами. И здесь всё определяется спецификой данной конкретной реализации.
using System;
namespace Interface02
{
// Объявляются два интерфейса, каждый из которых содержит объявление
// одноименного метода с единственным параметром соответствующего типа.
interface ICompare0
{
bool Eq(ICompare0 obj);
}
interface ICompare1
{
bool Eq(ICompare1 obj);
}
// Объявляются классы, наследующие оба интерфейса.
// В каждом из классов реализуются функции интерфейсов.
// В силу того, что объявленные в интерфейсах методы одноименные,
// в классах применяется явная реализация методов интерфейсов.
//_______________________________________________________________________.
class C0:ICompare0,ICompare1
{
public int commonVal;
int valC0;
public C0(int commonKey, int key)
{
commonVal = commonKey;
valC0 = key;
}
// Метод реализуется для обеспечения сравнения объектов СТРОГО одного типа - C0.
bool ICompare0.Eq(ICompare0 obj)
{
C0 test = obj as C0;
if (test == null) return false;
if (this.valC0 == test.valC0)
return true;
else
return false;
}
// Метод реализуется для обеспечения сравнения объектов разного типа.
bool ICompare1.Eq(ICompare1 obj)
{
C1 test = obj as C1;
if (test == null) return false;
if (this.commonVal == test.commonVal)
return true;
else
return false;
}
}
//_______________________________________________________________________.
class C1:ICompare0,ICompare1
{
public int commonVal;
string valC1;
public C1(int commonKey, string key)
{
commonVal = commonKey;
valC1 = string.Copy(key);
}
// В классе C1 при реализации функции интерфейса ICompare0 реализован
// метод сравнения, который обеспечивает сравнение как объектов типа C1,
// так и объектов типа C0.
bool ICompare0.Eq(ICompare0 obj)
{
C1 test;
// Попытка приведения аргумента к типу C1.
// В случае успеха - сравнение объектов по специфическому признаку,
// который для данного класса представлен строковой переменной valC1.
// В случае неуспеха приведения (очевидно, что сравниваются объекты разного типа)
// предпринимается попытка по второму сценарию (сравнение объектов разных типов).
// Таким образом, в рамках метода, реализующего один интерфейс,
// используется метод второго интерфейса. Разумеется, при явном приведении
// аргумента к типу второго интерфейса.
test = obj as C1;
if (test == null)
return ((ICompare1)this).Eq((ICompare1)obj);
if (this.valC1.Equals(test.valC1))
return true;
else
return false;
}
// Метод реализуется для обеспечения сравнения объектов разного типа.
bool ICompare1.Eq(ICompare1 obj)
{
C0 test = obj as C0;
if (test == null) return false;
if (this.commonVal == test.commonVal)
return true;
else
return false;
}
}
//===================================================================
// Место, где порождаются и сравниваются объекты.
class Class1
{
static void Main(string[] args)
{
C0 x1 = new C0(0,1);
C0 x2 = new C0(1,1);
// В выражениях вызова функций-членов интерфейсов НЕ ТРЕБУЕТСЯ явного
// приведения значения параметра к типу интерфейса.
// Сравнение объектов-представителей одного класса (C0).
if ((x1 as ICompare0).Eq(x2))
Console.WriteLine("Yes!");
else
Console.WriteLine("No!");
C1 y1 = new C1(0,"1");
C1 y2 = new C1(1,"1");
// Сравнение объектов-представителей одного класса (C1).
if ((y1 as ICompare0).Eq(y2))
Console.WriteLine("Yes!");
else
Console.WriteLine("No!");
// Попытка сравнения объектов-представителей разных классов.
if (((ICompare0)x1).Eq(y2))
Console.WriteLine("Yes!");
else
Console.WriteLine("No!");
if ((x1 as ICompare1).Eq(y2))
Console.WriteLine("Yes!");
else
Console.WriteLine("No!");
if (((ICompare1)y2).Eq(x2))
Console.WriteLine("Yes!");
else
Console.WriteLine("No!");
// Здесь будут задействован метод сравнения, реализованный
// в классе C0 по интерфейсу ICompare0, который не является универсальным.
// Отрицательный результат может быть получен не только по причине неравенства
// значений сравниваемых величин, но и по причине несоответствия типов операндов.
Console.WriteLine("__________x2==y2__________");
if ((x2 as ICompare0).Eq(y2))
Console.WriteLine("Yes!");
else
Console.WriteLine("No!");
// А здесь вероятность положительного результата выше, поскольку в классе
// C1 метод интерфейса ICompare0 реализован как УНИВЕРСАЛЬНЫЙ. И это значит,
// что данный метод никогда не вернёт отрицательного значения по причине
// несоответствия типов операндов.
Console.WriteLine("__________y2==x2__________");
if ((y2 as ICompare0).Eq(x2))
Console.WriteLine("Yes!");
else
Console.WriteLine("No!");
}
}
}