类型检查:typeof,GetType还是?

jas*_*onh 1435 c#

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

Type t = typeof(obj1);
if (t == typeof(int))
    // Some code here
Run Code Online (Sandbox Code Playgroud)

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

if (obj1.GetType() == typeof(int))
    // Some code here
Run Code Online (Sandbox Code Playgroud)

或这个:

if (obj1 is int)
    // Some code here
Run Code Online (Sandbox Code Playgroud)

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

Jim*_*mmy 1759

一切都不一样.

  • 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);
Run Code Online (Sandbox Code Playgroud)

怎么样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"
Run Code Online (Sandbox Code Playgroud)

  • 啊,所以如果我有一个福特级别来自Car和福特的一个实例,在那个实例上检查"是汽车"将是真的.说得通! (27认同)
  • @Shimmy如果在编译时评估typeof并且在运行时评估GetType(),那么GetType()会导致轻微的性能损失 (12认同)
  • @Prera​​kK`new Dog().GetType()是Animal`返回false(以及你的其他版本),因为`.GetType()`返回一个类型为`Type`的对象,而`Type`不是一个`Animal` . (7认同)
  • @Jimmy那么性能呢?`typeof`与`GetType()`? (3认同)
  • 需要澄清的是,我知道这一点,但是在添加代码示例之前,我已进行了评论。我想尝试为您本来很好的答案添加一些通俗易懂的英语。 (2认同)

And*_*are 182

使用typeof时,你想获得的类型编译时间.使用GetType时,你想要得到的类型执行时间.很少有任何情况可以使用,is因为它是一个演员,在大多数情况下,你最终还是会转换变量.

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

Foo foo = obj as Foo;

if (foo != null)
    // your code here
Run Code Online (Sandbox Code Playgroud)

这只使用一个演员而这种方法:

if (obj is Foo)
    Foo foo = (Foo)obj;
Run Code Online (Sandbox Code Playgroud)

需要两个.

  • 这个答案是否正确?你真的可以将一个实例传递给typeof()吗?我的经验一直没有.但我想通常检查一个实例可能必须在运行时发生,而在编译时检查一个类应该是可行的. (6认同)
  • 随着.NET 4的变化,`is`仍然执行演员表演? (4认同)
  • @jon(你的q后4年),不,你不能将一个实例传递给`typeof()`,这个答案不建议你可以.你改为传入类型,即`typeof(string)`works,`typeof("foo")`没有. (4认同)
  • 我们现在可以执行`if(obj是Foo foo){/ *在这里使用foo * /}` (3认同)

Sco*_*ham 69

1.

Type t = typeof(obj1);
if (t == typeof(int))
Run Code Online (Sandbox Code Playgroud)

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

2.

if (obj1.GetType() == typeof(int))
Run Code Online (Sandbox Code Playgroud)

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

3.

if (obj1 is int)
Run Code Online (Sandbox Code Playgroud)

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

  • 是的,我想是的."typeof(obj1)"在我尝试时无法编译. (4认同)
  • 从System.Int32或C#中的任何其他值类型派生是不可能的 (4认同)
  • @Sana,你为什么不试试呢:) 我想你会得到一个 System.Type 的实例,它代表 System.Type 类型!typeof 的文档在这里:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/typeof (2认同)

P D*_*ddy 49

Type t = typeof(obj1);
if (t == typeof(int))
    // Some code here
Run Code Online (Sandbox Code Playgroud)

这是一个错误.C#中的typeof运算符只能采用类型名称,而不是对象.

if (obj1.GetType() == typeof(int))
    // Some code here
Run Code Online (Sandbox Code Playgroud)

这可行,但可能不如您所期望的那样.对于值类型,正如您在此处所示,它是可接受的,但对于引用类型,如果类型是完全相同的类型,它将仅返回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");
}
Run Code Online (Sandbox Code Playgroud)

这将打印"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");
Run Code Online (Sandbox Code Playgroud)

但是,这种技术仍然存在一个重大问题.如果您的变量为null,则调用GetType()将抛出NullReferenceException.为了使其正常工作,您需要:

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

