404

インターフェイスをプログラミングするとき、多くのキャストやオブジェクト タイプの変換を行っていることがわかりました。

これら 2 つの変換方法に違いはありますか? もしそうなら、費用の違いはありますか、それとも私のプログラムにどのように影響しますか?

public interface IMyInterface
{
    void AMethod();
}

public class MyClass : IMyInterface
{
    public void AMethod()
    {
       //Do work
    }

    // Other helper methods....
}

public class Implementation
{
    IMyInterface _MyObj;
    MyClass _myCls1;
    MyClass _myCls2;

    public Implementation()
    {
        _MyObj = new MyClass();

        // What is the difference here:
        _myCls1 = (MyClass)_MyObj;
        _myCls2 = (_MyObj as MyClass);
    }
}

また、「一般的に」好ましい方法は何ですか?

4

18 に答える 18

538

行の下の答えは2008年に書かれました.

asC# 7 ではパターン マッチングが導入され、次のように記述できるようになり、演算子が大幅に置き換えられました。

if (randomObject is TargetType tt)
{
    // Use tt here
}

ttこの後もスコープ内にありますが、確実に割り当てられていないことに注意してください。(ボディ内確実に割り当てられますif。) これは場合によっては少し面倒なので、すべてのスコープで可能な限り最小数の変数を導入することを本当に気にしている場合は、引き続きisキャストを使用することをお勧めします。


これまでの回答のいずれも(この回答を開始した時点で!)、どれを使用する価値があるかを実際に説明しているとは思いません。

  • これをしないでください:

    // Bad code - checks type twice for no reason
    if (randomObject is TargetType)
    {
        TargetType foo = (TargetType) randomObject;
        // Do something with foo
    }
    

    これは 2 回チェックするだけでrandomObjectなく、 がローカル変数ではなくフィールドである場合、別のものをチェックしている可能性があります。randomObject別のスレッドが2 つの間の値を変更すると、「if」が渡されてもキャストが失敗する可能性があります。

  • randomObject本当にのインスタンスである必要がある場合TargetType、つまりそうでない場合は、バグがあることを意味し、キャストが適切な解決策です。これはすぐに例外をスローします。これは、誤った仮定の下で作業が行われなくなり、例外がバグの種類を正しく示していることを意味します。

    // This will throw an exception if randomObject is non-null and
    // refers to an object of an incompatible type. The cast is
    // the best code if that's the behaviour you want.
    TargetType convertedRandomObject = (TargetType) randomObject;
    
  • のインスタンスでありrandomObject 参照型である場合は、次のようなコードを使用します。TargetTypeTargetType

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
    
  • のインスタンスである可能randomObject があり、値型である場合、それ自体では使用できませんが、null 許容型を使用できます。TargetTypeTargetTypeasTargetType

    TargetType? convertedRandomObject = randomObject as TargetType?;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject.Value
    }
    

    (注: 現在、これは + cast よりも実際には遅いです。よりエレガントで一貫性があると思いますが、これで終わりです。)

  • 変換された値が本当に必要ではなく、それが TargetType のインスタンスであるかどうかを知る必要がある場合は、演算子isが役に立ちます。この場合、TargetType が参照型か値型かは問題ではありません。

  • が便利なジェネリックを含む他のケースがあるisかもしれません (T が参照型であるかどうかがわからないため、as を使用できないため) が、それらは比較的あいまいです。

  • isnull許容型と一緒に使用することを考えていなかったので、今までほぼ確実に値型のケースに使用してasいました:)


編集:値型の場合を除いて、上記のいずれもパフォーマンスについて言及していないことに注意してください.null可能な値型へのボックス化解除は実際には遅くなりますが、一貫しています。

naasking の回答によると、以下のコードに示すように、is-and-cast または is-and-as はどちらも最新の JIT で as-and-null-check と同じくらい高速です。

using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "x";
            values[i + 2] = new object();
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);
    }

    static void FindLengthWithIsAndCast(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string) o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

私のラップトップでは、これらすべてが約 60 ミリ秒で実行されます。2 つの注意事項:

  • それらの間に大きな違いはありません。(実際には、as-plus-null-check の方が確実遅い状況があります。上記のコードは、シールされたクラス用であるため、実際には型チェックを簡単にします。インターフェイスをチェックしている場合、バランスがわずかに傾いています。 as-plus-null-check を優先します。)
  • それらはすべてめちゃくちゃ速いです。後で値を実際に使用しない場合を除き、これがコードのボトルネックになることはありません。

