Создание экземпляра делегата

Экземпляр делегата создается выражением_создания_делегата (§7.6.10.5) или преобразованием в тип делегата. Вновь созданный экземпляр делегата затем ссылается на одно из следующего:

· на статический метод, на который имеется ссылка в выражении_создания_делегата, или

· на целевой объект (который не может иметь значение null) и метод экземпляра, на которые имеются ссылки в выражении_создания_делегата, или

· на другой делегат.

Пример:

delegate void D(int x);

class C
{
public static void M1(int i) {...}
public void M2(int i) {...}
}

class Test
{
static void Main() {
D cd1 = new D(C.M1); // static method
C t = new C();
D cd2 = new D(t.M2); // instance method
D cd3 = new D(cd2); // another delegate
}
}

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

Вызов делегата

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

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

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

Попытка вызова экземпляра делегата, имеющего значение null, приводит к исключению типа System.NullReferenceException.

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

using System;

delegate void D(int x);

class C
{
public static void M1(int i) {
Console.WriteLine("C.M1: " + i);
}

public static void M2(int i) {
Console.WriteLine("C.M2: " + i);
}

public void M3(int i) {
Console.WriteLine("C.M3: " + i);
}
}

class Test
{
static void Main() {
D cd1 = new D(C.M1);
cd1(-1); // call M1

D cd2 = new D(C.M2);
cd2(-2); // call M2

D cd3 = cd1 + cd2;
cd3(10); // call M1 then M2

cd3 += cd1;
cd3(20); // call M1, M2, then M1

C c = new C();
D cd4 = new D(c.M3);
cd3 += cd4;
cd3(30); // call M1, M2, M1, then M3

cd3 -= cd1; // remove last M1
cd3(40); // call M1, M2, then M3

cd3 -= cd4;
cd3(50); // call M1 then M2

cd3 -= cd2;
cd3(60); // call M1

cd3 -= cd2; // impossible removal is benign
cd3(60); // call M1

cd3 -= cd1; // invocation list is empty so cd3 is null

// cd3(70); // System.NullReferenceException thrown

cd3 -= cd1; // impossible removal is benign
}
}

Как показано в выражении cd3 += cd1;, делегат может присутствовать в списке вызова несколько раз. В этом случае он просто вызывается один раз при каждом появлении. В таком списке вызова, как этот, при удалении делегата фактически удаляется его последнее появление в списке вызова.

Непосредственно перед выполнением последнего оператора, cd3 -= cd1;, делегат cd3 ссылается на пустой список вызова. Попытка удаления делегата из пустого списка (или удаления несуществующего делегата из непустого списка) не является ошибкой.

В результате получается:

C.M1: -1
C.M2: -2
C.M1: 10
C.M2: 10
C.M1: 20
C.M2: 20
C.M1: 20
C.M1: 30
C.M2: 30
C.M1: 30
C.M3: 30
C.M1: 40
C.M2: 40
C.M3: 40
C.M1: 50
C.M2: 50
C.M1: 60
C.M1: 60

Исключения

Исключения в языке C# обеспечивают структурированный, единообразный и типобезопасный способ обработки состояний ошибки, как на системном уровне, так и на уровне приложения. Механизм исключения в языке C# вполне сходен с механизмом в языке C++, с несколькими важными отличиями:

· В C# все исключения должны быть представлены экземпляром типа класса, производным от System.Exception. В C++ для представления исключения может использоваться любое значение любого типа;

· в C# блок finally (§8.10) может использоваться для записи кода завершения, который выполняется как при нормальном выполнении, так и при исключительных состояниях. Такой код труден для написания в C++ без дублирования кода;

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

Причины исключений

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

· Оператор throw (§8.9.5) вызывает исключение немедленно и безусловно. Управление никогда не передается оператору, следующему за оператором throw.

· Определенные исключительные состояния, возникающие при обработке операторов и выражения C#, служат причиной исключения в определенных обстоятельствах, когда операцию не удается выполнить нормально. Например, операция целочисленного деления (§7.8.2) создает исключение System.DivideByZeroException, если знаменатель равен нулю. Список различных исключений, которые могут возникнуть таким образом, см. в §16.4.

Класс System.Exception

Класс System.Exception является базовым типом для всех исключений. Этот класс имеет несколько примечательных свойств, которые совместно используются всеми исключениями:

· Message — свойство только для чтения, типа string, содержащее удобочитаемое описание причины исключения;

· InnerException — доступное только для чтения свойство типа Exception. Если его значение не NULL, оно ссылается на исключение, вызвавшее текущее исключение — то есть, текущее исключение было вызвано при обработке блока catch, обрабатывавшего исключение InnerException. Иначе его значение, равное NULL, указывает, что это исключение не было вызвано другим исключением. Количество объектов исключения, сцепленных вместе таким образом, может быть произвольным.

Значения этих свойств можно указывать в вызовах конструктора экземпляров для System.Exception.

Обработка исключений

Исключения обрабатываются оператором try (§8.10).

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

Когда соответствующая конструкция catch найдена, система подготавливает передачу управления первому оператору конструкции catch. Прежде чем начинается выполнение конструкции catch, система сначала последовательно выполняет все предложения finally связанные с операторами try с большим уровнем вложенности, чем тот оператор, который вызвал исключение.

