CLR에서 'as'키워드를 사용하여 캐스팅


386

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

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

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

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


처음에 질문에 캐스트를 사용하는 이유에 대한 작은 예를 추가하거나 새로운 것을 시작할 수 있습니까? 단위 테스트에만 캐스트가 필요한 이유에 관심이 있습니다. 나는 그것이이 질문의 범위를 벗어난 것이라고 생각합니다.
Erik van Brakel

2
이를 위해 단위 테스트를 변경할 수 있습니다. 기본적으로 인터페이스에없는 콘크리트 객체에 속성이 있다는 사실로 요약됩니다. 그 속성을 설정해야하지만 그 속성은 다른 방법으로 설정되었을 것입니다. 그 질문에 대답합니까?
Frank V

패트릭 Hägne 아래가 예리하게 지적 된 바와 같이,이 IS 차이.
Neil

답변:


517

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

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


7
다음은 IL에 관한 is / as / casting의 비용입니다. atalasoft.com/cs/blogs/stevehawley/archive/2009/01/30/…
plinth

3
targetObject 대상 유형 인 경우 "is"및 캐스트 조합을 사용하는 것이 나쁜 방법으로 간주되는 이유는 무엇입니까? 더 느린 코드를 생성하지만,이 경우 "targetObject가 null 인 경우 수행"대신 "targetObject가 targetType 인 경우 수행"과 같이 AS 캐스트보다 의도가 더 명확합니다. 또한 AS 절은 불필요한 변수를 생성합니다. IF 범위를 벗어났습니다.
Valera Kolupaev

2
@ Valera : 좋은 점이지만, as / null 테스트는 거의 모든 C # 개발자에게 의도가 명확해야한다고 충분히 관용적이라고 제안합니다. 나는 개인적으로 + 캐스트에 관련된 중복을 좋아하지 않습니다. 실제로 두 가지 작업을 모두 수행하는 일종의 "as-if"구문을 원합니다. 그들은 너무 자주 함께 간다 ...
Jon Skeet

2
@ Jon Skeet : 늦어서 죄송합니다 .Is And Cast : 2135, Is And As : 2145, As And null check : 1961, 사양 : OS : Windows Seven, CPU : i5-520M, DDR3 1033 램 4GB, 어레이 벤치 마크 128,000,000 개 항목 중.
Behrooz

2
C # 7을 사용하면 수행 할 수 있습니다 if (randomObject is TargetType convertedRandomObject){ // Do stuff with convertedRandomObject.Value}또는 사용 switch/ case 문서 참조
WerWet

71

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

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

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


3
예외 발생은 비용이 많이 들지만 안전 점검으로 인해 더 많은 시간이 걸리므 오브젝트를 올바르게 캐스트 할 수 있다는 것을 알고 있다면 (Anton의 응답 참조). 그러나 안전 점검 비용은 상당히 작습니다.

17
잠재적으로 예외를 발생시키는 비용은 고려해야 할 요소이지만 종종 올바른 설계입니다.
Jeffrey L Whitledge

@panesofglass-참조 유형의 경우 변환 호환성은 항상 as 및 cast 모두에 대해 런타임에 확인되므로 두 가지 옵션을 구분하지 않습니다. (이것이 아니라면, 캐스트는 예외를 제기 할 수 없었습니다.)
Jeffrey L Whitledge

4
@Frank-예를 들어 pre-generics 콜렉션을 사용해야하고 API의 메소드에 Employees 목록이 필요하고 일부 조커가 대신 제품 목록을 전달하는 경우 올바르지 않은 캐스트 예외가 신호에 적합 할 수 있습니다 인터페이스 요구 사항 위반
Jeffrey L Whitledge

27

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)이 권장하기 때문입니다.


나는 캐스팅과에 대한 명확한 설명을 찾고 있었는데,이 대답은 일반적인 중간 언어 단계적 설명과 관련이 있기 때문에 더 명확 해졌습니다. 감사!
Morse

18

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


12

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

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

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


4
동일하지만 동일하지는 않습니다. 이렇게하면 더 많은 코드가 생성됩니다.
plinth

10

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

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


