类型检查:typeof,GetType还是?


1511

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

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

就个人而言,我觉得最后一个是最干净的,但是我缺少什么吗?哪一种是最佳使用方式,还是个人喜好?


27
别忘了as
RCIX

82
as并不是真正的类型检查...
jasonh

49
as肯定是类型检查的一种形式,每一点都按原样is!它有效地is在后台使用,并且在MSDN的整个地方都在使用它来提高代码的清洁度is。无需is先检查,而是as建立一个准备好使用的类型化变量的调用:如果为空,则进行适当响应。否则,请继续。当然,我已经看到并使用了很多东西。
Zaccone 2014年

15
假设/的语义适用于您的情况,则支持as/ is(在stackoverflow.com/a/27813381/477420中发现)存在明显的性能差异。
Alexei Levenkov 2015年

@samusarin它不“使用”反射。GetType您要链接的方法位于System.Reflection.Assembly-完全不同的方法,在这里无关紧要。
Kirk Woll

Answers:


1846

都是不同的。

  • 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"

29
嗯,所以如果我有一个从Car派生的Ford类和一个Ford实例,则在该实例上检查“ is Car”将为true。说得通!
杰森2009年

2
需要澄清的是,我知道这一点,但是在添加代码示例之前,我已进行了评论。我想尝试为您本来很好的答案添加一些普通的英语清晰度。
杰森2009年

12
@Shimmy如果在编译时评估了typeof且在运行时评估了GetType(),则有意义的是GetType()带来了轻微的性能下降
Cedric Mamo 2012年

那么新的Dog()。GetType()是Animal还是typeof(Dog)是Animal呢,它只是给出警告而不是错误?
2014年

7
@Prera​​kK new Dog().GetType() is Animal返回false(以及您的其他版本),因为.GetType()返回的对象类型为Type,而Type不是Animal
Maarten 2014年

194

使用typeof时,你想获得的类型编译时间。使用GetType时,你想要得到的类型执行时间。几乎没有任何情况可以使用,is因为它会进行强制转换,并且在大多数情况下,最终还是要强制转换该变量。

您还没有考虑过第四个选项(尤其是如果要将对象也转换为找到的类型时);即使用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);
}

4
随着.NET 4中的更改,是否is仍执行强制转换?
ahsteele

6
这个答案正确吗?您确实可以将实例传递给typeof()吗?我的经验不是。但是我想通常是这样的:检查实例可能必须在运行时进行,而检查类应该在编译时就可以了。
乔恩·库姆斯

4
@jon(问问后4年),不,您不能将实例传递到中typeof(),并且此答案并不建议您可以。您改为输入类型,即typeof(string)有效,typeof("foo")但不能。
亚伯

我不相信is在IL中执行强制转换,而是执行特殊操作。
abatishchev

3
我们现在可以做if (obj is Foo foo) { /* use foo here */ }
伊凡·加西亚·托皮特(IvanGarcíaTopete),

71

1。

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

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

2。

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

true是否obj1完全是type int。如果obj1从衍生int,则条件为false

3。

if (obj1 is int)

这是trueif obj1是一个int,或者它是从一个名为的类派生的int,或者它实现了名为的接口int


想一想,你是对的。但是,我已经在这里的几个代码示例中看到了它。它应该是类型t = obj1.GetType();
杰森2009年

4
是的,我是这样认为的。我尝试时不会编译“ typeof(obj1)”。
Scott Langham

4
无法从System.Int32或C#中的任何其他值类型派生
reggaeguitar 2014年

你能告诉我什么是typeof(typeof(system.int32))
萨那

1
@Sana,为什么不尝试呢:)我可以想象,尽管您返回了代表类型System.Type的System.Type实例!对于文件的typeof是在这里:docs.microsoft.com/en-us/dotnet/csharp/language-reference/...
斯科特朗廷

53
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",因为类型oDog,没有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操作是,如果它失败了,而不是返回,不会抛出异常铸造null。这样,CLR只需检查一次对象的类型,然后,我们只需要执行空检查即可,这会更有效。

但是要当心:许多人陷入了陷阱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();
    }
}

这是开发商显然是期待另一种情况o是一个Animal每一次,但是这不是明显在构造函数中,其中as用于铸造。在进入该Interact方法之前,这是不明显的,在该方法中,该animal字段应为正。在这种情况下,不仅会导致误导性异常,而且只有在比实际错误发生的时间晚很多的情况下才会抛出该异常。

