「is」とnullチェック付きのキャストキャスト


106

私はこれを回すことをリシャーパーが提案していることに気づきました:

if (myObj.myProp is MyType)
{
   ...
}

これに:

var myObjRef = myObj.myProp as MyType;
if (myObjRef != null)
{
   ...
}

なぜこの変更を提案するのですか?私は最適化の変更とコード削減の変更を提案するResharperに慣れていますが、これは私の単一のステートメントを取り、それを2行に変えたいと思っているようです。

MSDNによると:

ある 表現次の両方の条件が満たされている場合は、trueと評価されます。

がnullではありません。式はtypeにキャストできます。つまり、フォームのキャスト式は(type)(expression)例外をスローせずに完了します。

私はそれを読み違えているのisでしょうか、それともまったく同じチェックを行わないのですか?nullチェック用に別のローカル変数を明示的に作成する必要がない1行だけでですか?


1
コードの後半でmyObjRefを使用していますか?もしそうならMyProp、この変更後はゲッターは必要ありません。
デフォルトの

回答:


145

キャストが1つしかないからです。これを比較してください:

if (myObj.myProp is MyType) // cast #1
{
    var myObjRef = (MyType)myObj.myProp; // needs to be cast a second time
                                         // before using it as a MyType
    ...
}

これに:

var myObjRef = myObj.myProp as MyType; // only one cast
if (myObjRef != null)
{
    // myObjRef is already MyType and doesn't need to be cast again
    ...
}

C#7.0は、パターンマッチングを使用したよりコンパクトな構文をサポートしています。

if (myObj.myProp is MyType myObjRef)
{
    ...
}

3
丁度。'is'を使用すると、基本的にはreturn((myProp as MyType)== null)などの処理が行われます
Bambu

2
変更に関する限り、これはかなりの分です。nullチェックは、2番目の型チェックにかなり匹敵します。 as数ナノ秒速いかもしれませんが、私はこれを時期尚早なマイクロ最適化と考えています。
12

4
また、元のバージョンはスレッドセーフではないことに注意してください。myObjまたはの値がとキャストのmyProp間で(別のスレッドによって)変更さisれ、望ましくない動作が発生する可能性があります。
Jeff E

1
また、as+ を使用!= nullすると、ifが定義されている場合(nullの場合でも)のオーバーライドされた!=演算子も実行されることを追加できます。しながら、ほとんどの場合、これは非問題です(あなたがそれを正しく実装する場合は特に)、いくつかの極端な場合(不正なコード、パフォーマンス)で、それは希望されない場合があります。(ただし、かなり極端にする必要があります)MyTypemyObjRef
Chris Sinclair

1
@Chris:そうです、コードの正しい翻訳ではを使用しますobject.ReferenceEquals(null, myObjRef)
Ben Voigt

10

最良のオプションは、そのようなパターンマッチングを使用することです。

if (value is MyType casted){
    //Code with casted as MyType
    //value is still the same
}
//Note: casted can be used outside (after) the 'if' scope, too

これは質問の2番目のフラグメントよりも正確にどの程度優れていますか?
Victor

質問の2番目のフラグメントは、isの基本的な使用法(変数宣言なし)を参照しています。その場合、型を2回チェックします(1つはisステートメント内、もう1つはキャスト前)
Francesco Cattoni

6

実際に何が起こっているのかについての情報はまだありません。この例を見てください:

object o = "test";
if (o is string)
{
    var x = (string) o;
}

これは、次のILに変換されます。

IL_0000:  nop         
IL_0001:  ldstr       "test"
IL_0006:  stloc.0     // o
IL_0007:  ldloc.0     // o
IL_0008:  isinst      System.String
IL_000D:  ldnull      
IL_000E:  cgt.un      
IL_0010:  stloc.1     
IL_0011:  ldloc.1     
IL_0012:  brfalse.s   IL_001D
IL_0014:  nop         
IL_0015:  ldloc.0     // o
IL_0016:  castclass   System.String
IL_001B:  stloc.2     // x
IL_001C:  nop         
IL_001D:  ret   

ここで重要なのはisinstおよびcastclass呼び出しです。どちらも比較的高価です。これを代替と比較すると、isinstチェックのみを行うことがわかります。

object o = "test";
var oAsString = o as string;
if (oAsString != null)
{

}