지금까지 주조는 주로 내 단위 테스트에 필요했지만 가져와 주셔서 감사합니다. 이 작업을하는 동안 내 마음에 보관하겠습니다.
Frank V

두꺼비와 함께, 나는 또한 유닛 테스트 측면이 @Frank V의 캐스팅과 관련이있는 이유가 궁금합니다. 문제를 다르게 관리해야하는 다른 문제를 해결하기 위해
상원 의원

@TheSenator이 질문은 3 년이 넘었으므로 기억이 나지 않습니다. 그러나 단위 테스트에서도 인터페이스를 적극적으로 사용하고 있었을 것입니다. 팩토리 패턴을 사용 중이고 테스트 할 대상 객체의 공용 생성자에 액세스 할 수 없었기 때문일 수 있습니다.
Frank V

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
}

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

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

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

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

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


7
@naasking : 첫 번째 스 니펫에 따라 두 번 테스트하면 필드 또는 ref매개 변수 인 경우 두 테스트간에 유형이 변경 될 가능성이 있습니다 . 지역 변수에는 안전하지만 필드에는 안전하지 않습니다. 벤치 마크를 실행하고 싶지만 블로그 게시물에 제공 한 코드가 완전하지 않습니다. 나는 마이크로 최적화가 아니라는 것에 동의하지만, 값을 두 번 사용하는 것이 "as"와 nullity 테스트를 사용하는 것보다 더 읽기 쉽고 우아하다고 생각하지 않습니다. (btw 후에는 "as"대신 스트레이트 캐스트를 사용합니다.)
Jon Skeet

5
또한 왜 더 안전한지 알지 못합니다. 실제로 왜 안전 하지 않은지 보여주었습니다 . 물론, 범위에 변수가 널 (null) 일 수 있지만, 후속 "if"블록의 범위 밖에서 변수를 사용하지 않으면 괜찮습니다. 내가 제기 한 안전 문제 (값을 변경하는 필드 주변)는 표시된 코드와 관련이 있습니다. 안전 문제는 개발자가 다른 코드에서 느슨해야합니다.
Jon Skeet 2016 년

1
실제로는 / cast 또는 as / cast를 나타내는 +1이 더 느리지 않습니다. 완전한 테스트를 직접 수행 한 결과, 내가 볼 수있는 한 아무런 차이가 없는지 확인할 수 있습니다. 솔직히 아주 짧은 시간에 엄청난 수의 캐스트를 실행할 수 있습니다 . 답변을 전체 코드로 업데이트합니다.
Jon Skeet

1
실제로 바인딩이 로컬이 아닌 경우 TOCTTOU 버그 (검사 시간, 사용 시간)가 발생할 가능성이 있으므로주의해야합니다. 더 안전한 이유에 관해서는, 어떤 이유로 현지인을 재사용하고 싶어하는 많은 주니어 개발자들과 함께 일합니다. 캐스트 앤 널 (cast-and-null)은 내 경험상 매우 위험하며, 그런 식으로 코드를 설계하지 않기 때문에 TOCTTOU 상황에 처한 적이 없습니다. 런타임 테스트 속도는 가상 디스패치보다 훨씬 빠릅니다 [1]! 다시 : 코드, 캐스트 테스트의 소스를 찾을 수 있는지 볼 수 있습니다. [1] higherlogics.blogspot.com/2008/10/…
42 초

1
@naasking : 로컬 재사용 문제가 발생하지는 않았지만 더 미묘한 TOCTTOU 버그보다 코드 검토에서 발견하기가 더 쉽다고 말하고 싶습니다. 또한 밀폐 된 클래스 대신 인터페이스에 대한 자체 벤치 마크 검사를 다시 실행하고 null 검사와 같은 성능을 팁을 제공한다는 것을 지적 할 가치가 있습니다. 그러나 말했듯이 성능은 왜 내가 여기서 특정 접근법을 선택하지 않습니까?
Jon Skeet 5

4

캐스트가 실패하면 'as'키워드에서 예외가 발생하지 않습니다. 대신 변수를 널 (또는 값 유형의 기본값)로 설정합니다.