ですから、パフォーマンスについて心配する必要はありません。正確さと一貫性について心配しましょう。

is-and-cast (または is-and-as) は、参照する値の型がテストとキャストの間の別のスレッドによって変わる可能性があるため、変数を扱う場合はどちらも安全ではないと私は主張します。それは非常にまれな状況ですが、一貫して使用できる規則が必要です。

私はまた、as-then-null-check の方が懸念事項をより適切に分離できると主張しています。変換を試みる 1 つのステートメントと、その結果を使用する 1 つのステートメントがあります。is-and-cast または is-and-as はテストを実行してから、値の変換もう一度試みます。

別の言い方をすれば、誰でも次のように書いたことがありますか?

int value;
if (int.TryParse(text, out value))
{
    value = int.Parse(text);
    // Use value
}

それは is-and-cast が行っていることの一種です - 明らかにかなり安価な方法ではありますが。

于 2009-01-30T16:34:15.863 に答える
28

これが別の答えです。ILの比較があります。クラスを考えてみましょう:

public class MyClass
{
    public static void Main()
    {
        // Call the 2 methods
    }

    public void DirectCast(Object obj)
    {
        if ( obj is MyClass)
        { 
            MyClass myclass = (MyClass) obj; 
            Console.WriteLine(obj);
        } 
    } 


    public void UsesAs(object obj) 
    { 
        MyClass myclass = obj as MyClass; 
        if (myclass != null) 
        { 
            Console.WriteLine(obj);
        } 
    }
}

次に、各メソッドが生成する IL を見てください。op コードが意味をなさない場合でも、大きな違いが 1 つあります。それは、DirectCast メソッドで isinst が呼び出され、その後に castclass が呼び出されることです。したがって、基本的には 1 回ではなく 2 回呼び出します。

.method public hidebysig instance void  DirectCast(object obj) cil managed
{
  // Code size       22 (0x16)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  brfalse.s  IL_0015
  IL_0008:  ldarg.1
  IL_0009:  castclass  MyClass
  IL_000e:  pop
  IL_000f:  ldarg.1
  IL_0010:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0015:  ret
} // end of method MyClass::DirectCast

.method public hidebysig instance void  UsesAs(object obj) cil managed
{
  // Code size       17 (0x11)
  .maxstack  1
  .locals init (class MyClass V_0)
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  brfalse.s  IL_0010
  IL_000a:  ldarg.1
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0010:  ret
} // end of method MyClass::UsesAs

isinst キーワードとキャストクラスの比較

このブログ投稿では、2 つの方法を適切に比較しています。彼の要約は次のとおりです。

  • 直接比較すると、isinst は castclass よりも高速です (わずかですが)。
  • 変換が成功したことを確認するためにチェックを実行する必要がある場合、 isinst は castclass よりも大幅に高速でした
  • isinst と castclass の組み合わせは、最も速い「安全な」変換よりもはるかに遅い (12% 以上遅い) ため、使用しないでください。

As は読みやすく、.NET 開発チーム (またはとにかく Jeffrey Richter) によって推奨されているため、個人的には常に As を使用します。

于 2009-01-30T17:02:14.747 に答える
18

2つのより微妙な違いの1つは、キャスト演算子が含まれている場合、「as」キーワードをキャストに使用できないことです。

public class Foo
{
    public string Value;

    public static explicit operator string(Foo f)
    {
        return f.Value;
    }

}

public class Example
{
    public void Convert()
    {
        var f = new Foo();
        f.Value = "abc";

        string cast = (string)f;
        string tryCast = f as string;
    }
}

「as」キーワードはキャスト演算子を考慮していないため、これは最後の行でコンパイルされません(以前のバージョンではコンパイルされたと思いますが)。ただし、この行string cast = (string)f;は問題なく機能します。

于 2009-01-30T16:38:21.563 に答える
9

Jon Skeetのアドバイスを無視してください。つまり、テストとキャストのパターンを避けてください。

if (randomObject is TargetType)
{
    TargetType foo = randomObject as TargetType;
    // Do something with foo
}

これはキャストとヌルテストよりもコストがかかるという考えは神話です:

TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
    // Do stuff with convertedRandomObject
}

これは、機能しないマイクロ最適化です。私はいくつかの実際のテストを実行しましたが、テストとキャストは実際にはキャストとヌルの比較よりも高速です。また、キャストする必要がある場合にスコープ外のスコープにnull参照を含める可能性がないため、より安全です。不合格。