IL_0000:  nop         
IL_0001:  ldstr       "test"
IL_0006:  stloc.0     // o
IL_0007:  ldloc.0     // o
IL_0008:  isinst      System.String
IL_000D:  stloc.1     // oAsString
IL_000E:  ldloc.1     // oAsString
IL_000F:  ldnull      
IL_0010:  cgt.un      
IL_0012:  stloc.2     
IL_0013:  ldloc.2     
IL_0014:  brfalse.s   IL_0018
IL_0016:  nop         
IL_0017:  nop         
IL_0018:  ret  

言及する価値があるのは、値型では次のunbox.any代わりに使用することですcastclass

object o = 5;
if (o is int)
{
    var x = (int)o;
}

IL_0000:  nop         
IL_0001:  ldc.i4.5    
IL_0002:  box         System.Int32
IL_0007:  stloc.0     // o
IL_0008:  ldloc.0     // o
IL_0009:  isinst      System.Int32
IL_000E:  ldnull      
IL_000F:  cgt.un      
IL_0011:  stloc.1     
IL_0012:  ldloc.1     
IL_0013:  brfalse.s   IL_001E
IL_0015:  nop         
IL_0016:  ldloc.0     // o
IL_0017:  unbox.any   System.Int32
IL_001C:  stloc.2     // x
IL_001D:  nop         
IL_001E:  ret   

ただし、ここでわかるように、これは必ずしもより速い結果に変換されるわけではないことに注意してください。ただし、その質問が行われてから改善があったようです。キャストは以前と同じくらい高速に実行されているようですがaslinq現在は約3倍高速です。


4

リシャーパー警告:

"Type check and direct cast can be replaced with try cast and check for null"

どちらも機能しますが、コードがどのように適しているかによります。私の場合、その警告は無視します。

//1st way is n+1 times of casting
if (x is A) ((A)x).Run();
else if (x is B) ((B)x).Run();
else if (x is C) ((C)x).Run();
else if (x is D) ((D)x).Run();
//...
else if (x is N) ((N)x).Run();    
//...
else if (x is Z) ((Z)x).Run();

//2nd way is z times of casting
var a = x as Type A;
var b = x as Type B;
var c = x as Type C;
//..
var n = x as Type N;
//..
var z = x as Type Z;
if (a != null) a.Run();
elseif (b != null) b.Run();
elseif (c != null) c.Run();
...
elseif (n != null) n.Run();
...
elseif (x != null) x.Run();

私のコードでは、2番目の方法はより長く、パフォーマンスが低下しています。


1
実際の例では、単に設計上の問題があります。タイプを制御する場合は、のようなインターフェースを使用してくださいIRunable。コントロールを取得していない場合は、おそらく使用できますdynamicか?
M.ミンペン

3

私にとって、これは、そのタイプになるかどうかの確率に依存しているようです。ほとんどの場合、オブジェクトがそのタイプである場合は、前もってキャストする方が効率的です。そのタイプがたまにしかない場合は、最初にisでチェックする方が最適かもしれません。

ローカル変数を作成するコストは、型チェックのコストと比較して非常にわずかです。

読みやすさと範囲は、私にとって通常より重要な要素です。私はReSharperに同意せず、その理由だけで「is」演算子を使用します。これが真のボトルネックである場合は、後で最適化します。

(私はあなたがmyObj.myProp is MyTypeこの関数で一度だけ使用していると仮定しています)


0

同様に2番目の変更を提案する必要があります。

(MyType)myObj.myProp

myObjRef

これにより、元のコードと比較して、プロパティへのアクセスとキャストが節約されます。ただし、に変更isasた後でのみ可能です。


@デフォルト:いいえ、そうではありません。これは、コードに含まれていないという意味ではありません。
Ben Voigt

1
すみません。誤解。ただし、(MyType)キャストが失敗すると例外がスローされます。asのみを返しますnull
デフォルトの

@デフォルト:型はすでにチェックされているため、キャストは失敗しませんis(そのコードは問題です)。
Ben Voigt

1
ただし、re#はそのコードを置き換えたいと考えています。つまり、提案された変更を行った後は、そのコードは存在しません。
デフォルトの

ここであなたの考えに従っていると思います(少し時間がかかりました)。あなたは最初の行コードのどこかにあり、その行は2行目にRe#の提案の後に簡略化されるということですか?
デフォルトの

0

これは、myObj.myPropの厳密に型指定されたバージョン、つまりmyObjRefを作成することです。これは、キャストで実行する必要があるのではなく、ブロックでこの値を参照するときに使用する必要があります。

たとえば、これ:

myObjRef.SomeProperty

これより良いです:

((MyType)myObj.myProp).SomeProperty
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.