2016-04-13 2 views
7

Следующий фрагмент кода извлекается из примера 10-11 (р 343.) От программирования C# 5.0:Зачем компилировать общий код без ограничения T?

public static T[] Select<T>(this CultureInfo[] cultures, 
          Func<CultureInfo, T> map) 
{ 
    var result = new T[cultures.Length]; 
    for (int i = 0; i < cultures.Length; ++i) 
    { 
     result[i] = map(cultures[i]); 
    } 
    return result; 
} 

Я не могу понять, как она могла бы компилируются, не подвергая какой-либо информации о T путем применяя к нему ограничения. В частности, как мог компилятор узнать, сколько байтов выделяется для массива, учитывая, что T может не быть ссылочным типом, но тип значения (то есть, struct)? Кроме того, семантика операции присваивания result[i] = map(cultures[i]), по-видимому, зависит от того, является ли T ссылочным типом или типом значения.

+0

«сколько байтов выделять для массива» Должен ли компилятор знать это раньше времени? – BoltClock

+0

Не уверен, правильно ли я понял вопрос, но я думаю, что компилятор JIT знает все, что вы говорите. –

ответ

3

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

Во-первых, во время компиляции, общий метод создания, который является просто версией IL, что вы написали там в источнике , Он typeafe (map возвращает T, а ячейки массива имеют тип T, так что проблем там нет), и все в порядке.

Теперь, во время выполнения, (по крайней мере, концептуально) при первом использовании Select<string>, например, компилятор JIT в этой точке создает новый метод.Назовем этот метод Select__string (его обычно называют что-то вроде Select'string Я думаю, но я не хочу, чтобы вы думали, что это важно для целей этого объяснения). В этом новом методе, все экземпляры T заменяются string, и поэтому, конечно, в том, что скомпилированный метод все легко разработаны - задания, массив распределения размера и т.д.

Следующая вы Select<int> где-то в другом месте. Компилятор JIT теперь создает совершенно новый метод, который мы назовем Select__int. И снова все экземпляры T заменяются на int, и поэтому снова можно легко обрабатывать размер массива и семантику присваивания.

То же самое относится к родовым типам. Когда они на самом деле используются, List<string> и List<int> - это два совершенно разных типа. Вот почему генераторы .net настолько просты в написании и использовании.

Если это не ясно, можете ли вы привести пример того, что конкретно, в вашем коде выше, вы думаете, что все еще нужно будет знать или ограничить во время компиляции?

+0

Спасибо за подробное объяснение. Кажется, сейчас у меня есть ключ. Только когда вы делаете что-то, что не относится ко всем типам, например, вызывая специализированный метод, вам когда-нибудь понадобится применить какое-либо ограничение. – Lingxi

+0

Да, точно. –

9

Нет никакой зависимости между CultureInfo и T вообще. Все T в равной степени действительны, поскольку вы обрабатываете «преобразование», используя свой собственный Func<CultureInfo, T>.

Я думаю, вы смущены тем, как дженерики работают на C#. Они не являются функцией времени компиляции (например, на Java), они полностью выживают во время выполнения. Только тогда, когда вам действительно нужен тип reified generic, этот тип скомпилирован, и к этому моменту он уже знает, что такое T.

Это, конечно, одна из причин, почему у C# нет Java List<?>. Нет «общего родового типа», вы только «задерживаете» переопределение типов - если у вас есть List<string> и List<int>, эти два являются полностью отдельными типами, и только отражение говорит вам, что они происходят из одного и того же родового типа.

+0

Но все же, если вы вызываете какой-либо метод 'T', необходимо применить ограничение. Поэтому не все вещи можно отложить до времени исполнения. Существует ли какое-либо простое и общее правило, которое должно быть определено во время компиляции, а что нет? Извините, если вопрос звучит глупо. Я действительно новичок в C#: P Или, может быть, это заслуживает самого нового вопроса. – Lingxi

+2

@Lingxi: Да, * только * при запуске вызова членов T, которые зависят от знания того, что T точно, или когда вы начинаете назначать значения, зависящие от того, является ли T значением или ссылочным типом. Если вы не делаете ничего из своего общего метода, то не имеет значения, что такое T. – BoltClock

+0

@BoltClock Спасибо за подтверждение моей догадки, и я очень рад, что правило такое простое :) – Lingxi

4

В частности, как бы компилятор знает, сколько байт выделить для массива,

Это не делает. Компилятору это не нужно знать.

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

Кроме того, семантика операции присваивания result[i] = map(cultures[i]) видимому, зависит от T, является ли ссылочный тип или тип значения.

Это не зависит от этого. Независимо от типа, точная копия делается из того, что возвращается map. Если это тип значения, это означает, что значение копируется. Если это ссылочный тип, это означает, что ссылка копируется.

+0

Итак, единственное, что должно быть определено во время компиляции (и, следовательно, ограничение должно быть применено), - это когда вы вызываете метод 'T'? – Lingxi

Смежные вопросы