3
값 유형에 대한 기본값이 없습니다. 캐스트 값 유형에는 사용할 수 없습니다.
Patrik Hägne

2
'as'키워드는 실제로 값 유형에서 작동하지 않으므로 항상 null로 설정됩니다.
Erik van Brakel

4

이것은 질문에 대한 답변이 아니라 질문 코드 예제에 대한 의견입니다.

일반적으로 IMyInterface에서 MyClass로 Object를 캐스트 할 필요는 없습니다. 인터페이스의 가장 큰 장점은 인터페이스를 구현하는 입력으로 객체를 가져 오면 어떤 종류의 객체를 가져올 지 신경 쓰지 않아도된다는 것입니다.

IMyInterface를 MyClass로 캐스팅하면 이미 MyClass 유형의 객체를 가져오고 IMyInterface를 사용하는 것이 의미가 없습니다.

이제 내 충고 : 인터페이스가 잘 설계되면 많은 유형 캐스팅을 피할 수 있습니다.


3

as연산자는 과부하가 될 수없고, 참조 형식에 사용될 수 있고, 반환 null동작이 실패하는 경우. 예외는 발생하지 않습니다.

캐스팅은 호환 가능한 모든 유형에서 사용될 수 있으며 오버로드 될 수 있으며 작업이 실패하면 예외가 발생합니다.

사용할 선택은 상황에 따라 다릅니다. 기본적으로 실패한 전환에 대해 예외를 throw할지 여부는 중요합니다.


1
'as'는 흥미로운 패턴을 제공하는 nullable 값 형식에도 사용할 수 있습니다. 코드에 대한 내 대답을 참조하십시오.
Jon Skeet

1

내 대답은 유형을 확인하지 않고 캐스팅 후 null을 확인하지 않는 경우에만 속도에 관한 것입니다. Jon Skeet의 코드에 두 가지 추가 테스트를 추가했습니다.

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

이 모든 것이 매우 빠르기 때문에 (내가했던 것처럼) 속도에 집중하려고하지 마십시오.


마찬가지로 테스트에서 as(오류 검사가없는) 변환이 캐스팅보다 약 1 ~ 3 % 빠릅니다 (약 1 억 반복에서 550ms 대 550ms). 어느 쪽도 신청서를 작성하거나 중단하지 않습니다.
palswim

1

이미 여기에 노출 된 모든 것 외에도 명시 적 캐스팅 사이에서 주목할만한 실제적인 차이점을 발견했습니다.

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

.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 불구 대상으로 할 때 OK입니다 :

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'


0

as키워드는 변환이 실패 할 경우 예외를 발생시키지 않습니다 주요 차이와 호환 참조 유형 간의 명시 적 캐스트와 동일하게 작동합니다. 대신 대상 변수에 널값을 생성합니다. 예외는 성능면에서 매우 비싸기 때문에 훨씬 나은 캐스팅 방법으로 간주됩니다.


하나는 CastClass를 호출하고 다른 하나는 IL 코드에서 IsInst를 호출하므로 동일하지 않습니다.
Jenix

0

당신이 선택하는 것은 필요한 것에 달려 있습니다. 나는 명시 적 캐스팅을 선호합니다

IMyInterface = (IMyInterface)someobj;

IMyInterface 유형으로 객체를 가져와야하지만 그렇지 않은 경우-확실히 문제입니다. 부작용을 수정하는 대신 정확한 오류가 수정되므로 가능한 빨리 오류를 얻는 것이 좋습니다.

그러나 object매개 변수로 허용 되는 메서드를 처리 하는 경우 코드를 실행하기 전에 정확한 유형을 확인해야합니다. 이러한 경우에는 as피할 수 있으므로 유용합니다 InvalidCastException.


0

"as"를 사용한 후 null을 확인 하시겠습니까? 아니면 앱에서 예외를 발생 시키길 원하십니까?

경험적으로 볼 때 변수가 항상 캐스트를 사용할 때 예상되는 유형이 될 것으로 기대하는 경우가 있습니다. 변수가 내가 원하는 것으로 캐스팅되지 않고 as를 사용하여 null을 처리 할 준비가되면 as를 사용합니다.



0

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
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.