【问题标题】:Type Checking: typeof, GetType, or is?类型检查:typeof、GetType 或 is?
【发布时间】:2010-11-02 06:04:21
【问题描述】:

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

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

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

【问题讨论】:

  • 别忘了as
  • as 并不是真正的类型检查...
  • as 肯定是一种类型检查的形式,每一位都和is 一样多!它在幕后有效地使用了is,并且在MSDN 中的所有地方都在使用它与is 相比提高代码清洁度的地方。不是首先检查is,而是调用as 建立一个可供使用的类型变量:如果它为空,则适当响应;否则,继续。当然,我已经看过并使用了很多东西。
  • 假设 as/is(包含在 stackoverflow.com/a/27813381/477420 中)假设其语义适用于您的情况,存在显着的性能差异。
  • 我认为 typeof(obj1) 是一个语法错误。我想你的意思是 Type t = obj1.GetType();

标签: c# types typeof gettype


【解决方案1】:

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)

如果obj1int,或者如果它派生自名为int 的类,或者如果它实现名为int 的接口,则这是true

【讨论】:

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

如果您想在编译时获取类型,请使用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 月):

  • As of C# 7+,您现在可以进行内联转换,因此“is”方法现在也可以在一次转换中完成。

例子:

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

【讨论】:

  • 随着 .NET 4 的变化,is 仍然执行转换吗?
  • 这个答案正确吗?真的可以将实例传递给 typeof() 吗?我的经验是不。但我想通常情况下检查实例可能必须在运行时发生,而检查类应该在编译时可行。
  • @jon(你的 q. 4 年后),不,你不能将实例传递给typeof(),这个答案并不表明你可以。您改为传递类型,即typeof(string) 有效,typeof("foo") 无效。
  • 我不相信 is 在 IL 中执行这样的转换,而是非常特殊的操作。
  • 我们现在可以做if (obj is Foo foo) { /* use foo here */ }
【解决方案3】:

一切都不一样。

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

【讨论】:

  • 啊,所以如果我有一个派生自 Car 的 Ford 类和 Ford 的一个实例,那么在该实例上检查“is Car”将是正确的。有道理!
  • 澄清一下,我知道这一点,但我在您添加代码示例之前发表了评论。我想尝试为您已经非常出色的答案添加一些简单的英语清晰度。
  • @Shimmy 如果 typeof 在编译时被评估并且 GetType() 在运行时被评估,那么 GetType() 会导致轻微的性能损失是有道理的
  • @Prera​​kK new Dog().GetType() is Animal 返回 false(以及您的其他版本),因为 .GetType() 返回类型为 Type 的对象,而 Type 不是 Animal
  • 更一般地,当您使用is 时,如果结果(truefalse)在编译时已知,您会收到编译时警告。这意味着您应该更改代码!示例 1:void M(Dog d) { var test = d is System.Exception; } 在编译时可以看出,空引用或Dog 的实例永远不可能是System.Exception 的实例,因为涉及的类型是class 类型,每个class 可以有只有一个直接基类。示例 2:void M(int i) { var test = i is IConvertible; } 在编译时可以看出这始终是正确的(i 永远不会为空)。
【解决方案4】:

如果您使用的是 C# 7,那么是时候更新 Andrew Hare 的出色答案了。 Pattern matching 引入了一个很好的快捷方式,它在 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 的评论更新了更长的新方法以使用开关。

【讨论】:

  • 在这种情况下建议使用switch statement with pattern matching
  • 如何处理 is not ?在这个特定的代码块中? if (obj1 is int integerValue) { integerValue++; }
  • Ben,如果我理解您的问题,我将有一个 else 语句来处理其他情况,因为您不能将非整数放入整数变量中。 :)
【解决方案5】:
Type t = typeof(obj1);
if (t == typeof(int))
    // Some code here

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

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

