[C#] CLR에서 ‘as’키워드를 사용하여 캐스팅

인터페이스를 프로그래밍 할 때 많은 캐스팅 또는 객체 유형 변환을하고 있음을 발견했습니다.

이 두 가지 변환 방법간에 차이가 있습니까? 그렇다면 비용 차이가 있습니까? 아니면 이것이 프로그램에 어떤 영향을 줍니까?

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

또한 “일반적으로”선호되는 방법은 무엇입니까?



답변

이 줄 아래의 답변은 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
    }

    이 검사는 두 번뿐만 randomObject아니라 지역 변수가 아닌 필드 인 경우 다른 것을 검사 할 수 있습니다 . 다른 스레드 randomObject가 둘 사이의 값을 변경하면 “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 의 인스턴스 TargetTypeTargetType값 형식입니다, 우리는 사용할 수 없습니다 as와 함께 TargetType자체, 그러나 우리는 nullable 형식을 사용할 수 있습니다 :

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

    (참고 : 현재 이것은 실제로 + cast보다 느립니다 . 더 우아하고 일관성이 있다고 생각합니다.)

  • 당신이 정말로 변환 된 값이 필요하지 않습니다,하지만 당신은 그냥 여부를 알 필요가있는 경우 입니다 은 TargetType의 인스턴스, 다음 is연산자는 당신의 친구입니다. 이 경우 TargetType이 참조 유형인지 또는 값 유형인지는 중요하지 않습니다.

  • is유용한 경우 제네릭과 관련된 다른 경우가있을 수 있습니다 (T가 참조 유형인지 여부를 알 수 없기 때문에 사용할 수는 없으므로) 상대적으로 모호합니다.

  • 필자는 isnullable 유형과 as함께 사용할 생각을하지 않고 지금까지 값 유형 사례에 거의 확실하게 사용 했습니다.


편집 : 위의 값 중 nullable 값 유형으로의 unboxing이 실제로 느리지 만 일관성이 있음을 언급 한 value type case 이외의 성능에 대해서는 언급하지 않습니다.

Naasking의 답변에 따라 is-and-cast 또는 is-and-and-as는 아래 코드에서 볼 수 있듯이 최신 JIT를 사용하여 null 검사만큼 빠릅니다.

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 안에 실행됩니다. 두 가지주의 사항 :

  • 그들 사이에는 큰 차이가 없습니다. (사실, 상황이 거기로 플러스 null이 검사는 확실히있는에 있다 . 느린가 봉인 클래스이기 때문에 위의 코드는 실제로 타입 체크 쉽게, 만약 당신이 인터페이스에있는 거 검사, 균형 팁 약간 as-plus-null-check를 선호합니다.)
  • 그들은 모두 엄청나게 빠릅니다. 이것은 단순히 되지 않습니다 당신이 정말로하지 않을 않는 코드에서 병목 아무것도 나중에 값을.

따라서 성능에 대해 걱정하지 마십시오. 정확성과 일관성에 대해 걱정합시다.

변수를 다룰 때 is-and-cast (또는 is-and-as)는 모두 안전하지 않다고 주장합니다. 테스트와 캐스트 사이의 다른 스레드로 인해 참조되는 값의 유형이 변경 될 수 있기 때문입니다. 이는 매우 드문 상황이지만 일관성있게 사용할 수있는 규칙이 있습니다.

또한 null 검사는 더 나은 우려 분리를 제공합니다. 변환을 시도하는 문장과 결과를 사용하는 문장이 있습니다. is-and-cast 또는 is-and-as는 테스트를 수행 한 다음 값을 변환하려는 또 다른 시도를 수행 합니다.

다른 말로하면, 누군가는 것 이제까지 쓰기 :

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

그것은 다소 저렴한 방식이지만, 캐스트와 캐스트가하는 일입니다.


답변

캐스팅 할 수없는 경우 “as” 는 NULL을 반환합니다.

