题 类型检查:typeof,GetType还是?


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

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

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


1187
2018-06-11 19:10


起源


别忘了 as! - RCIX
as 虽然不是真正的类型检查...... - jasonh
as 肯定是一种类型检查的形式,每一点都是如此 is 是!它有效地使用 is 在幕后,并在MSDN中的所有地方使用,它提高了代码清洁度 is。而不是检查 is 首先,打电话给 as 建立一个可以使用的类型变量:如果它为null,则适当地响应;否则,继续。当然,我已经看到并使用了很多东西。 - Zaccone
有利的显着性能差异 as/is (涵盖在内 stackoverflow.com/a/27813381/477420)假设它的语义适合你的情况。 - Alexei Levenkov
@samusarin它没有“使用”反射。该 GetType 你链接的方法是在 System.Reflection.Assembly  - 一种完全不同的方法,在这里无关紧要。 - Kirk Woll


答案:


一切都不一样。

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

1497
2018-06-11 19:15



啊,所以如果我有一个福特级别来自Car和福特的一个实例,那么在那个实例上检查“是汽车”将是真的。说得通! - jasonh
为了澄清,我知道这一点,但在你添加代码示例之前我评论过。我想尝试为你已经很好的答案添加一些简单的英语清晰度。 - jasonh
@Jimmy那么性能呢? typeof 与 GetType()? - Shimmy
@Shimmy如果在编译时评估typeof并且在运行时评估GetType(),那么GetType()会导致轻微的性能损失 - Cedric Mamo
@Prera​​kK new Dog().GetType() is Animal 从那以后返回false(以及你的其他版本) .GetType() 返回一个类型的对象 Type,和 Type 不是 Animal。 - Maarten


使用 typeof 当你想获得类型时 编译时间。使用 GetType 当你想获得类型时 执行时间处理时间。很少有任何案例可供使用 is 因为它会进行演员表演,而且在大多数情况下,你最终还是会投射变量。

你没有考虑过第四个选项(特别是如果你要将一个对象转换为你找到的类型);那是用的 as

Foo foo = obj as Foo;

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

这仅用于  演员而这种方法:

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

要求


163
2018-06-11 19:14



随着.NET 4的变化 is 仍然演出? - ahsteele
这个答案是否正确?你真的可以将一个实例传递给typeof()吗?我的经验一直没有。但我想通常检查一个实例可能必须在运行时发生,而在编译时检查一个类应该是可行的。 - Jon Coombs
@jon(你的q后4年),不,你不能将实例传入 typeof(),这个答案不建议你可以。你传入的类型,即, typeof(string) 作品, typeof("foo") 才不是。 - Abel


1。

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

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

2。

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

如果obj1完全是int类型,则为true。如果obj1派生自int,则if条件将为false。

3。

if (obj1 is int)

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


59
2018-06-11 19:17



考虑1,你是对的。然而,我在这里的几个代码示例中看到了它。它应该是Type t = obj1.GetType(); - jasonh
是的,我想是的。 “typeof(obj1)”在我尝试时无法编译。 - Scott Langham
从System.Int32或C#中的任何其他值类型派生是不可能的 - reggaeguitar
你能说出什么是typeof(typeof(system.int32)) - Sana
@Sana,你为什么不尝试它:)我想你会得到一个表示System.Type类型的System.Type实例! typeof的文档在这里: docs.microsoft.com/en-us/dotnet/csharp/language-reference/... - Scott Langham


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 operator是一个转换器,如果失败则不会抛出异常,而是返回 null。这样,CLR只检查对象的类型一次,之后,我们只需要进行空检查,这样效率更高。

但要注意:许多人陷入困境 as。因为它不会抛出异常,所以有些人认为它是一个“安全”的演员,并且他们只使用它,避开常规演员。这会导致如下错误:

(o as Animal).Speak();

在这种情况下,开发人员显然是这样认为的 o 将 总是 豆 Animal,只要他们的假设是正确的,一切正常。但如果他们错了,那么他们最终会得到的是 NullReferenceException。经常演员,他们会得到一个 InvalidCastException 相反,哪个会更正确地识别问题。

有时,这个bug很难找到:

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

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


39
2018-06-11 19:34



如果(o是动物)((动物)o).Speak(); ?你能告诉我更多细节吗? - batmaci
@batmaci:它在答案中 - 它导致两种类型检查。第一次是 o is Animal,这需要CLR检查变量的类型 o 是一个 Animal。它第二次检查是否在语句中进行了转换 ((Animal)o).Speak()。而不是检查两次,检查一次使用 as。 - siride


我曾有一个 Type - 比较和不能使用的属性 is (喜欢 my_type is _BaseTypetoLookFor),但我可以使用这些:

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

请注意 IsInstanceOfType 和 IsAssignableFrom 返回 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

11
2018-05-15 10:39





我更喜欢

那就是说,如果你正在使用 ,你很可能  正确使用继承。

假设Person:Entity和那个Animal:Entity。 Feed是实体中的虚拟方法(让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
  }
}

7
2018-06-11 19:15



是的,我永远不会做前者,因为他知道Person来自Animal。 - jasonh
后者也不是真正使用继承。 Foo应该是在Person和Animal中重写的Entity的虚方法。 - Neil Williams
@bobobobo我认为你的意思是“超载”,而不是“继承”。 - lc.
@lc:不,我的意思是继承。第一个例子是一种不正确的方式(使用 是)获得不同的行为。第二个例子使用重载是,但避免使用 是。 - bobobobo
该示例的问题在于它无法扩展。如果添加了需要进食的新实体(例如昆虫或怪物),则需要在Entity类中添加新方法,然后在提供它的子类中覆盖它。如果(实体是X),那么这不是更优选的,否则如果(实体是Y)......这违反了LSP和OCP,继承可能不是问题的最佳解决方案。某种形式的授权可能是首选。 - ebrown


如果你正在使用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的评论更新了使用开关的更长的新方法。


6
2018-02-01 15:47



运用 switch 带模式匹配的语句 在这种情况下是可取的。 - Palec