40959

Использование двоичного кода

Лекция

Информатика, кибернетика и программирование

Т подходящее имя для обобщенного типа: public clss List T { } public clss LinkedList T { } если к обобщенному типу предъявляются специальные требования например что тип должен реализовывать интерфейс либо наследоваться от определенного класса или же используется два или более обобщенных типа в качестве параметров то следует применять осмысленные имена типов: public delegte void EventHndler TEventrgs object sender TEventrgs e; public delegte TOutput Converter TInput T0utput TInput from; public clss SortedList TKey...

Русский

2013-10-22

357.5 KB

2 чел.

PAGE  28

Лекция 7.
Обобщения

Производительность

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

Типы значений сохраняются в стеке. Типы ссылок — в куче. Классы С# являются ссылочными типами; структуры — типами значений. .NET позволяет легко конвертироватъ типы значений в ссылочные, поэтому их можно использовать там, где ожидаются объекты (ссылочных типов). Например, значение типа int можно присвоить объекту. Преобразование типа значений в ссылочный тип называется упаковкой. Упаковка происходит автоматически, когда метод ожидает параметр ссылочного типа, а ему передается тип значений. С другой стороны, упакованный тип значений может быть обратно преобразован к простому типу значений с помощью распаковки. При распаковке требуется операция приведения.

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

Безопасность типов

Другим свойством обобщений является безопасность типов. Когда в классе ArrayList сохраняются объекты, то в коллекцию могут быть вставлены объекты различных типов. Следующий пример демонстрирует вставку целого, строки и объекта типа MyClass в коллекцию ArrayList:

ArrayList list = new ArrayList();

list.Add(44);

list.Add("mystring");

list. Add (new MyClass());

Теперь, если реализовать итерацию по коллекции приведенным ниже оператором foreach, который проходит по целым элементам, то компилятор примет этот код. Однако поскольку не все элементы коллекции могут быть приведены к типу int, возникнет ошибка во время выполнения:

foreach(int i in list)

{

Console.WriteLine(i);

}

Ошибки должны быть обнаружены как можно раньше. При использовании обобщенного класса List<T> обобщенный тип-параметр Т задает тип элементов, который допускается вставлять в коллекцию. Компилятор не пропустит следующий код, потому что метод Add( ) имеет недопустимые аргументы:

List<int> list = new List<int>();

list.Add(44);

list.Add("mystring");      //  ошибка компиляции

list.Add(new MyClass());   //  ошибка компиляции

Повторное использование двоичного кода

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

В качестве примера рассмотрим код, использующий класс List<T> пространства имен System.Collections.Generic из сборки mscorlib для создания специализированных версий, предназначенных для хранения элементов типов int, string и MyClass:

List<int> list = new List<int>();

list.Add(44);

List<string> stringList = new List<string>();

stringList.Add("mystring");

List<MyClass> myclassList = new List<MyClass>();

myClassList.Add(new MyClass());

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

Увеличение кода в объеме

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

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

Рекомендации по именованию

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

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

public class List<T> {   }

public class LinkedList<T> {   }

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

public delegate void EventHandler<TEventArgs>(object sender,
TEventArgs e);

public delegate TOutput Converter<TInput, T0utput>(TInput from);

public class SortedList<TKey,   TValue>  {   }

Обобщенные классы коллекций

Многие обобщенные интерфейсы и коллекции определены в пространстве имен System.Collections.Generic. Эти классы можно использовать вместо классов коллекций. Многие из тех классов имеют обобщенные аналоги.

Обзор обобщенных коллекций

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

Основные интерфейсы и их функциональность представлены в табл. 7.1.


Таблица 7.1. Основные интерфейсы и функциональность обобщенных коллекций

Интерфейс

Методы и свойства

Описание

ICollection<T>

Add( )

Clear( )

Contains( )

CopyTo( )

Remove( )

Count

IsReadOnly

Интерфейс ICollection<T> реализован классами коллекций. Методы этого интерфейса могут быть использованы для добавления и удаления элементов коллекции. Обобщенный интерфейс ICollection<T> наследуется от необобщенного интерфейса IEnumerable. Поэтому можно передавать объекты, реализующие ICollection<T>, методам, которые требуют параметры IEnumerable.

IList<T>

Insert( )

RemoveAt( )

IndexOf ( )

Item

Интерфейс List<T> позволяет организовать доступ к элементам коллекции с помощью индексатора. Он также позволяет вставлять и удалять элементы в любой позиции. Подобно ICollection, IList<T> наследуется от IEnumerable.

IEnumerable<T>

GetEnumerator( )

Интерфейс IEnumerable<T> требуется для использования оператора foreach с коллекцией. Этот интерфейс определяет метод GetEnumerator( ) , который возвращает перечислитель, реализующий IEnumerator<T>. Обобщенный интерфейс IEnumerable<T> наследуется от необобщенного IEnumerable.

IEnumerator<T>

Current

Оператор fоreach использует перечислитель, реализующий IEnumerator<T> Для доступа ко всем элементам коллекции. Интерфейс IEnumerable<T>наследуется от необобщенных интерфейсов IEnumerator и IDisposable. Интерфейс IEnumerator определяет методы MoveNext( ) и Reset( ). IEnumerable<T> определяет безопасную к типам версию свойства current.