综上所述:

  • 如果只需要知道某个对象是否为某种类型,请使用is

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

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


if(o is Animal)((Animal)o).Speak();这有什么问题??您能否提供更多详细信息?
batmaci

2
@batmaci:答案是正确的-它导致两次类型检查。第一次是o is Animal,这需要CLR检查变量的类型o是否为Animal。它第二次检查是何时将其强制转换为语句((Animal)o).Speak()。而不是检查两次,而是使用检查一次as
siride

我发现这绝对是个很好的解释,感谢您的澄清!
Paul Efford

16

如果您使用的是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的注释使用开关。


1
在这种情况下,建议将switch语句与模式匹配一​​起使用。
Palec

您将如何处理不是?在这个特定的代码块中?if (obj1 is int integerValue) { integerValue++; }
Ben Vertonghen

本,如果我理解您的问题,那么我将只有一个else语句来处理其他情况,因为您不能将非整数放入整数变量中。:)
JoelC '19

14

我有一个Type-property可以比较,不能使用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

9

我更喜欢的

就是说,如果您使用is,则可能无法正确使用继承。

假定该人:实体,该动物:实体。Feed是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
  }
}

1
是的,我永远不会做前者,因为知道人来自动物。
杰森2009年

3
后者也不是真正使用继承。Foo应该是在Person和Animal中重写的Entity的虚拟方法。
尼尔·威廉姆斯

2
@bobobobo我认为您的意思是“超载”,而不是“继承”。
lc。

@lc:不,我是说继承。第一个示例是一种错误的方法(使用is)来获得不同的行为。第二个示例使用重载yes,但是避免使用is
bobobobo

1
该示例的问题在于它无法扩展。如果添加了需要吃的新实体(例如,昆虫或怪物),则需要在Entity类中添加一个新方法,然后在需要它的子类中覆盖它。如果(实体是X),则此列表比列表更可取;否则,如果(实体是Y),则列表最好。某种形式的授权可能是更可取的。
ebrown

5

我相信最后一个也要研究继承性(例如Dog is Animal == true),这在大多数情况下会更好。


2

这取决于我在做什么。如果我需要布尔值(例如,确定是否将其转换为整数),则使用is。如果出于某种原因(例如,传递给其他方法)实际需要该类型,我将使用GetType()


1
好点子。我忘了提到在看了几个使用if语句检查类型的答案后才想到这个问题。
杰森2009年

0

最后一个是更清晰,更明显的样式,并且还会检查子类型。其他人不检查多态性。


0

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

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方法来确定用于包含数值计算结果的类型。这取决于结果编号的存储要求。

    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
    */

-4
if (c is UserControl) c.Enabled = enable;

4
请编辑更多信息。不建议使用纯代码和“尝试此”答案,因为它们不包含可搜索的内容,并且不解释为什么有人应该“尝试此”。
abarisone

您的答案与问题无关。
menxin

-5

您可以在C#中使用“ typeof()”运算符,但是您需要使用System.IO来调用名称空间。如果要检查类型,则必须使用“ is”关键字。


7
typeof没有在名称空间中定义,它是一个关键字。System.IO与此无关。
Arturo TorresSánchez2015年

-5

性能测试typeof()vs GetType():

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

调试模式下的结果:

00:00:08.4096636
00:00:10.8570657

在发布模式下的结果:

00:00:02.3799048
00:00:07.1797128

1
不应将DateTime.UtcNow用于性能评估。使用您的代码,但使用Stopwatch类,在Debug模式下,我一直得到相反的结果。UseTypeOf:00:00:14.5074469 UseGetType:00:00:10.5799534。发行模式是一样的,如预期
阿列克谢谢尔巴克

@AlexeyShcherbak秒表和DateTime.Now之间的差不能超过10-20毫秒,请再次检查代码。而且我不在乎测试中的毫秒。我的代码也将是使用Stopwatch更长的几行代码。
亚历山大·瓦西里耶夫

1
一般来说,这是一种不好的做法,而不是针对您的特定情况。
Alexey Shcherbak

4
@AlexanderVasilyev绝不应该将大量的代码行用作做据证明有误导作用的参数。如msdn.microsoft.com/en-us/library/system.datetime(v=vs.110).aspx中所示DateTime如果您担心时间低于100ms,则不应使用它,因为它使用操作系统的时间范围。与Stopwatch使用处理器的相比TickDateTimeWin7中使用的分辨率高达15ms。
埃里克·吴
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.