Учитывая, что это - очень естественный вариант использования (если Вы не знаете что 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();
}
Править:
Я предполагаю, что не ясно дал понять свой вопрос. Я никогда не писал бы второй отрывок, поскольку это, конечно, избыточно. Я утверждаю, что CIL, сгенерированный компилятором при компиляции первого отрывка, эквивалентен второму отрывку, который избыточен. Вопросы: a) Это корректно? b) Если так, почему is
реализованный как этот?
Это вызвано тем, что я нахожу первый отрывок намного более ясным и более симпатичным, чем на самом деле правильно написанное
Bar y = x as Bar;
if (y != null) {
something();
}
ЗАКЛЮЧЕНИЕ:
Оптимизация is
/as
случай не является ответственностью компилятора, но JIT.
Кроме того, как с пустой проверкой это имеет меньше (и менее дорогой) инструкции, чем обе из альтернатив (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 для и бросок (.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 для и как (.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, и звонит во что-то () удаленный).
a) Правильно ли это
Да, хотя я бы выразился иначе. Вы говорите, что "is" - это синтаксический сахар для as-followed-by-null-check. Я бы сказал по-другому: что "as" - это синтаксический сахар для "проверки реализации типа, приведения в случае успеха, нуля в случае неудачи".
То есть, я был бы более склонен сказать, что
if (x is Bar) {
Bar y = x as Bar;
something();
}
фактически эквивалентно
if (x is Bar) {
Bar y = (x is Bar) ? (Bar)x : (Bar) null;
something();
}
Видите ли, вы хотите определить "as" в терминах "is", а не наоборот. На самом деле вопрос должен звучать так: "Почему as реализовано как is?" :-)
b) Если да, то почему реализовано именно так?
Потому что это правильная реализация спецификации.
Мне кажется, я не понимаю ход ваших мыслей. Есть ли что-то неправильное в этой реализации? Как бы вы предпочли, чтобы она была реализована? В вашем распоряжении инструкции "isinst" и "castclass"; опишите кодоген для вашей программы, который вы хотели бы видеть.
Во-первых, я не согласен с вашим предположением, что это более типичный вариант использования. Это может быть ваш любимый подход, но идиоматический подход - стиль «as + null check»:
Bar y = x as Bar;
if (y != null) {
something();
}
Как вы обнаружили, подход «is» требует дополнительных «as» или приведений, поэтому «as» По моему опыту, с нулевой проверкой это стандартный способ сделать это.
Я не вижу ничего оскорбительного в этом подходе «как», лично я не считаю его более неприятным для глаз, чем любой другой код.
Что касается вашего фактического вопроса, почему ключевое слово is
реализовано с помощью ключевого слова as
, я понятия не имею, но мне нравится игра слов в вашем вопросе :) Я подозреваю, что ни один из них на самом деле не реализован в терминах другого, но инструмент (думаю, Reflector), который вы использовали для генерации C # из IL, интерпретировал IL в терминах как
.
Ну, доступная инструкция IL (isinst) возвращает либо объект соответствующего типа, либо null, если такое преобразование невозможно. И она не выбрасывает исключение, если преобразование невозможно.
Учитывая это, и "is", и "as" тривиальны для реализации. Я бы не стал утверждать, что в данном случае "is" реализовано как "as", просто лежащая в основе IL инструкция позволяет реализовать оба варианта. Теперь, почему компилятор не может оптимизировать "is" с последующим "as" в один вызов isinst, это другой вопрос. Возможно, в данном случае это связано с областью видимости переменной (хотя к тому времени, когда это IL, область видимости на самом деле не существует)
Edit
Если подумать, вы не можете оптимизировать "is" с последующим "as" в один вызов isinst, не зная, что обсуждаемая переменная не подлежит обновлению из других потоков.
Предположим, что x - строка:
//Thread1
if(x is string)
//Thread2
x = new ComplexObject();
//Thread1
y = x as string
Здесь y должно быть null.
Вы не сделаете второй y = x как Bar;
, потому что у вас уже есть y, который является Bar.
Согласно сообщению в блоге How Many Passes? Эрика Липперта, это проход компилятора. Цитата:
Затем мы запускаем этап оптимизации, который перезаписывает тривиальные операторы is и as .
Возможно, именно поэтому вы видите один и тот же CIL, сгенерированный для обоих сниппетов.
Попробуйте это
SELECT DISTINCT a, b, c
FROM t1,
(SELECT DISTINCT a,b,c FROM t2) as tt
WHERE t1.a NOT IN tt.a
AND t1.b NOT IN tt.b
AND t1.c NOT IN tt.c
Примечание: Это не было протестировано, это даже не было доказано правильно.
-121--3926328-Вы были очень близки. Вы случайно использовали метод System.Object.Equals в выражении зависимости, а не в методе Is. Я также рекомендую при настройке общих типов, таких как последовательность или value types (int, DateTime), указать имя аргумента конструктора, чтобы избежать неоднозначности.
Вот мой тест с тем, что вы ищете:
[TestFixture]
public class configuring_concrete_types
{
[Test]
public void should_set_the_configured_ctor_value_type()
{
const int level = 23;
var container = new Container(x =>
{
x.For<Person>().Use<Doctor>();
x.ForConcreteType<Hospital>().Configure.Ctor<int>("level").Is(level);
});
var hospital = container.GetInstance<Hospital>();
hospital.Level.ShouldEqual(level);
}
}
-121--3926339- Вы можете написать код сейчас как
DoIfOfType<Bar>(possibleBar, b => b.something())
Что я бы сказал, было немного понятнее, но не так быстро без настоящей магии от компилятора.
Объем 'y' уменьшается, если вы помещаете объявление внутри цикла.
Кто бы это ни написал, вероятно, предпочитает преобразовывать «x как T» больше, чем «(T) x», и хотел ограничить объем «y».
Вы забыли о типах значений. Например:
static void Main(string[] args)
{
ValueType vt;
FooClass f = vt as FooClass;
}
private class FooClass
{
public int Bar { get; set; }
}
Не будет компилироваться, поскольку типы значений не могут быть преобразованы таким образом.
Я сильно подозреваю, что is быстрее, чем as и не требует выделения. Итак, если x редко бывает Bar, то первый сниппет хорош. Если x чаще всего Bar, то рекомендуется as, так как в этом случае не требуется второй бросок. Это зависит от использования и обстоятельств кода.
В вашем примере использование как
в любом случае является избыточным. Поскольку вы уже знаете, что x - это Bar
, вам следует использовать приведение:
if (x is Bar)
{
Bay y = (Bar)x;
}
В качестве альтернативы преобразовать, используя как
, и просто проверить на ноль:
Bar y = x as Bar;
if (y != null)
{
}