Подтвердить что ты не робот

Почему "выполняется" как "как"?

Учитывая, что это очень естественный случай использования (если вы не знаете, что на самом деле делает 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 и вызовы на что-то() удалены).

4b9b3361

Ответ 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 в основном является баром, рекомендуется использовать как, так как не требуется второй бросок. Это зависит от использования и условий кода.