c#类型检查: typeof, GetType, 还是 is?


Type Checking: typeof, GetType, or is?

我见过很多人使用以下代码:

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

但我知道你也可以这样做:

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

或者:

1
2
if (obj1 is int)
    // Some code here

就我个人而言,我觉得最后一个是最干净的,但有什么我遗漏了吗?哪一个最好用,还是个人喜好?


一切都不同。

  • typeof采用类型名(在编译时指定)。
  • GetType获取实例的运行时类型。
  • 如果实例在继承树中,则is返回true。

例子

1
2
3
4
5
6
7
8
9
10
11
12
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);

What about typeof(T)? Is it also resolved at compile time?

对。t总是表达式的类型。记住,一般方法基本上是一组具有适当类型的方法。例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
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。要在执行时获取类型时,请使用GetType。很少有情况使用is,因为它执行强制转换,而且在大多数情况下,您最终还是要强制转换变量。

还有第四种选择是您没有考虑的(特别是如果您要将对象强制转换为您找到的类型);那就是使用as

1
2
3
4
Foo foo = obj as Foo;

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

这只使用一个强制转换,而此方法:

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

需要两个。


1。

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

这是非法的,因为typeof只适用于类型,而不适用于变量。我假设obj1是一个变量。所以,通过这种方式,typeof是静态的,它在编译时而不是运行时工作。

2。

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

如果obj1的类型正好是int,则为true。如果obj1从int派生,则if条件将为false。

三。

1
if (obj1 is int)

如果obj1是一个int,或者它是从一个名为int的类派生的,或者它实现了一个名为int的接口,那么这是真的。


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

这是一个错误。C中的typeof运算符只能使用类型名,不能使用对象。

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

这是可行的,但可能不像你所期望的那样。对于值类型,如本文所示,它是可以接受的,但是对于引用类型,只有当类型是完全相同的类型,而不是继承层次结构中的其他类型时,它才会返回true。例如:

1
2
3
4
5
6
7
8
9
10
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。但是,如果您使用Type类的IsAssignableFrom方法,就可以实现这一点。

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

不过,这项技术仍然存在一个主要问题。如果变量为空,则对GetType()的调用将引发nullreferenceexception。为了使它正常工作,您需要:

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

这样,您就有了与is关键字相同的行为。因此,如果这是您想要的行为,您应该使用is关键字,它更可读、更高效。

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

不过,在大多数情况下,is关键字仍然不是你真正想要的,因为仅仅知道一个对象是某种类型通常是不够的。通常,您希望实际使用该对象作为该类型的实例,这也需要对其进行强制转换。所以你可能会发现自己在写这样的代码:

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

但这使得clr最多检查两次对象的类型。它将检查一次以满足is运算符的要求,如果o确实是Animal运算符,我们将再次进行检查以验证强制转换。

这样做更有效率:

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

as运算符是一个强制转换,如果失败,它不会抛出异常,而是返回null。通过这种方式,clr只检查对象的类型一次,之后,我们只需要进行一次空检查,这样更有效。

但是要小心:很多人都会被as困住。因为它不抛出异常,一些人认为它是一个"安全"的演员,他们专门使用它,避免常规的演员。这会导致如下错误:

1
(o as Animal).Speak();

在这种情况下,开发者明确地假设o永远是Animal,只要他们的假设是正确的,一切都可以正常工作。但如果他们错了,那么他们最后得到的是一个NullReferenceException。如果有固定的演员阵容,他们将得到一个InvalidCastException,这将更准确地识别出问题。

有时,很难找到此错误:

1
2
3
4
5
6
7
8
9
10
11
class Foo{
    readonly Animal animal;

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

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

这是另一种情况,开发人员显然希望o每次都是Animal,但在使用as强制转换的构造函数中,这并不明显。直到使用Interact方法,它才是显而易见的,在这种方法中,Animal字段应该是正分配的。在这种情况下,不仅您最终会得到一个误导性的异常,而且直到可能比实际错误发生的时间晚得多时才会抛出异常。

综上所述:

  • 如果您只需要知道一个对象是否属于某种类型,那么使用is

  • 如果需要将对象视为某个类型的实例,但您不确定该对象是否属于该类型,请使用as并检查null