IDictionary<TKey, TValue>

Add ( )

ContainsKey( ) Remove( ) TryGetValue ( )

Item, Keys, Values

Интерфейс IDictionary<K,V> реализуется коллекциями, элементы которых имеют ключ и значение.

IComparer<T>

Compare ()

Интерфейс IComparer<T> применяется для сортировки элементов внутри коллекции методом Compare( ) .

IEqualityComparer<T>

Equals () GetHashCode ()

IEqualityComparer<T> — второй интерфейс для сравнения объектов. С этим интерфейсом объекты могут быть проверены на равенство. Метод GetHashCode( ) должен возвращать уникальное значение для каждого объекта. Метод Equals( ) возвращает true, если объекты равны, и false — в противном случае.

Обобщенные классы коллекций и их функциональность представлены в табл. 7.2.

Таблица 7.2. Обобщенные классы коллекций и их функциональность

Класс

Реализованные интерфейсы

Описание

List<T>

IList<T>

ICollection<T> IEnumerable<T>

Класс List<T> — обобщенная замена класса ArrayList. Подобно ArrayList, List<T> может расти и уменьшаться динамически. Помимо реализации трех указанных интерфейсов, он также поддерживает дополнительную функциональность, такую как сортировку и смена порядка следования элементов на обратный.

Dictionary <ТКеу, TValue>

IDictionary<TKey, TValue>

ICollection<KeyValue Pair <TKey, TValue>>

IEnumerable<KeyValuePair <TKey, TValue>>

ISerializable IDeserializationCallback

Dictionary<TKey, TValue> — класс коллекций, которые хранят пары ключ-значение.

SortedList<ТКеу, TValue>

IDictionary<TKey, TValue>,

ICollection<Ke у Value Pair <TKey, TValue>>

IEnumerable<KeyValuePair <TKey, TValue»

SortedList<TKey, TValue> подобен Dictionary<TKey, TValue> с тем отличием, что эта коллекция сортируется по ключу.

LinkedList<T>

ICollection<T>

IEnumerableISerializable IDeserializationCallback

LinkedList<T> — двунаправленный связный список. Каждый элемент имеет ссылки на предыдущий и следующий элементы.

Queue<T>

ICollection<T>

IEnumerable<T>

Queue<T> — коллекция, поддерживающая алгоритм «первым пришел — первым обслужен». Элемент, добавленный первым, первым и будет прочитан. Метод Enque( ) добавляет объект в конец очереди; метод Deque( ) возвращает и удаляет объект из начала очереди. Методом Peek( ) можно прочитать первый объект очереди, не удаляя его.

Stack<T>

ICollection<T>

IEnumerable<T>

Stack<T> — коллекция, реализующая алгоритм «последним пришел — первым обслужен». Элемент, добавленный последним, будет прочитан из стека первым. Stack<T> предлагает методы Push( ) и Pop( ) . Push( ) добавляет элемент в конец стека, a Pop( ) читает и удаляет объект из конца стека. Метод Peek( ) позволяет прочитать последний элемент стека, не удаляя его.

Среда .NET Framework представляет некоторые базовые классы обобщенных коллций, которые могут быть использованы для создания пользовательских обобщенных классов. Классы, представленные в табл. 7.3, находятся в пространстве имен System.Collections.ObjectModel.

Таблица 7.3. Классы из пространства имен System.Collections.ObjectModel

Класс

Реализованные
интерфейсы

Описание

Collection<T>

IList<T>

ICollection<T>

IEnumerable<T>

Класс Collection<T> может быть использован в качестве базового для пользовательских обобщенных классов коллекций. Реализует интерфейсы IList<T>, IColleetion<T> и
IEnumerable<T>. Внутри этот класс использует класс List<T>, однако не предоставляет доступ ко всем методам List<T>. Поэтому в пользовательских обобщенных классах коллекций вы можете представить методы, необходимые для конкретного применения. В пользовательских классах можно получить доступ к лежащему в основе объекту List<T> через защищенное свойство Items .

ReadOnlyCollection<T>

IList<T>

ICollection<T>

IEnumerable<T>

ReadOnlyCollection<T> — версия класса Collection<T> с доступом только для чтения. Все методы интерфейсов, которые позволяют добавление и удаление элементов, реализованы явно и генерируют исключение

NotSupportedException. Объект этого класса может быть создан передачей конструктору любой коллекции, реализующей IList<T>.

KeyedCol lection<TKey, TItem>

IList<TItem>

ICollection<TItem>

IEnumerable<TItem>

KeyedCollectior<TKey, TItem> — абстрактный базовый класс, который можно использовать для создания пользовательских классов коллекций, имеющих ключ и значения. Класс наследуется от Collection<TItem>

Использование класса List<T>

Класс List<T> из пространства имен System.Collections.Generic очень похож в применении на ArrayList из пространства имен System.Collections. Этот класс реализует интерфейсы IList, ICollection и IEnumerable.

В следующем примере используется класс Student в качестве типа элементов, добавляемых к коллекции, хранящей информацию о студентах факультета Информатизации и управления РГЭУ. Этот класс имеет два поля — полное имя студента и номер групы, доступ к которым организован через свойства. Значения полей передаются экземпляру класса в параметрах конструктора. Метод ToString( ) переопределен так, чтобы возвращать имя студента и номер групы:

class Student

{

   private string fullName;

   private string group;

   public string FullName {

       get { return fullName; }

       set { fullName = value; } }

   public string Group  {

       get { return group; }

       set { group = value; } }

   public Student(string fullName, string group)

   {

       this.fullName = fullName;

       this.group = group;

   }

   public override string ToString()

   {

       return fullName + " группа " + group;

   }

Переменная students определена как имеющая тип List<Student>. Операцией new создается объект того же типа. Здесь используется конструктор класса List<T> поумолчанию. Но у него также есть конструктор, резервирующий память для определеного числа элементов, а также конструктор, копирующий элементы из другой коллекции типа List<T>.

Поскольку экземпляр класса List<T> создается с конкретным классом Student в качестве параметра, только объекты типа Student могут быть добавлены в коллекцию методом Add( ). В классе List<T> метод Add( ) определен как Add(Tltem). В следующем примере создаются и добавляются в коллекцию шесть студентов. Затем с помощью оператора foreach выполняется итерация по коллекции и каждый

class Program

{

   static void Main(string[] args)

   {

       List<Student> students = new List<Student>();

       students.Add(new Student ("Акинина Ольга","331"));

       students.Add(new Student ("Викулов Михаил","331"));

       students.Add(new Student ("Анисимова Наталья", "321"));

       students.Add(new Student ("Квасов Виктор", "321"));

       students.Add(new Student ("Илларионов Евнений", "334"));

       students.Add(new Student("Кузнецова Татьяна", "334"));

       foreach (Student st in students)

           Console.WriteLine(st);

       string s = Console.ReadLine();

   }

}

На рис.7.1 приведен результат работы программы.

Рис. 7.1. Вывод данных коллекции

Используя класс List<T>, можно не только добавлять элементы и обращаться к ним через перечислитель; этот класс также имеет методы для вставки и удаления элементов, очистки коллекции и копирования элементов в массив. Ниже описана еще более мощная функциональность. List<T> представляет методы для поиска и преобразования элементов, для изменения их порядка на противоположный и тому подобных действий.

Поиск элементов

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

Класс List<T> представляет методы Find( ) и FindAll( ) со следующими объявлениями:

public T Find(Predicate<T> match);

public List<T> FindAll(Predicate<T> match);

Оба метода требуют аргумента Predicate<T>. Тип Predicate<T> является делегатом, который ссылается на метод-предикат. Предикат — это метод, возвращающий булевское значение. Если предикат возвращает true, значит соответствие обнаружено и элемент найден. Если он возвращает false, то элемент не добавляется к результату поиска. В соответствии со своим определением, Predicate<T> должен иметь единственный аргумент типа Т. Метод Find( ) возвращает первый элемент, соответствующий предикату, a FindAll( ) — все соответствующие предикату элементы в списке-коллекции.

Итак, нам нужно определить предикат. Мы используем метод предиката StudentPredicate( ) для поиска студентов, обучающихся в определенной группе. Метод определен в классе FindStudent, который инициализируется номером студенческой группы. StudentPredicate( )  принимает объект Student, сравнивает группу студента с той, что была задана в аргументе конструктора, и возвращает true или false:

class FindStudent

{

   private string group;

   public FindStudent(string group)

   {

       this.group = group;

   }

   public bool StudentPredicate(Student student)

   {

       return student.Group == group;

   }

}

Чтобы найти нужных студентов, класс FindStudent инициализируется номером группы — 321, поскольку нам нужны студенты, которые учатся в группе 321. Методом FindAll( ) класса List<T> создается новый экземпляр делегата предиката, и этот делегат принимает метод finder.StudentPredicate. FindAll( ) возвращает список типа List<Student>, который используется в цикле foreach для итерации по всем найденным гонщикам и выдачи их на консоль:

FindStudent finder = new FindStudent("321");

 foreach (Student st in students.FindAll(new Predicate<Student>(finder.StudentPredicate)))