전에 캐스팅 하면 예외가 발생합니다.

성능을 위해 예외를 발생시키는 것은 일반적으로 시간이 더 걸립니다.


답변

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 코드가 아무 의미가 없더라도 DirectCast 메서드에서 isinst가 호출되고 castclass가 발생한다는 한 가지 큰 차이점을 알 수 있습니다. 기본적으로 하나 대신 두 개의 호출.

.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 키워드와 캐스트 클래스

이 블로그 게시물 은 두 가지 방법을 적절히 비교합니다. 그의 요약은 다음과 같습니다.

  • 직접 비교에서 isinst는 캐스트 클래스보다 빠릅니다 (약간만)
  • 변환이 성공적으로 이루어 졌는지 확인해야 할 때 isinst는 캐스트 클래스보다 훨씬 빠릅니다.
  • isinst와 castclass의 조합은 가장 빠른 “안전한”변환보다 훨씬 느리므로 (12 % 이상 느리게) 사용해서는 안됩니다.

개인적으로 항상 As를 사용합니다. 읽기 쉽고 .NET 개발 팀 (또는 Jeffrey Richter)이 권장하기 때문입니다.


답변

둘 사이의 더 미묘한 차이점 중 하나는 캐스트 연산자가 포함 된 경우 “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;은 잘 작동합니다.


답변

이 전환 복귀 수행 할 수없는 경우 결코 예외가 발생하지 널을 (대신 으로 참조 형식에서만 동작한다). 따라서 as를 사용 하는 것은 기본적으로

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

반면 C 스타일 캐스트는 변환이 불가능할 때 예외를 발생시킵니다.


답변

실제로 귀하의 질문에 대한 답변이 아니라 중요한 생각이 중요하다고 생각합니다.

인터페이스로 프로그래밍하는 경우 캐스트 할 필요가 없습니다. 바라건대이 캐스트는 매우 드 rare니다. 그렇지 않은 경우 일부 인터페이스를 다시 생각해야합니다.


답변

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
}

작동하지 않는 미세 최적화입니다. 나는 진짜 테스트를했다 하고, 테스트 및 캐스트 실제로 빠른 캐스트 – 및 – 널 비교보다, 당신은 캐스트해야하는 경우 외부 범위에서 널 참조를 가질 가능성이 없기 때문에 너무 더 안전 불합격.

테스트 및 캐스트가 더 빠르거나 적어도 느리지 않은 이유를 원한다면 간단하고 복잡한 이유가 있습니다.

단순함 : 순진한 컴파일러조차도 테스트 및 캐스트와 같은 두 가지 유사한 작업을 단일 테스트 및 분기로 통합합니다. cast-and-null-test는 두 가지 테스트와 브랜치를 강제 할 수 있습니다. 하나는 유형 테스트 용이고 하나는 실패시 null로 변환되고 다른 하나는 null 검사 자체입니다. 최소한 단일 테스트 및 분기에 모두 최적화되므로 테스트 및 캐스트는 캐스트 앤 널 테스트보다 느리거나 빠르지 않습니다.

복잡함 : 테스트 및 캐스트가 더 빠른 이유 : 캐스트 앤 널 테스트는 컴파일러가 라이브를 추적해야하는 외부 범위에 다른 변수를 도입했으며 제어가 얼마나 복잡한 지에 따라 해당 변수를 최적화하지 못할 수도 있습니다. 흐름입니다. 반대로, 테스트 앤 캐스트는 범위가 한정된 범위에서만 새 변수를 도입하므로 컴파일러는 범위가 종료 된 후 변수가 종료되었음을 알 수 있으므로 레지스터 할당을보다 잘 최적화 할 수 있습니다.

따라서이 “캐스트 앤 널 테스트가 테스트 앤 캐스트보다 낫다”는 조언 DIE를 보내 주시기 바랍니다. 부디. 테스트 앤 캐스트는 더 안전하고 빠릅니다.