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

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

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

1
public delegate void MyDelegate ( long 1, object о );

Объявление такого обобщенного делегата для преобразования выглядит следующим образом:

1
public delegate TOutput Converter<tinput, TOutput>( TInput input ) ;

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

Список параметров-типов следует за именем делегата, но предшествует списку параметров самого делегата.

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

Создание экземпляра делегата Converter — то же самое, что и создание экземпляра любого другого делегата. При создании экземпляра обобщенного делегата можно использовать операцию new и явно предоставить список типов во время компиляции. Или же можно применить сокращенный синтаксис, тогда компилятор самостоятельно выведет типы параметров.

Для удобства ниже еще раз показан код метода Main из этого примера:

1
2
3
4
5
6
7
static void Main()  {
MyContainer<long> IContainer = new MyContainer<long>();
MyContainer<int> iContainer = new MyContainer<int>();
IContainer.Add( 1 ) IContainer.Add( 2 ) , iContainer.Add( 3 ), iContainer.Add( 4
IContainer.Add( iContainer, EntryPoint.IntToLongConverter ),
foreach ( long 1 in IContainer )  {
Console.WriteLine ( 1 );

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

Фактически вызов Add лишен какого-либо явного списка параметров типа в точке вызова. Компилятор в состоянии выполнить ту же работу по сопоставлению типов, чтобы вывести закрытую форму вызванного метода Add, которой в данном случае будет Add.

Можно также с успехом написать код, подобный приведенному ниже, в котором каждый тип указан явно:

1
2
3
4
5
6
7
8
9
10
11
12
static void Main()  {
MyContainer<long> IContainer = new MyContainer<long>();
MyContainer<int> iContainer = new MyContainer<int>();
IContainer.Add( 1 );
IContainer.Add( 2 );
iContainer.Add( 3 );
iContainer.Add( 4 );
IContainer.Add<int>( iContainer,
new Converter<int, long>( EntryPoint.IntToLongConverter) );
foreach ( long 1 in IContainer )  {
Console.WriteLine ( 1 );
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ЭТО НЕ РАБОТАЕТ, КАК ОЖИДАЕТСЯ! ! !
using System;
using System.Collections.Generic;
public delegate void MyDelegate ( int i ) ;
public class DelegateContainer<t> {
public void Add( T del )  {
imp.Add( del ) ;
}
public void CallDelegates ( int k )  {
foreach ( T del in imp )  {
// del( k );
}
}
private List<t> imp = new List<t>();
}
public class EntryPoint {
static void Main()  {
DelegateContainer<myDelegate> delegates =
new DelegateContainer<myDelegate>();
delegates.Add( EntryPoint.Printlnt );
}
static void Printlnt ( int i )  {
Console.WriteLine ( i );
}

В таком виде, как он написан, этот код скомпилируется. Однако обратите внимание на закомментированную строку внутри метода Call Delegates. Если удалить комментарий с нее и попытаться скомпилировать с помощью компилятора Microsoft, то появится следующее сообщение об ошибке:

error CS0118: ‘del’ is a ‘variable’ but is used like a ‘method’
ошибка CS0118: del является переменной, но используется подобно методу

Проблема в том, что компилятор не может знать, что тип, на который ссылается указатель места заполнения Т, является делегатом. Те из вас, кто забежал вперед в этой главе, могут недоумевать — почему нет никакой формы ограничения (об ограничениях речь пойдет дальше), чтобы подсказать компилятору, что это делегат? Но даже если бы такая форма ограничения существовала, компилятор может быть не известно, как вызвать этот делегат. Ограничение не может нести никакой информации о количестве параметров, принимаемых делегатом.

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

Только представьте себе головную боль, вызванную попытками придумать способ занести динамическое количество параметров в стек перед вызовом делегата.

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

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

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

В следующем коде показано, как исправить предыдущую ситуацию:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using System.Collections.Generic-public delegate void MyDelegate<t>( T i );
public class DelegateContainer<t> {
public void Add( MyDelegate<t> del )  {
imp.Add( del ) ;
}
public void CallDelegates ( T k )  {
foreach ( MyDelegate<t> del in imp )  {
del ( k ) ;
}
}
private List<myDelegate<t> > imp = new List<myDelegate<t> >();
}
public class EntryPoint {
static void Main()  {
DelegateContainer<int> delegates = new DelegateContainer<int>();
delegates.Add( EntryPoint.Printlnt );
delegates.CallDelegates ( 42 );
}
static void Printlnt ( int i )  {
Console.WriteLine ( i );

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100