Студопедия
Случайная страница | ТОМ-1 | ТОМ-2 | ТОМ-3
АрхитектураБиологияГеографияДругоеИностранные языки
ИнформатикаИсторияКультураЛитератураМатематика
МедицинаМеханикаОбразованиеОхрана трудаПедагогика
ПолитикаПравоПрограммированиеПсихологияРелигия
СоциологияСпортСтроительствоФизикаФилософия
ФинансыХимияЭкологияЭкономикаЭлектроника

И напоследок. блок finally

Автоматическое управление памятью ссылочных данных | Упаковка и разупаковка данных | Типы данных со значением null | Конструкторы и деструкторы | Частично определяемые классы и их назначение | Механизм вызова событий | Создание пользовательских обобщенных коллекций | Создание обобщенных интерфейсов | Установка ограничений на параметры обобщенных классов | Обобщенные делегаты |


Читайте также:
  1. Firstly, secondly, finally, in addition to, also, moreover otherwise, while, but, consequently, in this paper, herein.
  2. Quot;Will you come away with me?" he said finally to her; but the Reed shook her head, she was so attached to her home.
  3. Then, fill in the paragraph plan. Finally, replace the underlined phrases with appropriate expressions from the list below.

Последним моментом, о котором нужно обязательно сказать при рассмотрении итераторов в C#, являются проблемы, связанные с блоком finally внутри блока итераторов. Давайте в последний пример добавим блок try/finally и, даже не глядя на сгенерированный код, подумаем о его поведении и возможных последствиях:

public static IEnumerable<int> GetNumbers(){ try { yield return 7; // 1 // 2: обработка первого элемента внешним кодом yield return 42; // 3 // 4: обработка второго элемента внешним кодом } finally { Console.WriteLine("Внутри блока finally метода GetNumbers"); }}

Как уже было сказано ранее, блок итератора не выполняется последовательно, а разворачивается в конечный автомат, реализация которого находится в методе MoveNext. Очевидно, блок finally должен выполняться не после каждого вызова метода MoveNext, а только один раз на полную итерацию последовательности, поскольку в противном случае мы можем, например, освободить ресурсы, которые потребуются при следующей итерации цикла. А раз так, то кто сможет гарантировать, что пользователь захочет пройти последовательность целиком? Почему, получив итератор из метода GetNumbers, пользователь обязательно должен вызвать MoveNext более одного раза?

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

private sealed class GetNumbersIterator: IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable{ // Остальные методы остаются такими же, поэтому пропущены private void m__Finally3() { this.__state = -1; /*состояние: "после" */ Console.WriteLine("Внутри блока finally метода GetNumbers"); } private bool MoveNext() { try { switch (this.__state) { case 0: /*состояние: "до" */ this.__state = -1; /*состояние: "выполняется" */ this.__state = 1; /*состояние: "выполняется; может быть вызван блок finilize*/ this.__current = 7; this.__state = 2; /*состояние: "приостановлен" */ return true; case 2: /*состояние: "приостановлен"*/ this.__state = 1; /*состояние: "выполняется"; может быть вызван блок finilize*/ this.__current = 42; this.__state = 3; /*состояние: "приостановлен" */ return true; case 3: /*состояние: "приостановлен" */ this.__state = 1; /*состояние: "выполняется" */ // Нормальное завершение блока итератора this.m__Finally3(); break; } return false; } fault { // Возникло исключение в блоке try ((System.IDisposable)this).Dispose(); } } void IDisposable.Dispose() { switch (this.__state) { case 1: case 2: case 3: try { } finally { // Явный вызов метода Dispose this.m__Finally3(); } break; } }}

Если блок итератора содержит блок finally, то весь код, расположенный в этом блоке, помещается в отдельный метод (в нашем случае в метод m_Finally3()), который будет вызван в следующих случаях:

1. После нормального завершения итерирования коллекции (либо после вызова yield break, либо после обыкновенного завершения блока итератора);

2. В случае генерации исключения в блоке итератора (вы могли обратить внимание на ключевое слово fault вместо finally; это не ошибка, такого ключевого слова нет в языке C#, но в языке IL такая конструкция существует, и означает, что этот фрагмент кода будет выполнен только в случае генерации исключения, после чего исключение будет проброшено далее по стеку);

3. В случае вызова метода Dispose.

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

public static IEnumerable<int> GetNumbers(){ try { yield return 7; // 1 // 2: обработка первого элемента внешним кодом yield return 42; // 3 // 4: обработка второго элемента внешним кодом } finally { Console.WriteLine("Внутри блока finally метода GetNumbers"); }}

Разработчик вправе предполагать, что если между строками 1 и 3 (т.е. при обработке первого элемента коллекции пользовательским кодом) произойдет исключение, то блок finally должен быть выполнен точно так же, как и при возникновении исключения непосредственно в блоке итератора, например, в строке 1. Такое поведение становится более очевидным, если вместо возвращения двух магических чисел блок итератора будет выполнять более осмысленную работу, например, открывать файл и возвращать строки по одной:

public static IEnumerable<string> ReadFile(string filename){ using (TextReader reader = File.OpenText(filename)) { string line; while ((line = reader.ReadLine())!= null) yield return line; }}

В этом фрагменте блок try/finally генерирует за нас компилятор, но это никак не влияет на поведение. Давайте еще раз зададимся вопросом: вправе ли разработчик рассчитывать на корректное освобождение ресурсов, если пользовательский код сгенерирует исключение?

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

foreach (var s in ReadFile(filename)){ // Обрабатываем очередную строку, // при этом при обработке может возникнуть исключение Console.WriteLine(s);}

Компилятор преобразовывает оператор foreach следующим образом:

IEnumerator<string> enumerator = ReadFile(filename).GetEnumerator();try{ while (enumerator.MoveNext()) { var s = enumerator.Current; // Обрабатываем очередную строку, // при этом при обработке может возникнуть исключение Console.WriteLine(s); }}finally{ IDisposable disposable = enumerator as System.IDisposable; if (disposable!= null) disposable.Dispose();}

В таком случае, если при обработке элемента коллекции возникнет исключение, то автоматически будет вызван метод Dispose итератора, что приведет к вызову блока finally блока итератора.

When a yield return statement is encountered:

o The expression given in the statement is evaluated, implicitly converted to the yield type, and assigned to the Current property of the enumerator object.

o Execution of the iterator body is suspended. The values of all local variables and parameters (including this) are saved, as is the location of this yield return statement. If the yield return statement is within one or more try blocks, the associated finally blocks are not executed at this time.

o The state of the enumerator object is changed to suspended.

o The MoveNext method returns true to its caller, indicating that the iteration successfully advanced to the next value.

6. When a yield break statement is encountered:

o If the yield break statement is within one or more try blocks, the associated finally blocks are executed.

o The state of the enumerator object is changed to after.

o The MoveNext method returns false to its caller, indicating that the iteration is complete.

7. When the end of the iterator body is encountered:

o The state of the enumerator object is changed to after.

o The MoveNext method returns false to its caller, indicating that the iteration is complete.

8. When an exception is thrown and propagated out of the iterator block:

o Appropriate finally blocks in the iterator body will have been executed by the exception propagation.

o The state of the enumerator object is changed to after.

o The exception propagation continues to the caller of the MoveNext method.

o

The Dispose method is used to clean up the iteration by bringing the enumerator object to the after state.

9. If the state of the enumerator object is before, invoking Dispose changes the state to after.

10. If the state of the enumerator object is running, the result of invoking Dispose is unspecified.

11. If the state of the enumerator object is suspended, invoking Dispose:

o Changes the state to running.

o Executes any finally blocks as if the last executed yield return statement were a yield break statement. If this causes an exception to be thrown and propagated out of the iterator body, the state of the enumerator object is set to after and the exception is propagated to the caller of the Dispose method.

o Changes the state to after.

12. If the state of the enumerator object is after, invoking Dispose has no affect.

This section describes a possible implementation of iterators in terms of standard C# constructs. The implementation described here is based on the same principles used by the Microsoft C# compiler, but it is by no means a mandated implementation or the only one possible.

The following Stack<T> class implements its GetEnumerator method using an iterator. The iterator enumerates the elements of the stack in top to bottom order.

using System;
using System.Collections;
using System.Collections.Generic;

class Stack<T>: IEnumerable<T>
{
T[] items;
int count;

public void Push(T item) {
if (items == null) {
items = new T[4];
}
else if (items.Length == count) {
T[] newItems = new T[count * 2];
Array.Copy(items, 0, newItems, 0, count);
items = newItems;
}
items[count++] = item;
}

public T Pop() {
T result = items[--count];
items[count] = default(T);
return result;
}

public IEnumerator<T> GetEnumerator() {
for (int i = count - 1; i >= 0; --i) yield return items[i];
}
}

 

using System;
using System.Collections.Generic;

class Test
{
static IEnumerable<int> FromTo(int from, int to)

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

static void Main() {
IEnumerable<int> e = FromTo(1, 10);
foreach (int x in e) {
foreach (int y in e) {
Console.Write("{0,3} ", x * y);
}
Console.WriteLine();
}
}
}

 

26. Понятие атрибутов в языке C#. Создание пользовательских атрибутов. Анализ атрибутов во время выполнения программы. Понятие рефлексии (reflection) в языке C#. Сериализация объектов.

 

Понятие атрибутов в языке C#

 

Types, members, and other entities in a C# program support modifiers that control certain aspects of their behavior. For example, the accessibility of a method is controlled using the public, protected, internal, and private modifiers. C# generalizes this capability such that user-defined types of declarative information can be attached to program entities and retrieved at run-time. Programs specify this additional declarative information by defining and using attributes.

The following example declares a HelpAttribute attribute that can be placed on program entities to provide links to their associated documentation.

 

using System;

public class HelpAttribute: Attribute
{
string url;
string topic;

public HelpAttribute(string url) {
this.url = url;
}

public string Url {
get { return url; }
}

public string Topic {
get { return topic; }
set { topic = value; }
}
}

All attribute classes derive from the System.Attribute base class provided by the.NET Framework. Attributes can be applied by giving their name, along with any arguments, inside square brackets just before the associated declaration. If an attribute’s name ends in Attribute, that part of the name can be omitted when the attribute is referenced. For example, the HelpAttribute attribute can be used as follows.

 

[Help("http://msdn.microsoft.com/.../MyClass.htm")]
public class Widget
{
[Help("http://msdn.microsoft.com/.../MyClass.htm", Topic = "Display")]
public void Display(string text) {}
}

This example attaches a HelpAttribute to the Widget class and another HelpAttribute to the Display method in the class. The public constructors of an attribute class control the information that must be provided when the attribute is attached to a program entity. Additional information can be provided by referencing public read-write properties of the attribute class (such as the reference to the Topic property previously).


Дата добавления: 2015-11-16; просмотров: 42 | Нарушение авторских прав


<== предыдущая страница | следующая страница ==>
Несколько слов о вложенных делегатах| Создание пользовательских атрибутов

mybiblioteka.su - 2015-2024 год. (0.01 сек.)