web-dev-qa-db-ja.com

CLRでの「as」キーワードの使用とキャスト

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

これら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);
    }
}

また、「一般に」推奨される方法は何ですか?

372
Frank V

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

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

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

ttはこの後もスコープ内にありますが、確実に割り当てられていないことに注意してください。 (それはisif本体内で確実に割り当てられます。)場合によっては少しうっとうしいので、すべてのスコープで可能な限り少ない数の変数を導入することに本当に関心があるなら、引き続きisを使用したいかもしれませんキャストによって。


これまでのところ(この回答を開始した時点で)答えのどれも、どれを使う価値があるのか​​を本当に説明していないと思います。

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

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

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

  • randomObjectが本当にshouldの場合は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;
    
  • randomObjectmightTargetTypeのインスタンスであり、TargetTypeが参照型である場合、次のようなコードを使用します。

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
    
  • randomObjectmightTargetTypeのインスタンスであり、TargetTypeが値型の場合、asTargetType自体と一緒に使用することはできませんが、null可能型を使用することはできます。

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

    (注:現在、これは 実際は+ castよりも遅い です。よりエレガントで一貫していると思いますが、そこに行きます。)

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

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

  • ほぼ確実に、これまでに値型の場合にisを使用しましたが、null許容型とasを一緒に使用することは考えていませんでした:)


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

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

私のラップトップでは、これらはすべて約60msで実行されます。注意すべき2つのこと:

  • それらの間に大きな違いはありません。 (実際には、as-plus-null-checkが確実にis遅い場合があります。上記のコードは、実際に型チェックを簡単にします。なぜなら封印されたクラスのためです。インターフェース、バランスのヒントはas-plus-null-checkを支持します。)
  • それらはすべて非常識高速です。これは、後でanythingを実行しない場合を除き、単純にしないがコードのボトルネックになります。

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

Is-and-cast(またはis-and-as)は、変数を処理する際に両方とも安全ではないことを維持します。変数が参照する値のタイプは、テストとキャストの間の別のスレッドにより変化する可能性があるためです。それは非常にまれな状況ですが、一貫して使用できる規則が必要です。

また、as-then-null-checkが懸念をより適切に分離することを維持します。変換を試みるステートメントが1つあり、その結果を使用するステートメントが1つあります。 is-and-castまたはis-and-asはテストを実行し、then値を変換する別の試行を行います。

別の言い方をすれば、誰でもeverと書くでしょう:

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

それは、キャスティングとキャストのようなものです-明らかに安価ですが。

499
Jon Skeet

"as"は、キャストできない場合はNULLを返します。

キャスト前は例外を発生させます。

パフォーマンスについては、例外の発生は通常、時間内によりコストがかかります。

69

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キーワードとcastclass

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

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

個人的には常にAsを使用しています。読みやすく、.NET開発チーム(またはジェフリーリヒター)が推奨しているためです。

26
Chris S

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;行は問題なく機能します。

18
Patrik Hägne

asは、代わりにnullを返す変換を実行できない場合、例外をスローしません(asは参照型でのみ動作します)。 asを使用することは、基本的に

_myCls2 = _myObj is MyClass ? (MyClass)_myObj : null;

一方、Cスタイルのキャストは、変換が不可能な場合に例外をスローします。

12
Anton Gogolev

あなたの質問に対する答えではありませんが、重要な関連点だと思います。

インターフェイスにプログラミングする場合、キャストする必要はありません。うまくいけば、これらのキャストは非常にまれです。そうでない場合は、インターフェイスの一部を再考する必要があります。

10
toad

Jon Skeetのアドバイスを無視して、re:avoid test-and-cast pattern、すなわち:

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

これはキャストとnullテストよりも費用がかかるという考えは、神話です。

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

それは動作しない微最適化です。 一部の実際のテスト を実行しましたが、テストとキャストは実際にはキャストとヌルの比較よりも高速です。また、null参照を持つ可能性がないため、より安全です。キャストが失敗した場合のifの範囲外。

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

シンプル:素朴なコンパイラでさえ、テストアンドキャストのような2つの類似した操作を1つのテストとブランチに結合します。 cast-and-null-testは、2つのテストとブランチを強制します。1つは型テスト用で、1つは失敗時のnullへの変換、もう1つはnullチェック自体用です。少なくとも1つのテストとブランチに最適化されるため、テストとキャストはキャストとヌルテストより遅くも速くもなりません。

Complex:なぜtest-and castが速いのか:cast-and-null-testは、コンパイラが活性のために追跡しなければならない外側のスコープに別の変数を導入します。また、制御フローの複雑さによっては、その変数を最適化することができない場合があります。逆に、テストとキャストでは、区切られたスコープでのみ新しい変数が導入されるため、コンパイラーはスコープの終了後に変数が無効になっていることを認識し、レジスタ割り当てを最適化できます。

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

9
naasking

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

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

IMyInterfaceをMyClassにキャストする場合、タイプMyClassのオブジェクトを取得し、IMyInterfaceを使用する意味がないことを既に想定している場合、IMyInterfaceを実装する他のクラスでコードをフィードすると、コードが破損するため...

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

4
f3lix

キャストが失敗した場合、「as」キーワードは例外をスローしません。代わりに、変数をnull(または値型のデフォルト値)に設定します。

4
TheSmurf

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

キャストは互換性のあるすべてのタイプで使用でき、オーバーロードすることができ、操作が失敗すると例外をスローします。

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

3