  • 如果需要将对象视为某个类型的实例,并且该对象应该是该类型的,请使用常规强制转换。


我有一个Type属性可以与之比较,不能使用is(如my_type is _BaseTypetoLookFor),但我可以使用这些:

1
2
3
base_type.IsInstanceOfType(derived_object);
base_type.IsAssignableFrom(derived_type);
derived_type.IsSubClassOf(base_type);

注意,在比较同一类型时,IsInstanceOfTypeIsAssignableFrom返回true,其中issubclassof将返回false。而IsSubclassOf不在接口上工作,其他两个在接口上工作。(另请参阅此问题和答案。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

如果你使用的是C 7,那么是时候更新安德鲁·黑尔的伟大答案了。模式匹配引入了一个很好的快捷方式,它在if语句的上下文中为我们提供了一个类型化变量,而不需要单独的声明/强制转换和检查:

1
2
3
4
if (obj1 is int integerValue)
{
    integerValue++;
}

对于这样一个演员组来说,这看起来并不是很吸引人,但是当你有很多可能的类型进入你的日常生活时,它确实会发光。以下是避免两次铸造的旧方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

尽可能地缩小这段代码,同时避免同一对象的重复强制转换,这一直困扰着我。上面的压缩很好,模式匹配如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
switch (obj1)
{
    case Button button:
        // do stuff...
        break;
    case TextBox text:
        // do stuff...
        break;
    case Label label:
        // do stuff...
        break;
    // and so on...
}

编辑:更新了更长的新方法,以根据palac的注释使用开关。


我更喜欢

也就是说,如果您使用的是IS,那么您可能没有正确地使用继承。

假设那个人:实体,那个动物:实体。feed是实体中的一种虚拟方法(使neil高兴)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
    }
  }
}

宁愿

1
2
3
4
5
6
7
8
9
10
11
class Person
{
  public override void Feed( Person p )
  {
    // feed the person
  }
  public override void Feed( Animal a )
  {
    // feed the animal
  }
}


我相信最后一个研究的也是遗传(例如狗是动物==true),这在大多数情况下是更好的。


这取决于我在做什么。如果我需要一个bool值(例如,要确定是否要强制转换为int),我将使用is。如果出于某种原因(比如说,为了传递给其他方法),我实际上需要该类型,那么将使用GetType()


用于获取类型的System.Type对象。类型表达式采用以下形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
System.Type type = typeof(int);

Example:

    public class ExampleClass
    {
       public int sampleMember;
       public void SampleMethod() {}

       static void Main()
       {
          Type t = typeof(ExampleClass);
          // Alternatively, you could use
          // ExampleClass obj = new ExampleClass();
          // Type t = obj.GetType();

          Console.WriteLine("Methods:");
          System.Reflection.MethodInfo[] methodInfo = t.GetMethods();

          foreach (System.Reflection.MethodInfo mInfo in methodInfo)
             Console.WriteLine(mInfo.ToString());

          Console.WriteLine("Members:");
          System.Reflection.MemberInfo[] memberInfo = t.GetMembers();

          foreach (System.Reflection.MemberInfo mInfo in memberInfo)
             Console.WriteLine(mInfo.ToString());
       }
    }
    /*
     Output:
        Methods:
        Void SampleMethod()
        System.String ToString()
        Boolean Equals(System.Object)
        Int32 GetHashCode()
        System.Type GetType()
        Members:
        Void SampleMethod()
        System.String ToString()
        Boolean Equals(System.Object)
        Int32 GetHashCode()
        System.Type GetType()
        Void .ctor()
        Int32 sampleMember
    */

此示例使用GetType方法确定用于包含数值计算结果的类型。这取决于结果编号的存储要求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    class GetTypeTest
    {
        static void Main()
        {
            int radius = 3;
            Console.WriteLine("Area = {0}", radius * radius * Math.PI);
            Console.WriteLine("The type is {0}",
                              (radius * radius * Math.PI).GetType()
            );
        }
    }
    /*
    Output:
    Area = 28.2743338823081
    The type is System.Double
    */

最后一个更清晰、更明显,还检查子类型。其他的则不检查多态性。


1
if (c is UserControl) c.Enabled = enable;


性能测试typeof()与gettype():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
namespace ConsoleApplication1
    {
    class Program
    {
        enum TestEnum { E1, E2, E3 }
        static void Main(string[] args)
        {
            {
                var start = DateTime.UtcNow;
                for (var i = 0; i < 1000000000; i++)
                    Test1(TestEnum.E2);
                Console.WriteLine(DateTime.UtcNow - start);
            }
            {
                var start = DateTime.UtcNow;
                for (var i = 0; i < 1000000000; i++)
                    Test2(TestEnum.E2);
                Console.WriteLine(DateTime.UtcNow - start);
            }
            Console.ReadLine();
        }
        static Type Test1<T>(T value) => typeof(T);
        static Type Test2(object value) => value.GetType();
    }
}

调试模式下的结果:

1
2
00:00:08.4096636
00:00:10.8570657

释放模式下的结果:

1
2
00:00:02.3799048
00:00:07.1797128


您可以在C中使用"typeof()"运算符,但需要使用System.io调用命名空间;如果要检查类型,则必须使用"is"关键字。