Страница 302 из 502
<b>delegate bool SomeOp<in Т>(Т obj);</b>
<b>// Объявить делегат, ковариантный по отношению к обобщенному типу Т.</b>
<b>delegate Т AnotherOp<out Т, V>(V obj);</b>
<b>class Alpha {</b>
<b> public int Val { get; set; }</b>
<b> public Alpha(int v) { Val = v; }</b>
<b>}</b>
<b>class Beta : Alpha {</b>
<b> public Beta(int v) : base(v) { }</b>
<b>}</b>
<b>class GenDelegateVarianceDemo {</b>
<b> // Возвратить логическое значение true, если значение</b>
<b> // переменной obj.Val окажется четным,</b>
<b> static bool IsEven(Alpha obj) {</b>
<b> if ((obj.Val % 2) == 0) return true;</b>
<b> return false;</b>
<b> }</b>
<b> static Beta ChangeIt(Alpha obj) {</b>
<b> return new Beta(obj.Val + 2);</b>
<b> }</b>
<b> static void Main() {</b>
<b> Alpha objA = new Alpha(4);</b>
<b> Beta objB = new Beta(9);</b>
<b> // Продемонстрировать сначала контравариантность.</b>
<b> // Объявить делегат SomeOp<Alpha> и задать для него метод IsEven.</b>
<b> SomeOp<Alpha> checklt = IsEven;</b>
<b> // Объявить делегат SomeOp<Beta>.</b>
<b> SomeOp<Beta> checklt2;</b>
<b> //А теперь- присвоить делегат SomeOp<Alpha> делегату SomeOp<Beta>.</b>
<b> // *** Это допустимо только благодаря контравариантности. ***</b>
<b> checklt2 = checklt;</b>
<b> // Вызвать метод через делегат.</b>
<b> Console.WriteLine(checklt2(objB));</b>
<b> // Далее, продемонстрировать контравариантность.</b>
<b> // Объявить сначала два делегата типа AnotherOp.</b>
<b> // Здесь возвращаемым типом является класс Beta,</b>
<b> //а параметром типа — класс Alpha.</b>
<b> // Обратите внимание на то, что для делегата modifylt</b>
<b> // задается метод Changelt.</b>
<b> AnotherOp<Beta, Alpha> modifyIt = ChangeIt;</b>
<b> // Здесь возвращаемым типом является класс Alpha,</b>
<b> // а параметром типа — тот же класс Alpha.</b>
<b> AnotherOp<Alpha, Alpha> modifyIt2;</b>
<b> // А теперь присвоить делегат modifylt делегату modifyIt2.</b>
<b> // *** Это допустимо только благодаря ковариантности. ***</b>
<b> modifyIt2 = modifyIt;</b>
<b> // Вызвать метод и вывести результаты на экран.</b>
<b> objA = modifyIt2(objA);</b>
<b> Console.WriteLine(objA.Val);</b>
<b> }</b>
<b>}</b>
Выполнение этой программы приводит к следующему результату.
<b>False</b>
<b>6</b>
Каждая операция достаточно подробно поясняется в комментариях к данной программе. Следует особо подчеркнуть, для успешной компиляции программы в объявлении обоих типов делегатов SomeOp and AnotherOp должны быть непременно указаны ключевые слова in и out соответственно. Без этих модификаторов компиляция программы будет выполнена с ошибками из-за отсутствия неявных преобразований типов в означенных строках кода.
Создание экземпляров объектов обобщенных типов
Когда приходится иметь дело с обобщениями, то нередко возникает вопрос: не приведет ли применение обобщенного класса к неоправданному раздуванию кода? Ответ на этот вопрос прост: не приведет. Дело в том, что в C# обобщения реализованы весьма эффективным образом: новые объекты конструируемого типа создаются лишь по мере надобности. Этот процесс описывается ниже.
Когда обобщенный класс компилируется в псевдокод MSIL, он сохраняет все свои параметры типа в их обобщенной форме. А когда конкретный экземпляр класса потребуется во время выполнения программы, то JIT-компилятор сконструирует конкретный вариант этого класса в исполняемом коде, в котором параметры типа заменяются аргументами типа. В каждом экземпляре с теми же самыми аргументами типа будет использоваться один и тот же вариант данного класса в исполняемом коде.
Так, если имеется некоторый обобщенный класс Gen<T>, то во всех объектах типа Gen<T> будет использоваться один и тот же исполняемый код данного класса. Следовательно, раздувание кода исключается благодаря тому, что в программе создаются только те варианты класса, которые действительно требуются. Когда же возникает потребность сконструировать объект другого типа, то компилируется новый вариант класса в исполняемом коде.