Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции OOP c#.doc
Скачиваний:
44
Добавлен:
22.09.2019
Размер:
3.38 Mб
Скачать

1.23. Нововведения в языке c# 2.0

В ноябре 2005 года корпорация Microsoft представила вторую версию платформы .NET. Эта версия содержит изменения, коснувшиеся как технологий и подходов, применяемых в рамках платформы, так и языков программирования для платформы. В данном и следующем параграфе рассмотрим нововведения во второй версии языка C#.

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

public static class AppSettings {

public static string BaseDir { . . . }

public static string GetRelativeDir() { . . . }

}

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

2. Частичные типы. Хорошей практикой программирования считается помещать описание каждого пользовательского типа в отдельный файл. Однако иногда классы и структуры получаются настолько большими, что указанный подход становиться непрактичным. Особенно это справедливо при использовании средств автоматического генерирования кода. Частичные типы (partial types) позволяют классам, структурам и интерфейсам быть разбитыми на несколько фрагментов, описанных в отдельных файлах с исходным текстом.

Для объявления частичного типа используется модификатор partial. Рассмотрим пример частичного типа:

// Файл part1.cs

partial class BrokenClass {

private int someField;

private string anotherField;

}

// Файл part2.cs

partial class BrokenClass {

public BrokenClass() { }

public void Method() { }

}

Подчеркнем, что все фрагменты частичного типа должны быть доступны во время компиляции, так как «сборку» типа выполняет компилятор. Еще одно замечание касается использования модификаторов, применяемых к типу. Модификаторы доступа должны быть одинаковыми у всех фрагментов. Если же к одному из фрагментов применяется модификатор sealed или abstract, то эти модификаторы считаются примененными ко всем фрагментам, то есть к типу в целом.

3. Модификаторы доступа для get и set частей свойств и индексаторов. Как правило, в пользовательском типе свойства открыты, имеют модификатор доступа public. Однако иногда логика типа требует, чтобы у свойства были отдельные «привилегии» для чтения и записи значений. Например, чтение позволено всем, а запись – только из методов того типа, где объявлено свойство. В C# 2.0 разрешено при описании свойства или индексатора указывать модификаторы доступа для get и set частей:

class SomeClass {

public int Prop {

get { . . . }

private set { . . . }

}

}

При указании модификаторов для get и set частей действуют два правила. Во-первых, модификатор может быть только у одной части. Во-вторых, он должен «понижать» видимость части по сравнению с видимостью всего свойства.

4. Безымянные методы. Назначение безымянных методов (anonymous methods) – сократить объем кода, который должен написать разработчик при работе с событиями. Рассмотрим пример, в котором назначаются обработчики событий для объектов класса CExampleClass (подробнее – в параграфе, посвященном работе с событиями).

class MainClass {

public static void firstReaction(int i) {

Console.WriteLine("{0} is a bad value!", i);

}

public static void secondReaction(int i) {

Console.WriteLine("Are you stupid?");

}

public static void Main() {

CExampleClass c = new CExampleClass();

// Назначаем обработчик

c.onErrorEvent += new Proc(firstReaction);

// Назначаем еще один обработчик

c.onErrorEvent += new Proc(secondReaction);

}

}

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

class MainClass {

public static void Main() {

CExampleClass c = new CExampleClass();

// Назначаем обработчик

c.onErrorEvent += delegate(int i) {

Console.WriteLine("{0} is a bad value!", i); };

// Назначаем еще один обработчик

c.onErrorEvent += delegate {

Console.WriteLine("Are you stupid?"); };

}

}

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

Обратите внимание в предыдущем фрагменте кода на второй безымянный метод. Он не использовал параметр события, что позволило не указывать сигнатуру метода после delegate. Компилятор автоматически выполняет соответствующие неявные преобразования для делегатов и безымянных методов. Точные правила совместимости делегата и безымянного метода выглядят следующим образом:

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

a. безымянный метод не имеет параметров, а делегат не имеет out-параметров;

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

2. Тип, возвращаемый делегатом, совместим с типом безымянного метода, если выполняется одно из двух условий:

a. тип делегата – void, а безымянный метод не имеет оператора return или оператор return записан без последующего выражения;

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

В следующем примере безымянные методы используются для написания функций «на лету». Безымянный метод передается как параметр, тип которого Function.

using System;

delegate double Function(double x);

