Как правильно писать тесты 46 Цикл разработки 46 Структура проекта с тестами 51 Утверждения (Asserts) 52 Утверждения в форме ограничений 54 Категории 56

Вид материалаТесты

Содержание


Отсутствие поддержки ограничений при использовании операций
Создание обобщенных базовых классов
Создание обобщенных интерфейсов
Подобный материал:
1   ...   39   40   41   42   43   44   45   46   47

Отсутствие поддержки ограничений при использовании операций




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


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

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

public class BasicMath

{

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


// Только для иллюстрации!

// Этот программный код не является допустимым в C# 2.0.

public class BasicMath where T : operator +, operator -,

operator *, operator /

{

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; }

}


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




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


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

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

public class MyList

{

private List listOfData = new List();

}


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

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

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

public class MyStringList : MyList

{}


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


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

public class MyList

{

private List listOfData = new List();

public virtual void PrintList(T data) { }

}


public class MyStringList : MyList

{

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

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

public override void PrintList(string data) { }

}


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


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

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

public class MyList where T : new()

{

private List listOfData = new List();


public virtual void PrintList(T data) { }

}


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

public class MyReadOnlyList : MyList where T : new()

{

public override void PrintList(T data) { }

}


Создание обобщенных интерфейсов




Вы уже видели при рассмотрении пространства имен System.Collections.Generic, что обобщенные интерфейсы в C# также допустимы (например, IEnumerable). Вы, конечно, можете определить свои собственные обобщенные интерфейсы (как с ограничениями, так и без ограничений). Предположим, что нужно определить интерфейс, который сможет выполнять бинарные операции с параметрами обобщенного типа.


public interface IBinaryOperations

{

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

{

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, как и ожидали.


static void Main(string[] args)

{

Console.WriteLine("***** Обобщенные интерфейсы *****\n");

BasicMath m = new BasicMath();

Console.WriteLine("1 + 1 = {0}", m.Add(1, 1));

Console.ReadLine();

}


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


public class BasicMath : IBinaryOperations

{

public double Add(double arg1, double arg2)

{ return arg1 + arg2; }

...

}