1. ホーム
  2. c#

[解決済み] CLRのキャストと'as'キーワードの使用について

2022-03-23 05:56:32

質問

インターフェースをプログラミングする際、キャストやオブジェクトの型変換をすることが多いのですが、どのようにすればよいのでしょうか?

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

また、"in general"とは、どのような方法が望ましいのでしょうか?

解決方法は?

この行の下にある答えは、2008年に書かれたものです。

C# 7 ではパターンマッチングが導入され、そのおかげで as 演算子が書けるようになりました。

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 はローカル変数ではなく、フィールドです。の値を他のスレッドが変更した場合、"if" はパスしてもキャストが失敗する可能性があります。 randomObject の間にある。

  • もし 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 かもしれない のインスタンスである。 TargetTypeTargetType が値型なら asTargetType そのものを使用することはできませんが、NULL可能な型を使用することは可能です。

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

    (注意:現在、これは よりも実際に遅い + cast . 私はこの方がエレガントで一貫性があると思いますが、どうでしょうか?)

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

  • ジェネリックを含む他のケースとして is は便利ですが(Tが参照型かどうか分からないのでasは使えない)、比較的不明瞭です。

  • を使ったことがあるのは、ほぼ間違いない。 is を使うことを思いつかず、今まで値型の場合の as を併用することです :)


EDIT: Value Typeのケース以外では、nullableのvalue Typeへのunboxingは実際に遅くなることを指摘しましたが、一貫しています。

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が間違いなく有効である状況もあります。 が遅くなる。もしインターフェイスをチェックするのであれば、バランスは若干as-plus-null-checkに傾くでしょう)。
  • それらはすべて めちゃめちゃ が速い。これは単に はしません。 を本当にしないのでなければ、コードのボトルネックになることはないでしょう。 なんでも を、その後の値で指定します。

だから、パフォーマンスを気にするのはやめよう。正しさと一貫性について心配しましょう。

変数を扱う場合、is-and-cast (または is-and-as) はどちらも安全でないと私は主張します。それはかなり稀な状況でしょうが、私はむしろ一貫して使用できる規約を持ちたいと思います。

また、as-then-null-checkは、より良い懸念の分離を与えることを主張します。変換を試みるステートメントと、その結果を使用するステートメントがあります。is-and-castまたはis-and-asはテストを実行し では というように、値の変換を再度試みる。

別の言い方をすれば、誰もが これまで を書きます。

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

これは、is-and-castがやっていることと同じようなものです--明らかに、もっと安い方法ですが。