有了这个,你就有了与is关键字相同的行为.因此,如果这是您想要的行为,您应该使用is更具可读性和更高效的关键字.

if(o is Animal)
    Console.WriteLine("o is an animal");
Run Code Online (Sandbox Code Playgroud)

但在大多数情况下,is关键字仍然不是您真正想要的,因为通常只知道对象属于某种类型是不够的.通常情况下,要真正地使用该对象作为类型,这需要铸造太的一个实例.所以你可能会发现自己编写这样的代码:

if(o is Animal)
    ((Animal)o).Speak();
Run Code Online (Sandbox Code Playgroud)

但这使得CLR最多检查对象的类型两次.它将检查一次以满足is操作员,如果o确实是Animal,我们再次检查以验证演员.

这样做效率更高:

Animal a = o as Animal;
if(a != null)
    a.Speak();
Run Code Online (Sandbox Code Playgroud)

as操作是,如果它失败了,而不是返回,不会抛出异常铸造null.这样,CLR只检查对象的类型一次,之后,我们只需要进行空检查,这样效率更高.

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

(o as Animal).Speak();
Run Code Online (Sandbox Code Playgroud)

在这种情况下,开发商显然是假设o永远是一个Animal,只要他们的假设是正确的,一切工作正常.但如果他们错了,那么他们最终会得到的是一个NullReferenceException.通过定期演员,他们会得到一个InvalidCastException代替,这将更正确地识别问题.

有时候,这个bug很难找到:

class Foo{
    readonly Animal animal;

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

    public void Interact(){
        animal.Speak();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是开发商显然是期待另一种情况o是一个Animal每一次,但是这不是明显在构造函数中,其中as用于铸造.直到你得到这个Interact方法animal才明确指定该字段.在这种情况下,不仅会导致误导性异常,而且在发生实际错误之前可能要晚得多.

综上所述:

  • 如果您只需要知道对象是否属于某种类型,请使用is.

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

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

  • @batmaci:答案是正确的-它导致两次类型检查。第一次是“ o是动物”,这要求CLR检查变量“ o”的类型是否是“动物”。它第二次检查是何时强制执行语句(((Animal)o).Speak()`。不用检查两次,而是使用`as`检查一次。 (2认同)

Yah*_*ous 13

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

base_type.IsInstanceOfType(derived_object);
base_type.IsAssignableFrom(derived_type);
derived_type.IsSubClassOf(base_type);
Run Code Online (Sandbox Code Playgroud)

请注意,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
Run Code Online (Sandbox Code Playgroud)


Joe*_*elC 13

如果你正在使用C#7,那么现在是时候更新Andrew Hare的好答案了.模式匹配引入了一个很好的快捷方式,它在if语句的上下文中提供了一个类型变量,而不需要单独的声明/强制转换和检查:

if (obj1 is int integerValue)
{
    integerValue++;
}
Run Code Online (Sandbox Code Playgroud)

对于像这样的单人演员来说,这看起来相当平庸,但是当你有很多可能的类型进入你的日常生活时,真的很闪耀.以下是避免施放两次的旧方法:

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
Run Code Online (Sandbox Code Playgroud)

尽可能地缩小这些代码,以及避免重复使用同一个对象,这一直困扰着我.上面的模式匹配很好地压缩到以下:

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

编辑:根据Palec的评论更新了使用开关的更长的新方法.


bob*_*obo 9

我更喜欢的

也就是说,如果您使用的,那么您可能无法正确使用继承.

假设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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

class Person
{
  public override void Feed( Person p )
  {
    // feed the person
  }
  public override void Feed( Animal a )
  {
    // feed the animal
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 后者也不是真正使用继承.Foo应该是在Person和Animal中重写的实体的虚方法. (3认同)
  • @bobobobo 我认为你的意思是“重载”,而不是“继承”。 (2认同)

Str*_*ior 5

我相信最后一个也看待继承(例如Dog is Animal == true),这在大多数情况下更好.