      Console.WriteLine(st);

Результат работы программы приведен на рис. 7.2 .

Рис. 7.2. Результаты поиска студентов группы 321

Выполнение определенного действия

Шаблоны делегатов используются не только с методами Find( ) /FindAll( ); они также применяются для выполнения некоторого действия над каждым элементом коллекции. Класс List<T> представляет метод ForEach( ), который использует делегат Action<T> для выполнения некоторого действия с каждым элементом коллекции.

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

Реализация анонимного метода выводит на консоль информацию о студентах:

students.ForEach(delegate(Student st){Console.WriteLine(st);});

Если понадобится выдавать на экран только определенных студентов, удовлетворяющих предикату, то для этого методы ForEach( ) и FindAll( ) можно комбинировать. Метод ForEach( ) применяется к списку, возвращенному FindAll( ):

students.FindAll(new Predicate<Student>(finder.StudentPredicate)).
ForEach(
delegate(Student st){Console.WriteLine(st);});

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

Сортировка

Класс List<T> позволяет сортировать элементы. Для метода Sort( ) определено несколько перегрузок. Ему могут быть переданы аргументы — обобщенный делегат Comparison<Т>, обобщенный интерфейс  IComparer<T>, либо диапазон вместе с обобщенным интерфейсом IComparer<T>:

public void List<T>.Sort();

public void List<T>.Sort(Comparison<T>);

public void List<T>.Sort(IComparer<T>);

public void List<T>.Sort(Int32, Int32, ICoraparer<T>);

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

Comparison<T> — это делегат для метода, который имеет два параметра типа Т и возвращает тип int. Если значения параметров равны, возвращается ноль, если первый параметр меньше второго, должно быть возвращено значение меньше нуля; в противном случае возвращается значение больше нуля.

В простой реализации метода сравнения можно использовать анонимный метод, следующем примере имеем два объекта (st1, st2) типа Student, у которых сравниваются свойства FullName с помощью строкового метода CompareTo( ):

students.Sort(delegate(Student st1, Student st2) {return st1.FullName.CompareTo(st2.FullName);});

При вызове этого метода весь список гонщиков сортируется по их именам (рис. 7.3).

Вместо применения делегата для сортировки коллекции также может быть применен интерфейс IComparer<T>.

Класс StudentComparer реализует интерфейс IComparer<T> для типа Student. Этот класс позволяет отсортировать коллекцию как по имени студента, так и номеру группы.

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

Вариант выбора сортировки по имени или группе определяется вложеным перечислимым типом CompareType. Значение CompareType передается конструктору класса StudentComparer. Интерфейс IComparer< Student > определяет метод Compare, используемый при сортировке. В реализации этого метода применяется метод CompareTo( ) класса String — либо по имени студента, либо по номеру группы:

class StudentComparer: IComparer<Student>

{

   public enum CompareType

   {

       FullName,

       Group

   }

   private CompareType compareType;

   public StudentComparer(CompareType compareType)

   {

       this.compareType = compareType;

   }

   public int Compare(Student st1, Student st2)

   {

       int result = 0;

       switch (compareType)

       {

           case CompareType.FullName:

               result = st1.FullName.CompareTo(st2.FullName);

               break;

           case CompareType.Group:

               result = st1.Group.CompareTo(st2.FullName);

               break;

       }

       return result;

   }

}

Когда сортируется коллекция студентов, методу Sort( ) передается новый экземпляр класса StudentComparer, и в следующем примере сортировка выполняется по номерам групп:

students.Sort(new StudentComparer(StudentComparer.CompareType.Group));

При вызове этого метода весь список студентов сортируется по учебной группк (рис. 7.4).

Рис. 7.4. Результаты сортировки студентов по группе

Преобразования типов

С помощью метода ConvertAll( ) класса List<T> все элементы коллекции могут быть преобразованы в другой тип. Метод ConvertAll( ) использует делегат Converter, определенный следующим образом:

public sealed delegate TOutput Converter<TInput, TOutput>(TInput from);

При преобразовании используются обобщенные типы TInput и TOutput. ТInput — это аргумент метода делегата, a TOutput — возвращаемый тип.

В следующем примере все элементы типа Student должны быть преобразованы в тип Person. В то время как тип Student включает полное имя и номер группы, тип Person включает имя (firstname) и фамилию (lastname). При таком преобразовании номер группы может быть проигнорирована, но имя нужно преобразовать в firstname и lastname:

class Person

{

   private string firstname;

   private string lastname;

   public Person(string firstname, string lastname)

   {

       this.firstname = firstname;

       this.lastname = lastname;

   }

   public override string ToString()

