28

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();
}

編集:

私は自分の質問を明確にしていなかったと思います。もちろん冗長なので、2番目のスニペットを書くことはありません。最初のスニペットをコンパイルするときにコンパイラによって生成された CIL は、冗長な 2 番目のスニペットと同等であると主張しています。質問: a) これは正しいですか? b) もしそうなら、なぜそのようにis実装されているのですか?

これは、最初のスニペットが実際によく書かれたものよりもはるかに明確できれいだと思うためです。

Bar y = x as Bar;
if (y != null) {
   something();
}

結論:

is/ケースの最適化はasコンパイラの責任ではなく、JIT の責任です。

isまた、null チェックと同様に、両方の選択肢 (およびasおよび) よりも少ない (isそして安価な) 命令しかありませんcast

補遺:

CIL for as with 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

is とキャストの 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 for is and 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、something() の呼び出しは削除されています)。

4

10 に答える 10

12

a)これは正しいですか

はい、私はそれを別の言い方で述べたでしょうが。あなたは、「is」がas-followed-by-null-checkのシンタックスシュガーであると言っています。別の言い方をすれば、「as」は「型の実装をチェックし、成功した場合はキャストし、失敗した場合は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(); 
} 

ほら、あなたは「is」の観点から「as」を定義したいのですが、その逆ではありません。問題は、実際には「なぜそのまま実装されているのか」ということです。:-)

b)もしそうなら、なぜそのように実装されているのですか?

それは仕様の正しい実装だからです。

私はここであなたの考えに従わないと思います。その実装に何か問題がありますか?どのように実装することを望みますか?「isinst」と「castclass」の指示を自由に使用できます。見たいプログラムのcodegenを記述します。

于 2010-02-12T16:22:38.800 に答える
9

利用可能な IL​​ 命令 (isinst) は、適切な型のオブジェクトを返すか、そのような変換が不可能な場合は null を返します。また、変換が不可能な場合でも例外はスローされません。

それを考えると、「is」と「as」の両方を実装するのは簡単です。この場合、「is」が「as」として実装されているとは主張しませんが、基礎となる IL 命令が両方の発生を許可しているだけです。ここで、コンパイラが「is」の後に「as」を 1 つの isinst 呼び出しに最適化できない理由は、別の問題です。おそらく、この場合、それは変数のスコープに関連しています(これがILになるまでに、スコープは実際には存在しませんが)

編集

よく考えてみると、議論中の変数が他のスレッドから更新されないことを知らずに、"is" の後に "as" を 1 つの isinst 呼び出しに最適化することはできません。

x が文字列であると仮定します。

//Thread1
if(x is string)

//Thread2
x = new ComplexObject();

//Thread1
    y = x as string

ここで、y は null でなければなりません。

于 2010-02-12T11:59:25.120 に答える
5

まず、これがより典型的なユースケースであるというあなたの前提に同意しません。これはあなたのお気に入りのアプローチかもしれませんが、慣用的なアプローチは「as + null チェック」スタイルです。

Bar y = x as Bar; 
if (y != null) { 
   something(); 
}

「is」アプローチには追加の「as」またはキャストが必要であることがわかったので、私の経験ではnullチェックを伴う「as」が標準的な方法です。

この「as」アプローチについて不快なことは何もありません。個人的には、他のコードよりも不快だとは思いません。

あなたの実際の質問に関しては、なぜisキーワードがキーワードの観点から実装されてasいるのか、私にはわかりませんが、あなたの質問の言葉遊びが好きです:) どちらも実際には他の観点から実装されていないと思いますが、ツール(リフレクターだと思います)ILからC#を生成するために使用したのは、ILをas.

于 2010-02-12T11:35:21.680 に答える
5

あなたの例では、の使用はasとにかく冗長です。あなたはすでにそれを知っているのでx is Bar、キャストを使用する必要があります:

if (x is Bar)
{
    Bay y = (Bar)x;
}

または、次を使用して変換しas、null をチェックします。

Bar y = x as Bar;
if (y != null)
{

}
于 2010-02-12T11:14:07.717 に答える
2

y = x as Bar;Bar である y を既に持っているため、2 番目は実行しません。

于 2010-02-12T11:09:52.890 に答える
1

ブログ投稿How Many Passes?によると、コンパイラパスである Eric Lippert によるものです。引用するには:

次に、単純な「is」および「as」演算子を書き換える最適化パスを実行します。

おそらくそれが、両方のスニペットに対して生成された同じ CIL を表示している理由です。

于 2010-02-12T12:54:35.860 に答える
1

次のようにコードを書くことができます

DoIfOfType<Bar>(possibleBar, b => b.something())

私が言うには、少し明確でしたが、コンパイラからの本当の魔法がなければそれほど速くはありません.

于 2010-02-12T15:03:51.857 に答える
0

宣言をループ内に配置すると、「y」のスコープは縮小されます。

これを書いた人はおそらく、'(T)x' よりも 'x as T' をキャストすることを好み、'y' のスコープを制限したかったのでしょう。

于 2010-02-12T15:06:48.777 に答える
0

値の型を忘れていました。例えば:

    static void Main(string[] args)
    {
        ValueType vt;
        FooClass f = vt as FooClass;

    }

    private class FooClass
    {
        public int Bar { get; set; }
    }

値の型はこのように変換できないため、コンパイルされません。

于 2010-02-12T15:34:50.647 に答える
-1

asよりも高速であり、割り当てを必要としないことを強く疑っています。したがって、x が Bar になることがめったにない場合は、最初のスニペットが適切です。x のほとんどが Bar である場合は、2 番目のキャストが必要ないため、as が推奨されますコードの使用状況や状況によって異なります。

于 2010-02-12T12:48:11.217 に答える