[C#] 유형 검사 : typeof, GetType 또는 is?

많은 사람들이 다음 코드를 사용하는 것을 보았습니다.

Type t = typeof(obj1);
if (t == typeof(int))
    // Some code here

그러나 나는 당신도 이것을 할 수 있다는 것을 알고 있습니다 :

if (obj1.GetType() == typeof(int))
    // Some code here

아니면 이거:

if (obj1 is int)
    // Some code here

개인적으로, 나는 마지막 것이 가장 깨끗하다고 ​​생각하지만 누락 된 것이 있습니까? 어느 것이 가장 사용하기 좋습니까, 아니면 개인 취향입니까?



답변

모두 다릅니다.

  • typeof 유형 이름 (컴파일시 지정)을 사용합니다.
  • GetType 인스턴스의 런타임 유형을 가져옵니다.
  • is 인스턴스가 상속 트리에 있으면 true를 반환합니다.

class Animal { } 
class Dog : Animal { }

void PrintTypes(Animal a) { 
    Console.WriteLine(a.GetType() == typeof(Animal)); // false 
    Console.WriteLine(a is Animal);                   // true 
    Console.WriteLine(a.GetType() == typeof(Dog));    // true
    Console.WriteLine(a is Dog);                      // true 
}

Dog spot = new Dog(); 
PrintTypes(spot);

무엇에 대해 typeof(T)? 컴파일 타임에도 해결됩니까?

예. T는 항상 표현식의 유형입니다. 제네릭 메서드는 기본적으로 적절한 형식의 메서드입니다. 예:

string Foo<T>(T parameter) { return typeof(T).Name; }

Animal probably_a_dog = new Dog();
Dog    definitely_a_dog = new Dog();

Foo(probably_a_dog); // this calls Foo<Animal> and returns "Animal"
Foo<Animal>(probably_a_dog); // this is exactly the same as above
Foo<Dog>(probably_a_dog); // !!! This will not compile. The parameter expects a Dog, you cannot pass in an Animal.

Foo(definitely_a_dog); // this calls Foo<Dog> and returns "Dog"
Foo<Dog>(definitely_a_dog); // this is exactly the same as above.
Foo<Animal>(definitely_a_dog); // this calls Foo<Animal> and returns "Animal". 
Foo((Animal)definitely_a_dog); // this does the same as above, returns "Animal"


답변

컴파일 타임에typeof 유형을 가져 오려는 경우에 사용하십시오 . 실행시 유형을 가져 오려면 사용하십시오 . 캐스트와 같이 사용할 경우는 거의 없으며 대부분의 경우 변수를 캐스트합니다.GetTypeis

고려하지 않은 네 번째 옵션이 있습니다 (특히 찾은 유형으로 객체를 캐스트하려는 경우). 사용하는 것 as입니다.

Foo foo = obj as Foo;

if (foo != null)
    // your code here

이것은 하나의 캐스트 만 사용 하지만이 접근법은 다음과 같습니다.

if (obj is Foo)
    Foo foo = (Foo)obj;

가 필요합니다 .

업데이트 (2020 년 1 월) :

  • C # 7+ 부터는 이제 인라인 캐스트 할 수 있으므로 ‘is’접근법을 하나의 캐스트로 수행 할 수 있습니다.

예:

if(obj is Foo newLocalFoo)
{
    // For example, you can now reference 'newLocalFoo' in this local scope
    Console.WriteLine(newLocalFoo);
}


답변

1.

Type t = typeof(obj1);
if (t == typeof(int))

typeof변수가 아닌 유형에서만 작동 하기 때문에 이것은 불법 입니다. obj1이 변수라고 가정합니다. 따라서이 방법 typeof은 정적이며 런타임 대신 컴파일 타임에 작동합니다.

2.

if (obj1.GetType() == typeof(int))

이것이 정확히 유형 인 true경우 obj1입니다 int. 경우 obj1도출에서 int의 경우 조건이 될 것입니다 false.

삼.

if (obj1 is int)

이다 true경우 obj1이다 int, 또는 그것이라는 클래스에서 파생 된 경우 int, 또는 호출 인터페이스를 구현하는 경우 int.


답변

Type t = typeof(obj1);
if (t == typeof(int))
    // Some code here

이것은 오류입니다. C #의 typeof 연산자는 개체가 아닌 형식 이름 만 사용할 수 있습니다.

if (obj1.GetType() == typeof(int))
    // Some code here

이것은 작동하지만 예상대로 작동하지 않을 수 있습니다. 값 유형의 경우 여기에 표시된 것처럼 허용되지만 참조 유형의 경우 유형이 상속 계층 구조의 다른 유형이 아닌 정확히 동일한 유형 인 경우에만 true를 리턴합니다 . 예를 들어 :

class Animal{}
class Dog : Animal{}

static void Foo(){
    object o = new Dog();

    if(o.GetType() == typeof(Animal))
        Console.WriteLine("o is an animal");
    Console.WriteLine("o is something else");
}

이 같이 인쇄 "o is something else"의 종류가 있기 때문에, o있다 Dog, 없다 Animal. 그러나 클래스 의 IsAssignableFrom메소드 를 사용하면이 작업을 수행 할 수 있습니다 Type.

if(typeof(Animal).IsAssignableFrom(o.GetType())) // note use of tested type
    Console.WriteLine("o is an animal");

그러나이 기술은 여전히 ​​큰 문제가되고 있습니다. 변수가 null 인 경우에 대한 호출 GetType()은 NullReferenceException을 발생시킵니다. 따라서 제대로 작동하려면 다음을 수행하십시오.

if(o != null && typeof(Animal).IsAssignableFrom(o.GetType()))
    Console.WriteLine("o is an animal");

이것으로 is키워드 와 동등한 동작을합니다 . 따라서 이것이 원하는 동작 인 경우 is보다 읽기 쉽고 효율적인 키워드를 사용해야합니다 .

if(o is Animal)
    Console.WriteLine("o is an animal");

그러나 대부분의 경우 is키워드는 여전히 원하는 것이 아닙니다. 일반적으로 객체가 특정 유형이라는 것을 아는 것만으로는 충분하지 않습니다. 일반적으로 실제로 해당 객체를 해당 유형의 인스턴스로 사용 하려면 캐스팅도 필요합니다. 따라서 다음과 같이 코드를 작성하는 것이 좋습니다.

if(o is Animal)
    ((Animal)o).Speak();

그러나 CLR은 객체 유형을 최대 두 번 확인합니다. is연산자 를 만족시키기 위해 한 번 확인하고 o실제로 인 경우 Animal캐스트를 다시 확인합니다.

대신이 작업을 수행하는 것이 더 효율적입니다.

Animal a = o as Animal;
if(a != null)
    a.Speak();

as운영자는 대신 반환, 실패 할 경우 예외를 throw하지 않습니다 캐스트입니다 null. 이런 식으로 CLR은 객체의 유형을 한 번만 확인한 다음 null 검사 만하면됩니다.

그러나 많은 사람들이와 함정에 빠지게됩니다 as. 예외를 던지지 않기 때문에 일부 사람들은 예외를 “안전한”캐스트로 생각하고 정기적으로 캐스트를 줄이면서 독점적으로 사용합니다. 다음과 같은 오류가 발생합니다.

(o as Animal).Speak();

이 경우, 개발자는 분명히 그 가정되는 o것입니다 항상Animal, 그리고 오래 자신의 가정이 올바른로, 모든 것이 잘 작동합니다. 그러나 그들이 틀렸다면, 여기에서 끝나는 것은입니다 NullReferenceException. 규칙적으로 캐스팅 InvalidCastException하면 문제를보다 정확하게 식별 할 수있게됩니다.

때로는이 버그를 찾기가 어려울 수 있습니다.

class Foo{
    readonly Animal animal;

    public Foo(object o){
        animal = o as Animal;
    }

    public void Interact(){
        animal.Speak();
    }
}

이것은 개발자가 명확하게 기대하고 또 다른 경우이다 oAnimal때마다, 그러나 이것은 생성자에 명확하지 않다 as캐스트가 사용됩니다. 필드가 긍정적으로 할당 될 것으로 예상되는 Interact방법에 도달 할 때까지 분명하지 않습니다 animal. 이 경우 오해의 소지가있을뿐 아니라 실제 오류가 발생했을 때보 다 훨씬 늦게 발생할 때까지 발생하지 않습니다.

요약하자면:

  • 객체가 어떤 유형인지 여부 만 알아야하는 경우을 사용하십시오 is.

  • 객체를 특정 유형의 인스턴스로 취급해야하지만 객체가 해당 유형인지 확실하지 않은 경우을 사용 as하고 확인하십시오 null.

  • 객체를 특정 유형의 인스턴스로 취급해야하고 객체가 해당 유형이어야하는 경우 규칙적인 캐스트를 사용하십시오.


답변

C # 7을 사용하고 있다면 Andrew Hare의 훌륭한 답변을 업데이트 할 때입니다. 패턴 일치 는 별도의 선언 / 캐스트 및 검사없이 if 문의 컨텍스트 내에서 형식화 된 변수를 제공하는 멋진 바로 가기를 도입했습니다.

if (obj1 is int integerValue)
{
    integerValue++;
}

이것은 이와 같은 단일 캐스트에 대해 매우 압도적이지만 루틴에 많은 유형이 올 때 실제로 빛납니다. 아래는 캐스팅을 두 번 피하는 오래된 방법입니다.

Button button = obj1 as Button;
if (button != null)
{
    // do stuff...
    return;
}
TextBox text = obj1 as TextBox;
if (text != null)
{
    // do stuff...
    return;
}
Label label = obj1 as Label;
if (label != null)
{
    // do stuff...
    return;
}
// ... and so on

가능한 한이 코드를 축소하고 동일한 객체의 중복 캐스트를 피하는 것이 항상 귀찮았습니다. 위는 다음과 일치하는 패턴으로 압축되어 있습니다.

switch (obj1)
{
    case Button button:
        // do stuff...
        break;
    case TextBox text:
        // do stuff...
        break;
    case Label label:
        // do stuff...
        break;
    // and so on...
}

편집 : Palec의 의견에 따라 스위치를 사용하도록 더 긴 새로운 방법을 업데이트했습니다.


답변

나는 Type비교할 속성 이 있었고 is(처럼 my_type is _BaseTypetoLookFor) 사용할 수 없지만 다음을 사용할 수 있습니다.

base_type.IsInstanceOfType(derived_object);
base_type.IsAssignableFrom(derived_type);
derived_type.IsSubClassOf(base_type);

공지 사항 IsInstanceOfTypeIsAssignableFrom반환 true동일한 유형을 비교할 때, IsSubClassOf가 반환됩니다 false. 그리고 IsSubclassOf다른 두 사람이 할 인터페이스에서 작동하지 않습니다. ( 이 질문과 답변 도 참조하십시오 .)

public class Animal {}
public interface ITrainable {}
public class Dog : Animal, ITrainable{}

Animal dog = new Dog();

typeof(Animal).IsInstanceOfType(dog);     // true
typeof(Dog).IsInstanceOfType(dog);        // true
typeof(ITrainable).IsInstanceOfType(dog); // true

typeof(Animal).IsAssignableFrom(dog.GetType());      // true
typeof(Dog).IsAssignableFrom(dog.GetType());         // true
typeof(ITrainable).IsAssignableFrom(dog.GetType()); // true

dog.GetType().IsSubclassOf(typeof(Animal));            // true
dog.GetType().IsSubclassOf(typeof(Dog));               // false
dog.GetType().IsSubclassOf(typeof(ITrainable)); // false


답변

내가 선호하는 IS를

즉, is를 사용하면 상속을 올바르게 사용 하지 않을 가능성이 높습니다 .

그 사람 : 실체, 그 동물 : 실체라고 가정하십시오. 피드는 Entity의 가상 메소드입니다 (Neil을 기쁘게하기 위해).

class Person
{
  // A Person should be able to Feed
  // another Entity, but they way he feeds
  // each is different
  public override void Feed( Entity e )
  {
    if( e is Person )
    {
      // feed me
    }
    else if( e is Animal )
    {
      // ruff
    }
  }
}

차라리

class Person
{
  public override void Feed( Person p )
  {
    // feed the person
  }
  public override void Feed( Animal a )
  {
    // feed the animal
  }
}