   {

       return firstname+ "  "+lastname;

   }

}

Преобразование выполняется вызовом метода students.ConvertAll<Person>. Параметр этого метода определен, как анонимный метод с аргументом типа Student, возвращающий значение типа Person. В реализации анонимного метода создается и возвращается новый объект Person. Свойство FullName объекта Student преобразуется в firstname и
lastname:

List<Person> persons = students.ConvertAll<Person>(delegate(Student st)

  {int ixSeparator = st.FullName.LastIndexOf(" ") + 1;

   string firstname = st.FullName.Substring(ixSeparator,

         st.FullName.Length - ixSeparator);

   string  lastname = st.FullName.Substring(0,

                                         ixSeparator - 1);

    return new Person(firstname, lastname);};

В результате такого преобразования получается список конвертированных объектов Person — то есть List<Person> (рис. 7.5).

Рис. 7.5. Результаты преобразования класса Student в класс Person

Использование класса Queue<T>

Класс Queue<T> обладает той же функциональностью, что и класс Queue — то есть это просто обобщенная версия очереди. Элементы обрабатываются по алгоритму «первым пришел — первым обслужен» (FIFO).

В качестве примера применения класса Queue<T> рассмотрим приложение управления документацией. Один поток управления будет добавлять документы в очередь, а второй — извлекать из очереди и обрабатывать.

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

public class Document

{

   private string title;

   private string content;

   public string Title

   {

       get { return title; }

   }

   public string Content

   {

       get { return content; }

   }

   public Document(string title, string content)

   {

       this.title = title;

       this.content = content;

   }

}

 

Класс DocumentManagerнадстройка над Queue<T>. Класс
DocumentManager определяет, как обрабатываются документы: добавляются в очередь методом AddDocument( ) и извлекаются методом
GetDocument( ).

Внутри метода AddDocument( ) документ добавляется в конец очереди с помощью метода Enqueue( ). Первый документ из очереди читается методом Dequeue( ) внутри GetDocument( ). Поскольку к DocumentManager могут иметь одновременный доступ несколько потоков параллельно, доступ к очереди блокируется оператором lock.

IsDocumentAvailable — свойство булевского типа, доступное только для чтения; оно возвращает true, если в очереди есть документы и false — в противном случае:

public class DocumentManager

{

 private readonly Queue<Document> documentQueue = new Queue<Document>();

 public void AddDocument(Document doc)

  {

    lock (this)

    {

       documentQueue.Enqueue(doc);

    }

  }

 public Document GetDocument()

  {

     Document doc = null;

     lock (this)

     {

         doc = documentQueue.Dequeue();

     }

     return doc;

  }

 public bool IsDocumentAvailable

  {

    get

   {

     return documentQueue.Count > 0;

   }

  }

}

Класс ProcessDocuments обрабатывает документы из очереди в отдельном потоке. Единственный его метод, который доступен извне — это Start( ). В теле метода Start( ) создается экземпляр нового потока. Объект
ProcessDocuments создается для запуска потока, а метод Run( ) определен как стартовый метод потока. ThreadStart — делегат, который ссылается на метод, подлежащий запуску в потоке. После создания объекта Thread поток запускается вызовом Thread.Start( ).

Внутри метода Run( ) класса ProcessDocuments определен бесконечный цикл. В этом цикле свойство IsDocumentAvailable используется для проверки наличия документа в очереди. Если в очереди есть документ, то документ извлекается DocumentManager и обрабатывается. В данном случае вся обработка сводится к отображению информации на консоли. В реальном приложении документ может быть записан в файл, сохранен в базе данных либо отправлен по сети.

public class ProcessDocument

{

 private DocumentManager documentManager;

 public static void Start(DocumentManager dm)

 {

    new Thread(new ProcessDocument(dm).Run).Start();

 }

 protected ProcessDocument(DocumentManager dm)

 {

   documentManager = dm;

 }

 protected void Run()

 {

    while (true)

    {

      if (documentManager.IsDocumentAvailable)

        {

          Document doc = documentManager.GetDocument();

          Console.WriteLine("Обработка документа    {0}", doc.Title);

        }

      Thread.Sleep(new Random().Next(20));

     }

 }

}

В методе Main( ) приложения создается объект DocumentManager, создается 100 документов с номерами от 0 до 99 и запускается поток обработки документов. Затем создается 10 документов с номерами от 100 до 110, и все они добавляются в DocumentManager. В реальном приложении документы могут поступать от Web-службы.

class Program

{

 static void Main(string[] args)

 {

   DocumentManager dm = new DocumentManager();

   for (int i = 0; i < 100; i++)

   {

     Document doc = new Document("Документ " + i.ToString(), "Содержание  " + i.ToString());

     dm.AddDocument(doc);

     Thread.Sleep(new Random().Next(10));

   }

   Console.WriteLine("\nФормирование документов завершено\n");

   ProcessDocument.Start(dm);

   for (int i = 101; i < 110; i++)

   {

     Document doc = new Document("Документ "+i.ToString(), "Содержание  "+i.ToString());

     dm.AddDocument(doc);

     Console.WriteLine("Добавленный документ  {0}", doc.Title);

     Thread.Sleep(new Random().Next(20));

   }

 }

}

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

а) начало вывода

б) окончание вывода

Рис. 7.6. Добавление и извлечение из очереди документов

Создание пользовательских обобщенных классов

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

public class MyGeneric<T>

{

 private  T member;

 public void Method(T obj)

 { }

}

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

Ниже мы переделаем класс DocumentManager, который был определен ранее. Попытаемся построить класс, достаточно гибкий, чтобы он поддерживал не только тип Document. Вот новая версия класса:

public class DocumentManager<T>: IDocumentManager<T>

{

   private readonly Queue<T> documentQueue = new Queue<T>();

   public void AddDocument(T doc)

   {

       lock (this)

       {

           documentQueue.Enqueue(doc);

       }

   }

   public T GetDocument()

   {

       T doc = default(T);

       lock (this)

       {

           doc = documentQueue.Dequeue();

       }

       return doc;

   }

   public bool IsDocumentAvailable

   {

       get

       {

           return (documentQueue.Count > 0)? true : false;

       }

   }

}

Ранее класс Document использовался классом DocumentManager. Теперь мы объявили его как использующий тип Т. DocumentManager стал настолько гибким, что теперь позволяет использовать в качестве Т любой тип. Этот тип может применяться с переменными внутри класса. Как здесь показано, обобщенный тип также может быть применен при использовании обобщенного типа, такого как Queue<T>. Тот же тип, что служит параметром при создании экземпляра DocumentManager<T>, применяется для создания экземпляра Queue<T>:

public class DocumentManager<T>: IDocumentManager<T>

{

   private readonly Queue<T> documentQueue = new Queue<T>();

Методы AddDocument( ) и GetDocument( ) определяют Т в качестве типа параметра и типа возврата:

public void AddDocument(T doc)

   {

// . . . .

   }

public T GetDocument()

