Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
OOP_answers (1).docx
Скачиваний:
1
Добавлен:
01.03.2025
Размер:
2.9 Mб
Скачать

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

Итак, пространство имен System.Collections.Generic предлагает множество типов, позволяющих создавать эффективные контейнеры, удовлетворяющие требованиям типовой безопасности. С учетом множества доступных вариантов очень велика вероятность того, что в .NET 2.0 у вас вообще не возникнет необходимости в построении пользовательских типов коллекции. Тем не менее, чтобы показать, как строится обобщенный контейнер, нашей следующей задачей будет создание обобщенного класса коллекции, который мы назовем CarCollection<T>.

Подобно созданному выше необобщенному типу CarCollection, наш новый вариант будет использовать уже существующий тип коллекции для хранения своих элементов (в данном случае это List<>). Будет реализована и поддержка цикла foreach путем реализации обобщенного интерфейса IEnumerable<>. Обратите внимание на то, что IEnumerable<> расширяет необобщенный интерфейс IEnumerable, поэтому компилятор ожидает, что вы реализуете две версии метода GetEnumerator(). Вот как может выглядеть соответствующая модификация.

public class CarCollection<T> : IEnumerable<T>

{

private List<T> arCars = new List<T>();

public T GetCar(int pos)

{ return arCars[pos]; }

public void AddCar(T c)

{ arCars.Add(c); }

public void ClearCars()

{ arCars.Clear(); }

public int Count

{ get { return arCars.Count; } }

// IEnumerable<T> расширяет IEnumerable, поэтому

// нужно реализовать обе версии GetEnumerator().

IEnumerator<T> IEnumerable<T>.GetEnumerator()

{ return arCars.GetEnumerator(); }

IEnumerator IEnumerable.GetEnumerator()

{ return arCars.GetEnumerator(); }

}

Этот обновленный тип CarCollection<T> можно использовать так.

static void Main(string[] args)

{

Console.WriteLine("* Пользовательская обобщенная коллекция *\n");

// Создание коллекции объектов Car.

CarCollection<Car> myCars = new CarCollection<Car>();

myCars.AddCar(new Car("Rusty", 20));

myCars.AddCar(new Car("Zippy", 90));

foreach (Car c in myCars)

{

Console.WriteLine("PetName: {0}, Speed: {1}",

c.PetName, c.Speed);

}

Console.ReadLine();

}

При создании обобщенных методов для вас может оказаться сюрпризом появление ошибок компилятора, когда с параметрами типа используются операции C# (+, -, *, == и т.д.). Например, я уверен, вы сочли бы полезными классы Add(), Subtract(), Multiply() и Divide(), способные работать с обобщенными типами.

// Ошибка компиляции!

// Нельзя применять операции к параметрам типа!

public class BasicMath<T>

{

public T Add(T arg1, T arg2)

{ return arg1 + arg2; }

public T Subtract(T arg1, T arg2)

{ return arg1 - arg2; }

public T Multiply(T arg1, T arg2)

{ return arg1 * arg2; }

public T Divide(T arg1, T arg2)

{ return arg1 / arg2; }

}

Как ни печально, этот класс BasicMath<T> не компилируется. Это может показаться большим ограничением, но не следует забывать, что обобщения являются обобщениями. Конечно, тип System.Int32 может прекрасно работать с бинарными операциями C#. Однако, если, например, <T> будет пользовательским классом или типом структуры, компилятор не сможет сделать никаких предположений о характере перегруженных операций +, -, * и /.

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

// Предположим, что создан пользовательский

// обобщенный класс списка.

public class MyList<T>

{

private List<T> listOfData = new List<T>();

}

// Конкретные типы должны указать параметр типа,

// если они получаются из обобщенного

// базового класса.

public class MyStringList : MyList<string>

{}

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

// Обобщенный класс с виртуальным методом.

public class MyList<T>

{

private List<T> listOfData = new List<T>();

public virtual void PrintList(T data) { }

}

public class MyStringList : MyList<string>

{

// В производных методах нужно заменить параметр типа,

// используемый в родительском классе.

public override void PrintList(string data) { }

}

Если производный тип тоже является обобщенным, дочерний класс может (опционально) использовать заменитель типа в своем определении. Однако знайте, что любые ограничения, размещенные в базовом классе, должны “учитываться” и производным типом. Например:

// Обратите внимание, теперь здесь имеется ограничение,

// требующее конструктор по умолчанию.

public class MyList<T> where T : new()

{

private List<T> listOfData = new List<T>();

public virtual void PrintList(T data) { }

}

// Производный тип должен учитывать ограничения базового.

public class MyReadOnlyList<T> : MyList<T> where T : new()

{

public override void PrintList(T data) { }

}

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]