Преобразование обобщенного типа

Как уже упоминалось ранее, неявных преобразований разных конструируемых типов, сформированных из одного обобщенного типа, не существует. Те же правила, которые действуют при определении того, является ли объект типа X преобразуемым в объект типа Y, в равной мере касаются определения возможности преобразования объекта типа List в объект типа

1
List<object>

Когда такое преобразование желательно, должна быть создана пользовательская операция преобразования, как в случае преобразования объектов типа X в объекты типа Y, когда они разделяют отношение наследования. В противном случае необходимо создать метод преобразования, чтобы превратить один тип в другой.

Например, следующий код неверен:

1
2
3
4
// НЕВЕРНЫЙ КОД! ! !
public void SomeMethod ( List<int> theList )  {
List<object> theSameList = theList; // Так поступать нельзя!!!
}

С появлением в С# 4.0 синтаксиса для объявления обобщенных интерфейсов и делегатов как вариантных это уже не совсем так. Но в рамках этого поста все верно до тех пор, пока все обобщенные типы трактуются как инвариантные, что получается по умолчанию, если обобщенное объявление не декорировано синтаксисом вариантности.

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

1
List<int>BList<object>

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

Таким образом, чтобы преобразовать

1
List<int>BList<object>

, придется предоставить делегат, который знает, как выполнить преобразование в экземпляр object. Использование данного делегата — это единственный способ для метода

1
ConvertAll<toutput>

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

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

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

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

Выражение значения по умолчанию

Иногда при работе с определениями обобщенных типов и обобщенных методов нужно инициализировать объект или экземпляр значения параметризованного типа его значением по умолчанию. Вспомните, что значение по умолчанию для ссылочной переменной — это то же самое, что установка ее в null, в то время как значение по умолчанию для типа значений эквивалентно установке всех его бит в 0.

Необходимо построить выражение для обобщений, учитывающее это семантическое отличие, и для этой задачи можно использовать выражение значения по умолчанию (default), показанное в следующем примере кода:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;
public class MyContainer<t> {
public MyContainer ()  {
// Начальное наполнение, imp = new T [ 4 ] ;
for ( int i = 0;
i < imp. Length;
++i )  {
inp[i] = default (T) ;
}
public bool IsDefault( int i )  {
if( i < 0 || i >= imp.Length )  {
throw new ArgumentOutOfRangeException ();
}
if ( imp [i] == null )  {
return true;
} else {
return false;
}
private T[] imp;
}
public class EntryPoint {
static void Main()  {
MyContainer<int> intColl =
new MyContainer<int> ();
MyContainer<object> objColl =
new MyContainer<object> ();
Console.WriteLine ( intColl.IsDefault (0) );
Console.WriteLine ( objColl.IsDefault (0) );
}

Обратите внимание на синтаксис внутри конструктора MyContainer, где каждый элемент массива явно инициализируется его значением по умолчанию. Во время выполнения тип Т может быть типом значений или ссылочным типом, поэтому нельзя просто присвоить значение null и рассчитывать, что это будет работать с типами значений.

Фактически, если попытаться присвоить imp [i] значение null, то компилятор выдаст следующее сообщение об ошибке:

error CS0403: Cannot convert null to type parameter fTf because it could be a non-nullable value type. Consider using 1 default(T) ‘ instead.

ошибка CS0403: Невозможно преобразовать null к параметру типа T, поскольку он должен быть типом значения. Взамен используйте default (Т) .

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

Запуск ранее приведенного кода дает следующий вывод:
False True

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

1
2
3
4
5
6
7
8
9
public bool IsDefault ( int i )  {
if ( i < 0 || i >= imp.Length )  {
throw new ArgumentOutOfRangeException();
}
if( Object.Equals(imp[i], default(T)) )  {
return true;
} else {
return false;
}

Вы можете следить за любыми ответами на эту запись через 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