   {

     // .   

   }

Обобщенному типу невозможно присвоить null. Причина в том, что вместо него может быть подставлен тип значения, не допускающий null, a null допускаются только со ссылочными типами. Чтобы обойти эту проблему, можно воспользоваться ключевым словом default. Оно позволяет присваивать null ссылочным типам и 0 — типам значений.

T doc = default(T);

Проведем изменение класса ProcessDocuments для того чтобы сделать его обобщенным. Поскольку ProcessDocuments вызывает методы класса Document, для него должна использоваться конструкция where, специфицирующая методы и свойства, которые должны присутствовать у обобщенного типа. Конструкция where может требовать от обобщенного типа реализации интерфейса или наследования от определенного базового класса.

Новая версия ProcessDocuments будет независимой от класса Document, равно как и от класса DocumentManager. Это достигается определением интерфейсов IDocument и IDocumentManager<T>, которые определяют методы и свойства, используемые ProcessDocuments<T, U>.

Интерфейс IDocument определяет свойства Title и Content, доступные только для чтения:

public interface IDocument

{

   string Title

   {

       get;

   }

   string Content

   {

       get;

   }

}

Ранее созданный нами класс Document теперь реализует интерфейс IDocument:

public class Document: IDocument

   {     }

IDocumentManager<T>это обобщенный интерфейс. Тип Т для методов этого интерфейса от случая к случаю может быть реализован по-разному:

public interface IDocumentManager<T>

{

   void AddDocument(T doc);

   T GetDocument();

   bool IsDocumentAvailable

   {

       get;

   }

}

Теперь класс DocumentManager<T> реализует интерфейс
IDocumentManager<T>. Обобщенный тип для интерфейса определен классом DocumentManager<T>:

public class DocumentManager<T>: IDocumentManager<T>

   {    }

Теперь изменим класс ProcessDocuments так, чтобы он использовал два обобщенных типа: TDocument и TDocumentManager. Первая конструкция where утверждает, что тип TDocument должен реализовывать интерфейс
IDocument. Одним из типов, который может быть подставлен вместо
TDocument, является Document, потому что этот класс реализует интерфейс IDocument. Однако и любой другой класс, реализующий интерфейс IDocument, также может быть подставлен вместо TDocument. Конструкция where для типа TDocumentManager определяет, что этот тип должен реализовывать интерфейс IDocumentManager<TDocument>. Поэтому в случае подстановки Document вместо TDocument TDocumentManager должен быть классом, реализующим интерфейс IDocumentManager<Document>.

Обобщенные типы TDocument и TDocumentManager теперь используются в реализации:

public class ProcessDocument<TDocument, TDocumentManager>

   where TDocument: IDocument

   where TDocumentManager: IDocumentManager<TDocument>

{

   private TDocumentManager documentManager;

   public static void Start(TDocumentManager dm)

   {

       new Thread(new ProcessDocument<TDocument,

               TDocumentManager>(dm).Run).Start();

   }

   protected ProcessDocument (TDocumentManager dm)

   {

       documentManager = dm;

   }

   protected void Run()

   {

       while (true)

       {

           if (documentManager.IsDocumentAvailable)

           {

               TDocument doc = documentManager.GetDocument();

               Console.WriteLine("Обработка документа    {0}",

                                                    doc.Title);

           }

           Thread.Sleep(new Random().Next(10));

       }

   }

}

В методе Main( ) DocumentManager теперь инициируется классом
Document. Статский метод Start( ) класса ProcessDocuments вызывается с передачей класса Document в качестве параметра TDocument, и класса
DocumentManager<Document> в качестве параметра TDocumentManager:

class Program

{

   static void Main(string[] args)

   {

       DocumentManager<Document> dm = new DocumentManager<Document>();

       for (int i = 0; i < 100; i++)

       {

           Document doc = new Document("Документ " + i.ToString(), "Содержание  " + i.ToString());

           dm.AddDocument(doc);

           Thread.Sleep(new Random().Next(10));

       }

       Console.WriteLine("\nФормирование документов завершено\n");

       

       ProcessDocument<Document, DocumentManager<Document>>.Start(dm);

       for (int i = 101; i < 110; i++)

       {

           Document doc = new Document("Документ "+i.ToString(), "Содержание  "+i.ToString());

           dm.AddDocument(doc);

           Console.WriteLine("Добавленный документ  {0}", doc.Title);

           Thread.Sleep(new Random().Next(20));

       }

   }

}

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

а) начало вывода

б) окончание вывода

Рис. 7.7. Добавление и извлечение из очереди документов для обобщенных классов

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

public class MyClass<T>