class Test {

static double[] Apply(double[] a, Function f) {

double[] result = new double[a.Length];

for (int i = 0; i < a.Length; i++)

result[i] = f(a[i]);

return result;

}

static double[] MultAllBy(double[] a, double factor) {

return Apply(a, delegate(double x){ return x*factor;});

}

static void Main() {

double[] a = { 0.0, 0.5, 1.0 };

double[] s = Apply(a, delegate(double x){ return x*x;});

double[] doubles = MultAllBy(a, 2.0);

}

}

Как описывалось выше, безымянные методы могут быть неявно приведены к типу соответствующего делегата. C# 2.0 позволяет проводить подобное преобразование и с использованием обычных методов. Рассмотрим фрагмент кода:

addButton.Click += new EventHandler(AddClick);

Apply(a, new Function(Math.Sin));

В C# 2.0 он может быть переписан в следующей короткой форме:

addButton.Click += AddClick;

Apply(a, Math.Sin);

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

5. Итераторы. Язык C# содержит удобную синтаксическую конструкцию foreach для перебора элементов пользовательского типа. Чтобы поддерживать перебор при помощи foreach, тип должен реализовывать интерфейс IEnumerable. Кодирование поддержки этого интерфейса упрощается с использованием итераторов. Итератор (iterator) – это блок кода, который порождает упорядоченную последовательность значений. Итератор отличает присутствие в блоке кода одного или нескольких операторов yield. Оператор yield return <выражение> возвращает следующее значение в последовательности, оператор yield break прекращает генерирование последовательности.

Итераторы могут использоваться в качестве тела функции, если тип возвращаемого значения функции – это тип, реализующий или наследованный от интерфейсов IEnumerator, IEnumerator<T>, IEnumerable, IEnumerable<T>.

Рассмотрим пример использования итераторов. Следующее приложение выводит на консоль таблицу умножения:

using System;

using System.Collections;

class Test {

static IEnumerable FromTo(int from, int to) {

while (from <= to) yield return from++;

}

static void Main() {

IEnumerable e = FromTo(1, 10);

foreach (int x in e) {

foreach (int y in e) {

Console.Write("{0,3} ", x * y);

}

Console.WriteLine();

}

}

}

6. Типы с поддержкой null-значений. Для разработчиков всегда была проблемой поддержка неопределенных, пустых значений в структурных типах. Иногда для указания на неопределенное значение использовалось дополнительное булево поле, иногда – некая специальная константа. Язык C# новой версии предлагает для решения этой проблемы типы с поддержкой null-значений.

Тип с поддержкой null-значений (далее для краткости – null-тип) объявляется с использованием модификатора ?, записанного непосредственно после имени типа. Например, для типа int соответствующий null-тип объявляется как int?. Null-типы могут быть объявлены только для структурных типов (примитивных или пользовательских). В null-типе присутствует специальный булевский индикатор HasValue, указывающий на наличие значения, и свойство Value, содержащее значение. Попытка прочитать значение Value при HasValue=false ведет к генерации исключения.

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

int? x = 123;

int? y = null;

if (x.HasValue) Console.WriteLine(x.Value);

if (y.HasValue) Console.WriteLine(y.Value);

Существует возможность неявного приведения структурного типа в соответствующий null-тип. Кроме этого, любой переменной null-типа может быть присвоено значение null. Если для структурного типа S возможно приведение к структурному типу T, то соответствующая возможность имеется и для типов S? и T?. Также возможно неявное приведение типа S к типу T? и явное приведение S? к T. В последнем случае возможна генерации исключительной ситуации – если значение типа S? не определено.

int x = 10;

int? z = x; // неявное приведение int к int?

double? w = z; // неявное приведение int? к double?

double y = (double)z; // явное приведение int? к double

С поддержкой null-типов связано появление в C# новой операции ??. Результатом выражения a ?? b является a, если оно содержит некое значение, и b – в противном случае. Таким образом, b – это то значение, которое следует использовать, если a не определено. Тип результата выражения a ?? b определяется типом операнда b.

int? x = GetNullableInt();

int? y = GetNullableInt();

int? z = x ?? y;

int i = z ?? -1;

Операцию ?? можно применить и для ссылочных типов:

string s = GetStringValue();

Console.WriteLine(s ?? "Unspecified");

В этом фрагменте кода на консоль выводится значение строки s, или "Unspecified", если s=null.