这会起作用,但可能不像您所期望的那样。对于值类型,正如您在此处显示的那样,它是可以接受的,但对于引用类型,它只会在类型是 exact same 类型时返回 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。但是,如果您使用 Type 类的 IsAssignableFrom 方法,则可以完成这项工作。

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:答案就在其中——它会导致两次类型检查。第一次是o is Animal,需要CLR检查变量o的类型是否为Animal。它的第二次检查是在声明((Animal)o).Speak() 中强制转换时。与其检查两次,不如使用as 检查一次。
  • 我发现这是一个非常好的解释,感谢您的澄清!
【解决方案6】:
if (c is UserControl) c.Enabled = enable;

【讨论】:

  • 请编辑更多信息。不建议使用纯代码和“试试这个”的答案,因为它们不包含可搜索的内容,也没有解释为什么有人应该“试试这个”。
  • 您的回答与问题无关。
【解决方案7】:

我有一个 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 不适用于其他两个接口的接口。 (另见this question and answer。)

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

【讨论】:

    【解决方案8】:

    用于获取某个类型的 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
        */
    

    【讨论】:

      【解决方案9】:

      性能测试 typeof() 与 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
      

      【讨论】:

      • 不应使用 DateTime.UtcNow 来衡量性能。使用您的代码,但使用 Stopwatch 类,我在调试模式下得到了始终相反的结果。 UseTypeOf:00:00:14.5074469 UseGetType:00:00:10.5799534。发布模式和预期一样
      • @AlexeyShcherbak Stopwatch 和 DateTime.Now 之间的差异不能超过 10-20 毫秒,请再次检查您的代码。而且我不在乎测试中的毫秒数。此外,使用 Stopwatch 时,我的代码将增加几行代码。
      • 一般来说这是不好的做法,而不是在您的特定情况下。
      • @AlexanderVasilyev 永远不应将代码行数用作做有据可查误导性的事情的论据。如msdn.microsoft.com/en-us/library/system.datetime(v=vs.110).aspx 中所见,如果您担心时间低于100ms,则不应使用DateTime,因为它使用操作系统的时间范围。与使用处理器的TickStopwatch 相比,Win7 中DateTime 使用的分辨率高达15ms。
      【解决方案10】:

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

      【讨论】:

      • typeof 没有在命名空间中定义,它是一个关键字。 System.IO 与此无关。
      【解决方案11】:

      我更喜欢

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

      假设 Person : Entity 和 Animal : Entity。 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
        }
      }
      

      【讨论】:

      • 没错,我永远不会做前者,因为我知道 Person 派生自 Animal。
      • 后者也没有真正使用继承。 Foo 应该是在 Person 和 Animal 中被覆盖的 Entity 的虚拟方法。
      • @bobobobo 我认为您的意思是“重载”,而不是“继承”。
      • @lc:不,我的意思是继承。第一个例子是一种不正确的方式(使用is)来获得不同的行为。第二个示例使用重载 yes,但避免使用 is.
      • 这个例子的问题是它不能扩展。如果您添加了需要进食的新实体(例如昆虫或怪物),则需要在 Entity 类中添加一个新方法,然后在为其提供食物的子类中覆盖它。这并不比列表更可取 if (entity is X) else if (entity is Y)... 这违反了 LSP 和 OCP,继承可能不是解决问题的最佳方法。某种形式的委派可能会更受欢迎。
      【解决方案12】:

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

      【讨论】:

      • 好点。我忘了提到,在查看了几个使用 if 语句检查类型的答案后,我得到了这个问题。
      【解决方案13】:

      最后一个更简洁,更明显,并且还检查子类型。其他的不检查多态性。

      【讨论】:

        【解决方案14】:

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

        【讨论】:

          猜你喜欢
          • 2011-07-18
          • 1970-01-01
          • 2011-07-20
          • 2012-07-03
          • 1970-01-01
          • 2013-10-24
          • 1970-01-01
          • 2016-06-25
          相关资源
          最近更新 更多