Почему "выполняется" как "как"?
Учитывая, что это очень естественный случай использования (если вы не знаете, что на самом деле делает as
),
if (x is Bar) {
Bar y = x as Bar;
something();
}
является фактически эквивалентным (то есть генерируемый компилятором CIL из приведенного выше кода будет эквивалентен):
Bar y = x as Bar;
if (y != null) {
y = x as Bar; //The conversion is done twice!
something();
}
EDIT:
Думаю, я не задал вопрос. Я бы никогда не написал второй фрагмент, поскольку он, конечно, лишний. Я утверждаю, что CIL, сгенерированный компилятором при компиляции первого фрагмента, эквивалентен второму фрагменту, который является избыточным. Вопросы: а) Это правильно? b) Если да, то почему is
реализовано так?
Это потому, что я нахожу первый фрагмент намного понятнее и красивее, чем на самом деле хорошо написанный
Bar y = x as Bar;
if (y != null) {
something();
}
ВЫВОД:
Оптимизация случая is
/as
не является ответственностью компилятора, а JIT's.
Кроме того, как и при нулевой проверке, он имеет меньше (и менее дорогостоящих) инструкций, чем обе альтернативы (is
и as
и is
и cast
).
Приложение:
CIL как для nullcheck (.NET 3.5):
L_0001: ldarg.1
L_0002: isinst string
L_0007: stloc.0
L_0008: ldloc.0
L_0009: ldnull
L_000a: ceq
L_000c: stloc.1
L_000d: ldloc.1
L_000e: brtrue.s L_0019
L_0011: ldarg.0
L_0019: ret
CIL for is and cast (.NET 3.5):
L_0001: ldarg.1
L_0002: isinst string
L_0007: ldnull
L_0008: cgt.un
L_000a: ldc.i4.0
L_000b: ceq
L_000d: stloc.1
L_000e: ldloc.1
L_000f: brtrue.s L_0021
L_0012: ldarg.1
L_0013: castclass string
L_0018: stloc.0
L_0019: ldarg.0
L_0021: ret
CIL for is и as (.NET 3.5):
L_0001: ldarg.1
L_0002: isinst string
L_0007: ldnull
L_0008: cgt.un
L_000a: ldc.i4.0
L_000b: ceq
L_000d: stloc.1
L_000e: ldloc.1
L_000f: brtrue.s L_0021
L_0012: ldarg.1
L_0013: isinst string
L_0018: stloc.0
L_0019: ldarg.0
L_0021: ret
Они были отредактированы для краткости (объявления методов, nops и вызовы на что-то() удалены).
Ответы
Ответ 1
a) Правильно ли это
Да, хотя я бы сказал об этом по-другому. Вы говорите, что "есть" является синтаксическим сахаром для проверки "как-за-путём". Я бы сказал это по-другому: "как" является синтаксическим сахаром для "проверки реализации типа, литой, если успех, null, если сбой".
То есть я был бы более склонным сказать
if (x is Bar) {
Bar y = x as Bar;
something();
}
эффективно эквивалентно
if (x is Bar) {
Bar y = (x is Bar) ? (Bar)x : (Bar) null;
something();
}
Смотрите, вы хотите определить "как" в терминах "есть", а не наоборот. Вопрос действительно должен быть "почему так же реализовано, как есть?": -)
b) Если да, то почему такое реализовано?
Потому что правильная реализация спецификации.
Думаю, я не следую твоей мысли. Что-то не так с этой реализацией? Как бы вы предпочли, чтобы это было реализовано? У вас есть инструкции "isinst" и "castclass" в вашем распоряжении; описать кодеген для своей программы, которую вы хотели бы видеть.
Ответ 2
Ну, команда IL, которая доступна (isinst), вернет либо объект соответствующего типа, либо null, если такое преобразование невозможно. И это не вызывает исключения, если преобразование невозможно.
Учитывая, что "как" , так и "как" тривиально реализовать. Я бы не утверждал, что "есть" в этом случае реализуется как "как" , так что базовая инструкция IL позволяет обойтись. Теперь, почему компилятор не может оптимизировать "есть" , а затем "как" в один единственный вызов, это другое дело. Вероятно, в этом случае это связано с переменной областью (хотя к тому времени это IL, область действительно не существует)
Изменить
Во-вторых, вы не можете оптимизировать "есть" , а затем "как" в один вызов isinst, не зная, что обсуждаемая переменная не подлежит обновлению из других потоков.
Предполагая, что x является строкой:
//Thread1
if(x is string)
//Thread2
x = new ComplexObject();
//Thread1
y = x as string
Здесь y должно быть нулевым.
Ответ 3
В вашем примере использование as
в любом случае является избыточным. Поскольку вы уже знаете, что x is Bar
, вы должны использовать листинг:
if (x is Bar)
{
Bay y = (Bar)x;
}
В качестве альтернативы, конвертируйте с помощью as
и просто проверьте значение null:
Bar y = x as Bar;
if (y != null)
{
}
Ответ 4
Во-первых, я не согласен с вашей предпосылкой, что это более типичный вариант использования. Это может быть ваш любимый подход, но идиоматический подход - стиль "как + null check":
Bar y = x as Bar;
if (y != null) {
something();
}
Как вы нашли, подход "есть" требует дополнительных "как" или приведения, поэтому "как" с нулевой проверкой является стандартным способом сделать это в моем опыте.
Я не вижу ничего оскорбительного в этом "как" , лично я не думаю, что это более неприятно на глазу, чем любой другой код.
Что касается вашего фактического вопроса, почему ключевое слово is
реализовано с точки зрения ключевого слова as
, я понятия не имею, но мне нравится игра слов в вашем вопросе:) Я подозреваю, что ни один из них не реализован в отличие от другого, но инструмент (рефлектор, я думаю), который вы использовали для генерации С# из IL, интерпретировал IL в терминах as
.
Ответ 5
Вы не будете делать второй y = x as Bar;
, потому что у вас уже есть y, который является Bar.
Ответ 6
Согласно сообщению в блоге Сколько проходов? от Eric Lippert, это пропуск компилятора. Цитировать:
Затем мы запустим прогон оптимизации, который переписывает тривиальное "есть" и "как" операторы.
Поэтому, возможно, именно поэтому вы видите тот же CIL, сгенерированный для обоих фрагментов.
Ответ 7
Теперь вы можете написать код как
DoIfOfType<Bar>(possibleBar, b => b.something())
Что я бы сказал, было немного яснее, но не так быстро, без реальной магии от компилятора.
Ответ 8
Область "y" уменьшается, если вы помещаете объявление внутри цикла.
Тот, кто написал это, вероятно, предпочитает кастинг 'x as T' больше, чем '(T) x', и хотел бы ограничить область 'y'.
Ответ 9
Вы забыли о типах значений. Например:
static void Main(string[] args)
{
ValueType vt;
FooClass f = vt as FooClass;
}
private class FooClass
{
public int Bar { get; set; }
}
Не будет компилироваться, поскольку типы значений не могут быть преобразованы так.
Ответ 10
Я подозреваю, что быстрее как и не требует выделения. Так что, если x редко бывает Bar, тогда первый фрагмент хорош. Если x в основном является баром, рекомендуется использовать как, так как не требуется второй бросок. Это зависит от использования и условий кода.