Если соответствующая конструкция catch не найдена, выполняется одно из следующих двух действий:

· Если поиск соответствующей конструкции catch доходит до статического конструктора (§10.12) или инициализатора статического поля, создается исключение System.TypeInitializationException в точке, запустившей вызов статического конструктора. Внутреннее исключение System.TypeInitializationException содержит то исключение, которое возникло первоначально;

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

Исключения, происходящие при выполнении деструктора, заслуживают особого упоминания. Если исключение происходит при выполнения деструктора и не перехватывается, выполнение этого деструктора завершается и вызывается деструктор базового класса (если он имеется). Если нет базового класса (как в случае типа object) или нет деструктора базового класса, исключение удаляется.

Общие классы исключений

Следующие исключения вызываются определенными операциями языка C#.

System.ArithmeticException Базовый класс для исключений, происходящих при арифметических операциях, таких как System.DivideByZeroException и System.OverflowException.
System.ArrayTypeMismatchException Вызывается при ошибке сохранения в массиве, оттого что фактический тип сохраняемого элемента несовместим с фактическим типом массива.
System.DivideByZeroException Вызывается при попытке деления целого значения на нуль.
System.IndexOutOfRangeException Вызывается при попытке индексировать массив индексом меньше нуля или выходящим за границы массива.
System.InvalidCastException Вызывается при ошибке во время выполнения явного преобразования из базового типа или интерфейса в производный тип.
System.NullReferenceException Вызывается, если ссылка null используется таким образом, что требуется объект, на который указывает ссылка.
System.OutOfMemoryException Вызывается при неудачной попытке выделения памяти (с помощью оператора new).
System.OverflowException Вызывается при переполнении в арифметической операции в контексте checked.
System.StackOverflowException Вызывается при исчерпании стека выполнения из-за слишком большого числа отложенных вызовов методов; обычно это указывает на очень глубокую или неограниченную рекурсию.
System.TypeInitializationException Вызывается, когда статический конструктор создает исключение и нет предложений catch для его перехвата.

Атрибуты

Многое в языке C# дает возможность программисту указывать декларативные сведения о сущностях, определенных в программе. Например, доступность метода в классе указывается путем добавления к нему модификаторов метода public, protected, internal и private.

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

Атрибуты определяются посредством объявления классов атрибутов (§17.1), которые могут иметь позиционные и именованные параметры (§17.1.2). Атрибуты вложены в сущности в программе C# с помощью спецификаций атрибутов (§17.2) и могут извлекаться во время выполнения как экземпляры атрибутов (§17.3).

Классы атрибутов

Класс, который прямо или косвенно производится от абстрактного класса System.Attribute, является классом атрибута. Объявление класса атрибута определяет новый вид атрибута, который можно поместить в объявлении. Классы атрибутов принято именовать с помощью суффикса Attribute. При использовании атрибута суффикс может оставаться или опускаться.

Использование атрибутов

Атрибут AttributeUsage (§17.4.1) применяется для описания, как использовать класс атрибута.

Атрибут AttributeUsage имеет позиционный параметр (§17.1.2), позволяющий классу атрибута указать те виды объявлений, в которых можно использовать атрибут. Пример:

using System;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public class SimpleAttribute: Attribute
{
...
}

В этом примере определяется класс атрибута с именем SimpleAttribute, который можно помещать только в объявления_классов и объявления_интерфейсов. Пример:

[Simple] class Class1 {...}

[Simple] interface Interface1 {...}

Показано несколько возможностей использования атрибута Simple. Хотя этот атрибут определен с именем SimpleAttribute, при его использовании можно опускать суффикс Attribute, то есть пользоваться коротким именем Simple. Таким образом, приведенный выше пример семантически эквивалентен следующему:

[SimpleAttribute] class Class1 {...}

[SimpleAttribute] interface Interface1 {...}

Атрибут AttributeUsage имеет именованный параметр (§17.1.2), называемый AllowMultiple, который указывает, можно ли задать атрибут более одного раза для данной сущности. Если параметр AllowMultiple для класса атрибута имеет значение True, то этот класс атрибута является классом атрибута многократного использования и его можно указывать для сущности более одного раза. Если параметр AllowMultiple для класса атрибута имеет значение False, то этот класс атрибута является классом атрибута однократного использования и его можно указывать для сущности не более одного раза.

Пример:

using System;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class AuthorAttribute: Attribute
{
private string name;

public AuthorAttribute(string name) {
this.name = name;
}

public string Name {
get { return name; }
}
}

определен класс атрибута многократного использования с именем AuthorAttribute. Пример:

[Author("Brian Kernighan"), Author("Dennis Ritchie")]
class Class1
{
...
}

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

Атрибут AttributeUsage имеет другой именованный параметр, называемый Inherited, который указывает, наследуется ли указанный для базового класса атрибут также классами, производными от этого базового класса. Если параметр Inherited для класса атрибута имеет значение True, этот атрибут наследуется. Если параметр Inherited для класса атрибута имеет значение False, этот атрибут не наследуется. Если параметр не установлен, его значение по умолчанию True.

Класс атрибута X, не имеющий вложенного в него атрибута AttributeUsage, как в следующем примере:

using System;

class X: Attribute {...}

эквивалентен следующему:

using System;

[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X: Attribute {...}

Наши рекомендации