
- •1. Понятие модуля. Принципы модульного программирования. Понятие объекта как динамического модуля.
- •2. Понятие класса. Понятие метода. Представление метода в виде обычной процедуры. Понятие конструктора и деструктора.
- •3. Понятие свойства. Методы получения и установки значений свойств. Свойства-массивы (в некоторых языках программирования). Индексаторы (в некоторых языках программирования).
- •Информация о типе времени выполнения программы
- •5. Классы в программных модулях. Атрибуты доступа к элементам объектов. Термин «инкапсуляция».
- •Термин «инкапсуляция»
- •Virtual – в базовом классе
- •7. Понятие ссылки на метод объекта (или делегата – в зависимости от языка программирования). Понятие события. Применение ссылок на методы для расширения объектов.
- •8. Понятие метакласса (в некоторых языках программирования). Методы, применяемые к классам. Виртуальные конструкторы (в некоторых языках).
- •Понятие метакласса (в некоторых языках программирования)
- •Виртуальные конструкторы (в некоторых языках)
- •3) Finally – вызвать free.
- •Если глобально-уникальный идентификатор назначается интерфейсу, то он записывается после ключевого слова interface и заключается в квадратные скобки, например:
- •Microsoft Visual Studio
- •Import Network;
- •Var Stat: SocketStat;
- •Var SocketStatCollection: … ;
- •Конструкторы и деструкторы
- •Стандартные конструкторы
- •Создание объектов по значению (на стеке) и по ссылке (в динамической памяти)
- •Операторы new и delete
- •Размещающий оператор new
- •Порядок конструирования и разрушения объектов
- •Вложенные определения классов
- •«Друзья» класса
- •Статические члены класса
- •19. Перегрузка бинарных операторов. Перегрузка унарных операторов. Перегрузка операторов преобразования типа.
- •Индексаторы
- •Механизм вызова событий
- •Создание пользовательских обобщенных коллекций
- •Создание обобщенных интерфейсов
- •Несколько слов о вложенных делегатах
- •25. Понятие итератора в языке c#. Оператор foreach. Оператор yield.
- •И напоследок... Блок finally
- •26. Понятие атрибутов в языке c#. Создание пользовательских атрибутов. Анализ атрибутов во время выполнения программы. Понятие рефлексии (reflection) в языке c#. Сериализация объектов.
- •Что такое метаданные и зачем они нужны?
- •1. Метаданные в .Net обязательны и универсальны.
- •2. Метаданные в .Net общедоступны.
- •3. Метаданные в .Net исчерпывающи.
- •4. Метаданные в .Net расширяемы.
- •5. Метаданные в .Net конструируемы программно.
- •Получение экземпляра класса Type
- •Динамическая загрузка сборок
- •Динамическая загрузка типов
- •Исследование типа
- •Характеристики типа как целого
- •Члены класса
- •Исследование объекта
- •Динамическое создание объекта и вызов методов
- •Создание объекта по его типу
- •Декларативное программирование
- •Новые механизмы абстракции?
- •Динамическое создание типов
- •Роль графов объектов
- •Formatter сериализации
- •XmlSerializer
- •Интерфейсы iFormatter и iRemotingFormatter
- •Точность типов среди форматеров
- •28*. Ооп в языке программирования Smalltalk. Достоинства и недостатки этого языка в сравнениии с языком программирования c#.
Создание обобщенных интерфейсов
Вы уже видели при рассмотрении пространства имен System.Collections.Generic, что обобщенные интерфейсы в C# также допустимы (например, IEnumerable<T>). Вы, конечно, можете определить свои собственные обобщенные интерфейсы (как с ограничениями, так и без ограничений). Предположим, что нужно определить интерфейс, который сможет выполнять бинарные операции с параметрами обобщенного типа.
public interface IBinaryOperations<T> { T Add(T arg1, T arg2); T Subtract(T arg1, T arg2); T Multiply(T arg1, T arg2); T Divide(T arg1, T arg2); } |
Известно, что интерфейсы остаются почти бесполезными, пока они не реализованы некоторым классом или структурой. При реализации обобщенного интерфейса поддерживающий его тип указывает тип заполнителя.
public class BasicMath : IBinaryOperations<int> { public int Add(int arg1, int arg2) { return arg1 + arg2; }
public int Subtract(int arg1, int arg2) { return arg1 - arg2; }
public int Multiply(int arg1, int arg2) { return arg1 * arg2; }
public int Divide(int arg1, int arg2) { return arg1 / arg2; } } |
После этого вы можете использовать BasicMath.
Если вместо этого требуется создать класс BasicMath, действующий на числа с плавающим десятичным разделителем, можно конкретизировать параметр типа так.
public class BasicMath : IBinaryOperations<double> { public double Add(double arg1, double arg2) { return arg1 + arg2; } ... } |
C# Generics and C++ templates are both language features that provide support for parameterized types. However, there are many differences between the two.
At the syntax level, C# generics are a simpler approach to parameterized types without the complexity of C++ templates. In addition, C# does not attempt to provide all of the functionality that C++ templates provide. At the implementation level, the primary difference is that C# generic type substitutions are performed at runtime and generic type information is thereby preserved for instantiated objects.
The following are the key differences between C# Generics and C++ templates:
C# generics do not provide the same amount of flexibility as C++ templates. For example, it is not possible to call arithmetic operators in a C# generic class, although it is possible to call user defined operators.
C# does not allow non-type template parameters, such as
template C<int i> {}
.C# does not support explicit specialization; that is, a custom implementation of a template for a specific type.
C# does not support partial specialization: a custom implementation for a subset of the type arguments.
C# does not allow the type parameter to be used as the base class for the generic type.
C# does not allow type parameters to have default types.
In C#, a generic type parameter cannot itself be a generic, although constructed types can be used as generics. C++ does allow template parameters.
C++ allows code that might not be valid for all type parameters in the template, which is then checked for the specific type used as the type parameter. C# requires code in a class to be written in such a way that it will work with any type that satisfies the constraints. For example, in C++ it is possible to write a function that uses the arithmetic operators + and - on objects of the type parameter, which will produce an error at the time of instantiation of the template with a type that does not support these operators. C# disallows this; the only language constructs allowed are those that can be deduced from the constraints.
Установка ограничений на параметры обобщенных классов
В настоящий момент класс CarCollection<T> привлекает нас только открытыми методами с уникальными именами. Кроме того, пользователь объекта может создать экземпляр CarCollection<T> и указать практически любой параметр типа.
// Это синтаксически корректно, но выглядит, // по крайней мере, странно... CarCollection<int> myInts = new CarCollection<int>(); myInts.AddCar(5); myInts.AddCar(11); |
Чтобы проиллюстрировать другую форму типичного непредусмотренного использования объекта, предположим, что вы создали два новых класса — SportsCar (спортивная машина) и MiniVan (минивэн), — которые являются производными от Car.
public class SportsCar : Car { public SportsCar(string p, int s) : base(p, s){} // Дополнительные методы для SportsCar. }
public class MiniVan : Car { public MiniVan(string p, int s) : base(p, s){} // Дополнительные методы для MiniVan. } |
В соответствии с законами наследования, в коллекцию CarCollection<T>, созданную с параметром типа Car, можно добавлять и типы MiniVan и SportsCar.
// CarCollection<Car> может хранить любой тип, производный от Car. CarCollection<Car> myCars = new CarCollection<Car>(); myInts.AddCar(new MiniVan("Family Truckster", 55)); myInts.AddCar(new SportsCar("Crusher", 40)); |
Это синтаксически корректно, но что делать, если вдруг понадобится добавить в CarCollection<T> новый открытый метод, например, с именем PrintPetName()? Такая задача кажется простой — достаточно получить доступ к подходящему элементу из List<T> и вызвать свойство PetName.
// Ошибка! // System.Объект не имеет свойства с именем PetName. public void PrintPetName(int pos) { Console.WriteLine(arCars[pos].PetName); } |
Однако в таком виде программный код скомпилирован не будет, поскольку истинная суть <T> еще не известна, и вы не можете с уверенностью утверждать, что какой-то элемент типа List<T> будет иметь свойство PetName. Когда параметр типа не имеет никаких ограничений (как в данном случае), обобщенный тип называется свободным (unbound). По идее параметры свободного типа должны иметь только члены System.Object (которые, очевидно, не имеют свойства PetName).
Вы можете попытаться “обмануть” компилятор путем преобразования элемента, возвращенного из метода индексатора List<T>, в строго типизованный объект Car, чтобы затем вызвать PetName возвращенного объекта.
// Ошибка! // Нельзя превратить тип 'T' в 'Car'! public void PrintPetName(int pos) { Console.WriteLine(((Car)arCars[pos]).PetName); } |
Но это тоже не компилируется, поскольку компилятор не знает значения параметра типа <T> и не может гарантировать, что преобразование будет законным.
Для решения именно таких проблем обобщения .NET могут опционально определяться с ограничениями, для чего используется ключевое слово where.
Ограничение обобщения |
Описание |
where T : struct |
Параметр типа <T> должен иметь в цепочке наследования System.ValueType |
where T : class |
Параметр типа <T> не должен иметь в цепочке наследования System.ValueType (т.е. <T> должен быть ссылочным типом) |
where T : new() |
Параметр типа <T> должен иметь конструктор, заданный по умолчанию. Это полезно тогда, когда обобщенный тип должен создать экземпляр параметра типа, а вы не имеете ясных предположений о формате пользовательских конструкторов. Заметьте, что это ограничение должно быть последним в списке ограничений, если у типа их несколько |
where T : БазовыйКласс |
Параметр типа <T> должен быть производным класса, указанного параметром БазовыйКласс |
where T : Интерфейс |
Параметр типа <T> должен реализовывать интерфейс, указанный параметром Интерфейс |
При наложении ограничений с помощью ключевого слова where список ограничений размещается после имени базового класса обобщенного типа и списка интерфейсов. В качестве конкретных примеров рассмотрите следующие ограничения обобщенного класса MyGenericClass.
// Вложенные элементы должны иметь конструктор, // заданный по умолчанию. public class MyGenericClass<T> where T : new() {...}
// Вложенные элементы должны быть классами, реализующими IDrawable // и поддерживающими конструктор, заданный по умолчанию. public class MyGenericClass<T> where T : class, IDrawable, new() {...}
// MyGenericClass получается из MyBase и реализует ISomeInterface, // а вложенные элементы должны быть структурами. public class MyGenericClass<T> : MyBase, ISomeInterface where T : struct {...} |
При построении обобщенного типа, в котором указано несколько параметров типа, вы можете указать уникальный набор ограничений для каждого из таких параметров.
// <K> должен иметь конструктор, заданный по умолчанию, // а <T> должен реализовывать открытый интерфейс IComparable. public class MyGenericClass<K, T> where K : new() where T : IComparable<T> {...} |
Если вы хотите изменить тип CarCollection<T> так, чтобы в него можно было поместить только производные от Car, вы можете записать следующее.
public class CarCollection<T> : IEnumerable<T> where T : Car { ... public void PrintPetName(int pos) { // Поскольку теперь все элементы должны быть из семейства Car, // свойство PetName можно вызывать непосредственно. Console.WriteLine(arCars[pos].PetName); } } |
При таких ограничениях на CarCollection<T> реализация PrintPetName() становится очень простой, поскольку теперь компилятор может предполагать, что <T> является производным от Car. Более того, если указанный пользователем параметр типа не совместим с Car, будет сгенерирована ошибка компиляции.
// Ошибка компиляции! CarCollection<int> myInts = new CarCollection<int>(); |
Вы должны понимать, что обобщенные методы тоже могут использовать ключевое слово where. Например, если нужно гарантировать, чтобы методу Swap(), созданному в этой главе выше, передавались только типы, производные от System.ValueType, измените свой программный код так.
// Этот метод переставит любые типы, характеризуемые значениями. static void Swap<T>(ref T a, ref T b) where T : struct { ... } |
Обобщенные делегаты
Предположим, например, что требуется определить делегат, который сможет вызывать любой метод, возвращающий void и принимающий один аргумент. Если аргумент может меняться, это можно учесть с помощью параметра типа. Для примера рассмотрим следующий программный код (обратите внимание на то, что целевые объекты делегата регистрируются как с помощью “традиционного” синтаксиса делегата, так и с помощью группового преобразования метода).
namespace GenericDelegate { // Этот обобщенный делегат может вызвать любой метод, // возвращающий void и принимающий один параметр. public delegate void MyGenericDelegate<T>(T arg);
class Program { static void Main(string[] args) { Console.WriteLine("***** Обобщенные делегаты *****\n");
// Регистрация цели с помощью 'традиционного' // синтаксиса делегата. MyGenericDelegate<string> strTarget = new MyGenericDelegate<string>(StringTarget); strTarget("Некоторые строковые данные");
// Регистрация цели с помощью // группового преобразования метода. MyGenericDelegate<int> intTarget = IntTarget; intTarget(9); Console.ReadLine(); }
static void StringTarget(string arg) { Console.WriteLine("arg в верхнем регистре: {0}", arg.ToUpper()); }
static void IntTarget(int arg) { Console.WriteLine("++arg: {0}", ++arg); } } } |
Обратите внимание на то, что MyGenericDelegate<T> определяет один параметр типа, представляющий аргумент, отправляемый целевому объекту делегата. При создании экземпляра этого типа требуется конкретизировать значение параметра типа, а также имя метода, вызываемого делегатом. Так, если вы укажете строковый тип, то отправите целевому методу строковое значение.
// Создание экземпляра MyGenericDelegate<T> // со значением string для параметра типа. MyGenericDelegate<string> strTarget = new MyGenericDelegate<string>(StringTarget); strTarget("Некоторые строковые данные"); |
С учетом формата объекта strTarget метод StringTarget() должен теперь получить в качестве параметра одну строку.
static void StringTarget(string arg) { Console.WriteLine("arg в верхнем регистре: {0}", arg.ToUpper()); } |
Как видите, обобщенные делегаты предлагают более гибкий подход для указания вызываемых методов.
В рамках .NET 1.1 аналогичного результата можно достичь с помощью базового System.Object.
public delegate void MyDelegate(object arg); |
Это позволяет посылать любой тип данных целевому объекту делегата, но при этом не гарантируется типовая безопасность, а кроме того, остаются нерешенными проблемы создания объектных образов. Предположим, например, что мы создали два экземпляра MyDelegate, которые указывают на один и тот же метод MyTarget. Обратите внимание на проблемы создания объектных образов и восстановления значений, а также на отсутствие гарантий типовой безопасности.
class Program { static void Main(string[] args) { ... // Регистрация цели с помощью // 'традиционного' синтаксиса делегата. MyDelegate d = new MyDelegate(MyTarget); d("Дополнительные строковые данные");
// Регистрация цели с помощью // группового преобразования метода. MyDelegate d2 = MyTarget; d2(9); // Проблема объектного образа. ... }
// Ввиду отсутствия типовой безопасности мы должны // определить соответствующий тип до преобразования. static void MyTarget(object arg) { if(arg is int) { int i = (int)arg; // Проблема восстановления значения. Console.WriteLine("++arg: {0}", ++i); } if(arg is string) { string s = (string)arg; Console.WriteLine("arg в верхнем регистре: {0}", s.ToUpper()); } } } |
Когда вы посылаете целевому объекту тип, характеризуемый значением, это значение (конечно же) “упаковывается” в объект и “распаковывается” после его получения целевым методом. Точно также, поскольку поступающий параметр может быть всем чем угодно, вы должны динамически проверить соответствующий тип перед тем, как выполнить преобразование. С помощью обобщенных делегатов вы можете получить всю необходимую гибкость без “проблем”.