where T : IFoo, new() { //-..

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

Обобщенные методы

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

Метод Swap<T> определяет Т как обобщенный тип, который используется для двух аргументов и переменной temp:

void Swap<T>(ref T х, ref Т у)

{

  Т temp;

  temp = х;

  х = у;

  у = temp;

}

Обобщенный метод может быть вызван с указанием конкретного типа вместо обобщенного:

int i = 4;

int j = 5;

Swap<int>(ref i, ref j);

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

int i = 4;

int j = 5;

Swap(ref i, ref j);

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

public class Account

{

   private string name;

   private decimal balance;

   public string Name

   {

       get { return name; }

   }

   public decimal Balance

   {

       get { return balance; }

   }

   public Account(string name, decimal balance)

   {

       this.name = name;

       this.balance = balance;

   }

}

Все счета, чей баланс нужно аккумулировать, добавляются в список счетов типа List<Account>:

List<Account> acounts = new List<Account>();

acounts.Add(new Account("Карасев Станислав ", 100000));

acounts.Add(new Account("Орлова Капитолина ", 150000));

acounts.Add(new Account("Скляной Дмитрий   ", 200000));

acounts.Add(new Account("Афаносьев Яков    ", 500000));

acounts.Add(new Account("Сурмалян Мелина   ", 900000));

Традиционный способ накопления всех объектов Account заключается в применении цикла foreach для прохода по всем объектам Account, как показано ниже. Поскольку оператор foreach требует интерфейса IEnumerable для выполнения итерации по элементам коллекции, аргумент метода
AccumulateSimple( ) имеет тип IEnumerable. Таким образом, метод
AccumulateSimple( ) может быть использован со всеми классами коллекций, которые реализуют интерфейс IEnumerable.

В реализации этого метода выполняется прямое обращение к свойству Balance класса Account:

public static class Algorithm

{

   public static decimal AccumulateSimple( IEnumerable e)

   {

       decimal sum = 0;

       foreach (Account a in e)

       {

           sum += a.Balance;

       }

       return sum;

   }

}

Метод AccumulateSimple вызывается следующим образом:

decimal amount = Algorithm.AccumulateSimple(acounts);

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

class Program

{

   static void Main(string[] args)

   {

       List<Account> acounts = new List<Account>();

       acounts.Add(new Account("Карасев Станислав ", 100000));

       acounts.Add(new Account("Орлова Капитолина ", 150000));

       acounts.Add(new Account("Скляной Дмитрий   ", 200000));

       acounts.Add(new Account("Афаносьев Яков    ", 500000));

       acounts.Add(new Account("Сурмалян Мелина   ", 900000));

       foreach (Account a in acounts)

           Console.WriteLine("Клиент:   {0}  Баланс счета:   {1}", a.Name, a.Balance);

       decimal amount = Algorithm.AccumulateSimple(acounts);

       Console.WriteLine("\n\nБаланс {1} счетов:   {0}", amount, acounts.Count.ToString());

       string s = Console.ReadLine();

   }

}

Рис. 7.8. Результаты работы программы

Проблема первой реализации связана с тем, что она работает только с объектами Account. Этого можно избежать, применив обобщенный метод.

Вторая версия Accumulate( ) принимает только тип, реализующий интерфейс IAccount:

public interface IAccount

{

   string Name

   {

       get;

   }

   decimal Balance

   {

       get;

   }

}

В рассматриваетом нами примере класс Account должен поддерживать интерфейс IAccount. 

public class Account: IAccount

Как мы уже видели в случае обобщенных классов, обобщенный тип может быть ограничен с помощью конструкции where. To же ограничение, которое используется с обобщенными классами, может быть применено и к обобщенным методам. Изменим параметр метода Accumulate( ) на
IEnumerable<T>. IEnumerable<T> - это обобщенная версия интерфейса
IEnumerable, которая реализована обобщенными классами коллекций:

public static class Algorithm

{

   public static decimal Accumulate<TAccount>(IEnumerable<TAccount> coll)

       where TAccount : IAccount

   {

       decimal sum = 0;

       foreach (TAccount a in coll)

       {

           sum += a.Balance;

       }

       return sum;

   }

}

Одним важным ограничением конструкции where в С# 2.0 является невозможность определения операций, которые должны быть реализованы обобщенным типом. Операции нельзя определять в интерфейсах. Конструкцией where можно определять только базовые классы, интерфейсы и конструктор по умолчанию.

Новая версия метода Accumulate( ) может быть вызвана с шаблонным параметром:

decimal amount = Algorithm.Accumulate<Account>(acounts);

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

decimal amount = Algorithm.Accumulate(acounts);

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

Обобщенные делегаты

Делегаты — это безопасные к типам ссылки на методы. Благодаря концепции обобщенных делегатов, параметры делегатов могут быть определены позднее.

Изменим метод Accumulate( ), чтобы он использовал два обобщенных типа. Тип TInput будет типом объектов, которые нужно накапливать, a TSummary — типом возврата. Первый параметр Accumulate( ) — интерфейс IEnumerable<T>, как и раньше. Второй параметр требует делегата Action, который ссылается на метод, вызываемый для накопления балансов.

При реализации метод, на который ссылается делегат Action, вызывается для каждого элемента и затем возвращается сумма вычислений:

public static class Algorithm

{

   public delegate TSummary Action<TInput, TSummary>(TInput t, TSummary u);

   public static TSummary Accumulate<TInput, TSummary>(IEnumerable<TInput>

                  coll, Action<TInput, TSummary> action)

   {

       TSummary sum = default(TSummary);

       foreach (TInput input in coll)

       {

           sum = action(input, sum);

       }

       return sum;

   }

}

Метод Accumulate( ) может быть вызван методом Main с применением анонимного метода, который определяет баланс счета, в качестве второго параметра:

decimal amount = Algorithm.Accumulate<Account, decimal>(acounts, delegate(Account acc, decimal d)

  { return acc.Balance + d;});

Если в дополнение к Account балансы могут понадобиться более одного раза, то можно перенести функциональность в отдельный метод —
AccountAdder( ) класса Algorithm:

public static decimal AccountAdder(Account acc, decimal d)

   {

       return acc.Balance + d;

   }

Тогда его можно использовать с методом Accumulate( ) в методе Main:

           

decimal amount = Algorithm.Accumulate<Account, decimal>(acounts, Algorithm.AccountAdder);

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

Применив метод AccumulateIf( ), можно сделать метод Accumulate( ) более гибким. В методе AccumulateIf( ) добавим дополнительный параметр типа Predicate<T>. Делегат Predicate<T> ссылается на метод, который должен быть вызван для проверки счета, участвующего в накоплении. В цикле foreach метод Action будет вызван только в том случае, если предикат match возвратит true:

public static TSummary AccumulateIf<TInput, TSummary>

       (IEnumerable<TInput> coll,

       Action<TInput, TSummary> action,

       Predicate<TInput> match)

   {

       TSummary sum = default(TSummary);

       foreach (TInput input in coll)

       {

           if (match(input))

           {

               sum = action(input, sum);

           }

       }

       return sum;

  } 

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

 decimal amount = Algorithm.AccumulateIf<Account, decimal>(

       acounts,

       delegate(Account acc, decimal d) { return acc.Balance + d; },

       delegate(Account acc){return acc.Balance > 200000 ? true : false;});

Результаты работы программы с использованием
метода
AccumulateIf( ) приведен на рис. 7.9

Рис. 7.9. Результаты работы программы с использованием
метода
AccumulateIf( ) 

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

Основное назначение обобщений — классы коллекций. Среда .NET расширена пространством имен System.Collections.Generic, включающим обобщенные версии классов коллекций из пространства имен
System.Collections. Мы рассмотрели использование нескольких обобщенных классов коллекций, таких как List<T>, Queue<T>.


 

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

17957. ФІНАНСОВЕ ПЛАНУВАННЯ НА ПІДПРИЄМСТВАХ 251.5 KB
  Тема 10 ФІНАНСОВЕ ПЛАНУВАННЯ НА ПІДПРИЄМСТВАХ Зміст завдання та методи фінансового планування. Зміст і структура фінансового плану підприємства. Зміст оперативного фінансового плану надходження коштів і здійснення платежів. Касовий план. 10.1. Зміс
17958. ФІНАНСОВА САНАЦІЯ ПІДПРИЄМСТВ 133 KB
  Тема 11 ФІНАНСОВА САНАЦІЯ ПІДПРИЄМСТВ Фінансова санація підприємств. Фінансова криза на підприємстві. Санаційний аудит. Розробка програми санації. Санація шляхом реорганізації реструктуризації. 11.1. Фінансова санація підприємств Заходи щодо о...
17959. ГРОШОВІ НАДХОДЖЕННЯ ПІДПРИЄМСТВ 145.5 KB
  Тема З ГРОШОВІ НАДХОДЖЕННЯ ПІДПРИЄМСТВ Економічна характеристика склад і класифікація грошових надходжень підприємств. Доходи виручка від реалізації продукції. Доходи від фінансовоінвестиційної та іншої діяльності. 3.1. Характеристика склад і кла
17960. ОСНОВИ ФІНАНСІВ ПІДПРИЄМСТВ 143.5 KB
  Тема1 ОСНОВИ ФІНАНСІВ ПІДПРИЄМСТВ Поняття і сутність фінансів підприємств. Функції фінансів підприємств. Грошові доходи грошові фонди фінансові ресурси підприємств. Основи організації фінансів підприємств. Фінансова діяльність підприємства. Зміст...
17961. Финансовая деятельность государства 131.5 KB
  ЛЕКЦИЯ 1 Финансовая деятельность государства 1. Понятие финансов и сущность финансовой деятельности государства. 2. Принципы и методы финансовой деятельности государства. 3. Финансовая система Украины ее состав. 4. Система и правовое положение органов вл...
17962. Предмет, метод и система финансового права 115.5 KB
  ЛЕКЦИЯ 2 Предмет метод и система финансового права 1. Понятие финансового права. 2. Место финансового права в системе права. 3. Понятие и виды финансовоправовых норм. 4. Финансовоправовые отношения их особенности. 5. Система финансового права. 6. Исто...
17963. Правовые основы финансового контроля в Украине 132 KB
  ЛЕКЦИЯ 3 Правовые основы финансового контроля в Украине 1. Сущность и назначение финансового контроля. 2. Организация и органы финансового контроля. 3. Формы и методы финансового контроля. 1. Сущность и назначение финансового контроля Контроль за ра
17964. Государственный бюджет Украины и бюджетное право 175.5 KB
  ЛЕКЦИЯ 4 Государственный бюджет Украины и бюджетное право . 1. Понятие бюджета и его роль в обществе. 2. Понятие бюджетного права и его источники. 3. Бюджетноправовые отношения в обществе. 4. Бюджетная система. 5. Бюджетная классификация. 6. Государствен...
17965. Бюджетный процесс в Украине и его участники 447.5 KB
  ЛЕКЦИЯ 5 Бюджетный процесс в Украине и его участники 1. Понятие и содержание бюджетного процесса. 2. Распорядители бюджетных средств и бюджетные назначения. 3. Порядок составления проекта государственного бюджета Украины. 4. Порядок рассмотрения и принят...