私の答えは、型をチェックせず、キャスト後に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

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

1
CoperNick

.NET Framework 4.Xを対象とするOffice PIAを使用する場合は、asキーワードを使用する必要があります。使用しないとコンパイルされません。

Microsoft.Office.Interop.Outlook.Application o = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.MailItem m = o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem) as Microsoft.Office.Interop.Outlook.MailItem;

キャスティングは、.NET 2.0を対象とする場合は問題ありません:

Microsoft.Office.Interop.Outlook.MailItem m = (Microsoft.Office.Interop.Outlook.MailItem)o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);

.NET 4.Xを対象とする場合、エラーは次のとおりです。

エラーCS0656:コンパイラに必要なメンバー 'Microsoft.CSharp.RuntimeBinder.Binder.Convert'がありません

エラーCS0656:コンパイラに必要なメンバー 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'がありません

1
Olivier MATROT

ここですでに公開されていたすべてのことに加えて、明示的なキャストと

var x = (T) ...

as演算子を使用します。

以下に例を示します。

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GenericCaster<string>(12345));
        Console.WriteLine(GenericCaster<object>(new { a = 100, b = "string" }) ?? "null");
        Console.WriteLine(GenericCaster<double>(20.4));

        //prints:
        //12345
        //null
        //20.4

        Console.WriteLine(GenericCaster2<string>(12345));
        Console.WriteLine(GenericCaster2<object>(new { a = 100, b = "string" }) ?? "null");

        //will not compile -> 20.4 does not comply due to the type constraint "T : class"
        //Console.WriteLine(GenericCaster2<double>(20.4));
    }

    static T GenericCaster<T>(object value, T defaultValue = default(T))
    {
        T castedValue;
        try
        {
            castedValue = (T) Convert.ChangeType(value, typeof(T));
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }

    static T GenericCaster2<T>(object value, T defaultValue = default(T)) where T : class
    {
        T castedValue;
        try
        {
            castedValue = Convert.ChangeType(value, typeof(T)) as T;
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }
}

最下行: GenericCaster2は構造体タイプでは機能しません。 GenericCasterはそうします。

1
Veverke

asキーワードは、互換性のある参照型間の明示的なキャストと同じように機能しますが、大きな違いは、変換が失敗しても例外が発生しないことです。むしろ、ターゲット変数にヌル値を生成します。例外はパフォーマンスの点で非常に高価なので、キャストのはるかに優れた方法と見なされます。

0
Cerebrus

次のリンクをご覧ください。

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

0
juFo

それは、「as」を使用した後にnullをチェックしますか、それともアプリが例外をスローすることを望みますか?

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

0
Darryl Braaten

OPの問題は、特定のキャスト状況に限定されます。タイトルはより多くの状況をカバーしています。
これは、現在考えられるすべての関連するキャスト状況の概要です。

private class CBase
{
}

private class CInherited : CBase
{
}

private enum EnumTest
{
  zero,
  one,
  two
}

private static void Main (string[] args)
{
  //########## classes ##########
  // object creation, implicit cast to object
  object oBase = new CBase ();
  object oInherited = new CInherited ();

  CBase oBase2 = null;
  CInherited oInherited2 = null;
  bool bCanCast = false;

  // explicit cast using "()"
  oBase2 = (CBase)oBase;    // works
  oBase2 = (CBase)oInherited;    // works
  //oInherited2 = (CInherited)oBase;   System.InvalidCastException
  oInherited2 = (CInherited)oInherited;    // works

  // explicit cast using "as"
  oBase2 = oBase as CBase;
  oBase2 = oInherited as CBase;
  oInherited2 = oBase as CInherited;  // returns null, equals C++/CLI "dynamic_cast"
  oInherited2 = oInherited as CInherited;

  // testing with Type.IsAssignableFrom(), results (of course) equal the results of the cast operations
  bCanCast = typeof (CBase).IsAssignableFrom (oBase.GetType ());    // true
  bCanCast = typeof (CBase).IsAssignableFrom (oInherited.GetType ());    // true
  bCanCast = typeof (CInherited).IsAssignableFrom (oBase.GetType ());    // false
  bCanCast = typeof (CInherited).IsAssignableFrom (oInherited.GetType ());    // true

  //########## value types ##########
  int iValue = 2;
  double dValue = 1.1;
  EnumTest enValue = EnumTest.two;

  // implicit cast, explicit cast using "()"
  int iValue2 = iValue;   // no cast
  double dValue2 = iValue;  // implicit conversion
  EnumTest enValue2 = (EnumTest)iValue;  // conversion by explicit cast. underlying type of EnumTest is int, but explicit cast needed (error CS0266: Cannot implicitly convert type 'int' to 'test01.Program.EnumTest')

  iValue2 = (int)dValue;   // conversion by explicit cast. implicit cast not possible (error CS0266: Cannot implicitly convert type 'double' to 'int')
  dValue2 = dValue;
  enValue2 = (EnumTest)dValue;  // underlying type is int, so "1.1" beomces "1" and then "one"

  iValue2 = (int)enValue;
  dValue2 = (double)enValue;
  enValue2 = enValue;   // no cast

  // explicit cast using "as"
  // iValue2 = iValue as int;   error CS0077: The as operator must be used with a reference type or nullable type
}
0
Tobias Knauss

何を選択するかは、必要なものに強く依存します。明示的なキャストが好きです

IMyInterface = (IMyInterface)someobj;

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

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

0
Oleg