テストとキャストが速い、または少なくとも遅くない理由が必要な場合は、単純で複雑な理由があります。

単純:単純なコンパイラーでさえ、テストとキャストのような2つの同様の操作を1つのテストとブランチに統合します。cast-and-null-testは、2つのテストとブランチを強制する場合があります。1つはタイプテスト用で、失敗時にnullに変換し、もう1つはnullチェック自体用です。少なくとも、これらは両方とも単一のテストとブランチに最適化されるため、テストとキャストはキャストとヌルテストよりも遅くも速くもなりません。

複雑:テストとキャストが高速である理由:キャストとヌルテストは、コンパイラがライブネスを追跡する必要がある外部スコープに別の変数を導入し、制御の複雑さによってはその変数を最適化できない場合があります-流れはです。逆に、テストアンドキャストは区切られたスコープにのみ新しい変数を導入するため、コンパイラはスコープの終了後に変数が無効であることを認識し、レジスタ割り当てをより適切に最適化できます。

ですから、この「キャストアンドヌルテストはテストアンドキャストよりも優れている」というアドバイスをDIEに任せてください。お願いします。テストアンドキャストは、より安全で高速です。

于 2009-11-14T13:44:16.707 に答える
4

これは質問への回答ではなく、質問のコード例へのコメントです。

通常、オブジェクトを IMyInterface から MyClass などにキャストする必要はありません。インターフェイスの優れた点は、インターフェイスを実装する入力としてオブジェクトを取得する場合、取得するオブジェクトの種類を気にする必要がないことです。

IMyInterface を MyClass にキャストすると、既に MyClass 型のオブジェクトを取得すると想定されており、IMyInterface を実装する他のクラスでコードをフィードすると、コードが壊れてしまうため、IMyInterface を使用しても意味がありません...

さて、私のアドバイス: インターフェースが適切に設計されていれば、多くの型キャストを避けることができます。

于 2009-01-30T16:36:49.320 に答える
3

演算子はas参照型でのみ使用でき、オーバーロードできずnull、操作が失敗した場合に返されます。例外をスローすることはありません。

キャストは互換性のある任意の型で使用でき、オーバーロードすることができ、操作が失敗すると例外がスローされます。

どちらを使用するかは、状況によって異なります。主に、失敗した変換で例外をスローするかどうかの問題です。

于 2009-01-30T16:31:16.913 に答える
1

私の答えは、型をチェックせず、キャスト後に null をチェックしない場合の速度についてのみです。Jon Skeet のコードに次の 2 つのテストを追加しました。

using System;
using System.Diagnostics;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];

        for (int i = 0; i < Size; i++)
        {
            values[i] = "x";
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);

        FindLengthWithCast(values);
        FindLengthWithAs(values);

        Console.ReadLine();
    }

    static void FindLengthWithIsAndCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string)o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
    static void FindLengthWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = (string)o;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

結果:

Is and Cast: 30000000 : 88
Is and As: 30000000 : 93
As and null check: 30000000 : 56
Cast: 30000000 : 66
As: 30000000 : 46

これはすべて非常に高速であるため、(私が行ったように)速度に集中しようとしないでください。

于 2013-09-25T16:28:16.817 に答える
0

これらのリンクを見てください:

詳細とパフォーマンステストが表示されます。

于 2013-02-08T09:14:21.683 に答える
0

"as" を使用した後に null をチェックするか、アプリで例外をスローするかによって異なります。

私の経験則は、変数が、キャストを使用したいときに期待している型であると常に期待している場合です。変数が私が望むものにキャストされない可能性があり、as を使用して null を処理する準備ができている場合は、as を使用します。

于 2009-01-30T16:33:37.470 に答える
0

何を選択するかは、何が必要かによって大きく異なります。明示的なキャストを好む

IMyInterface = (IMyInterface)someobj;

オブジェクトが IMyInterface タイプである必要があり、そうでない場合、それは間違いなく問題です。副作用を修正するのではなく、正確なエラーが修正されるため、できるだけ早くエラーを取得することをお勧めします。

ただし、パラメーターとして受け入れるメソッドを扱う場合はobject、コードを実行する前に正確な型を確認する必要があります。そのような場合asは回避できるので便利ですInvalidCastException

于 2009-01-30T16:59:04.237 に答える