Which is faster between is and typeof
Asked Answered
H

4

156

Which of these pieces of code is faster?

if (obj is ClassA) {}

if (obj.GetType() == typeof(ClassA)) {}

Edit: I'm aware that they don't do the same thing.

Hallucination answered 8/10, 2008 at 20:19 Comment(1)
Answered a similar question here: #58201Longlimbed
S
173

This should answer that question, and then some.

The second line, if (obj.GetType() == typeof(ClassA)) {}, is faster, for those that don't want to read the article.

(Be aware that they don't do the same thing)

Solvent answered 8/10, 2008 at 20:21 Comment(13)
+1: In the past I wondered why the C# compiler didn't compile typeof(string).TypeHandle to the ldtoken CIL instruction, but it looks like the CLR takes care of it in the JIT. It still takes a few extra opcodes but it's a more generalized application of the optimization.Doting
Read higherlogics.blogspot.ca/2013/09/… too - they retest for different frameworks and x86 vs x64 with widely differing results.Methodical
Please note this is true only for reference types. And the speed difference is not that significant. Given the boxing penalty in case of value types for GetType, is is always a safer choice as far as performance is concerned. Of course they do different things.Lugubrious
If you put that in Resharper suggests changing it to "is"!Logging
@nawfal, I initially thought your point about the boxing penalty made sense for struct types, but given that we're testing an object obj; variable, isn't it already boxed when this tends to be tested? Is there a case where you need to test the type of something and it isn't already boxed as an object?Counterpoint
@RobSedgwick, I wonder if the situation has changed since .NET 2.0 when the specially-optimized GetType() == typeof approach was the fastest (least wasteful) and FxCop warned not to use is (as is better, but not possible for struct types; FxCop may not have warned for is with a struct type). Perhaps is is more optimized in .NET 4.x than it used to be in .NET 2.0?Counterpoint
@RobParker if the value type is already boxed as an object variable, then you are right, calling GetType makes no difference. But I am not sure if that is the case in the given link.Lugubrious
@nawfal, what other declared type could the variable in question be that could possibly hold a struct type? Can a struct inherit, or implement an interface (I'm thinking "no", but maybe the latter is allowed)?Counterpoint
@RobParker Structs can implement interfaces but if you declare them as their interface type, then yes they will be boxed. For e.g. IInterfaceForMyStruct myStruct = new MyStruct(); leads to a boxed variable. Of course all structs and enums inherit from a common base class ValueType (which in turn inherit from object leading to unified type system) but user defined value types cant inherit anything, only implement.Lugubrious
@nawfal, okay, good clarification. But how, then, could you have this problem and not already have a boxed variable? If it was a declared specific value type there'd be no need to test its type (especially with no inheritance possible). Can you declare a ValueType variable which can hold any value type, and would that not have to be boxed already? I would think that supporting the polymorphism would require it to be boxed in that case, too.Counterpoint
@RobParker you're right. If you declare your value type as ValueType the variable has to be boxed. If variable is declared as a specific value type you can always rely on mystruct is MyStruct rather than mystruct.GetType() which will cause boxing. Perhaps useful in generic contexts.Lugubrious
The link leads to some menu.Havstad
Dead link. Would someone have at least the reference of the article?Marra
J
202

Does it matter which is faster, if they don't do the same thing? Comparing the performance of statements with different meaning seems like a bad idea.

is tells you if the object implements ClassA anywhere in its type heirarchy. GetType() tells you about the most-derived type.

Not the same thing.

Jumbo answered 8/10, 2008 at 20:23 Comment(3)
It does matter, because in my case I'm positive they return the same result.Hallucination
@[ilitirit]: they return the same result right now, but if you add a subclass later they won'tZoe
Optimising now will make your code brittle and difficult to maintain.Exhortation
S
173

This should answer that question, and then some.

The second line, if (obj.GetType() == typeof(ClassA)) {}, is faster, for those that don't want to read the article.

(Be aware that they don't do the same thing)

Solvent answered 8/10, 2008 at 20:21 Comment(13)
+1: In the past I wondered why the C# compiler didn't compile typeof(string).TypeHandle to the ldtoken CIL instruction, but it looks like the CLR takes care of it in the JIT. It still takes a few extra opcodes but it's a more generalized application of the optimization.Doting
Read higherlogics.blogspot.ca/2013/09/… too - they retest for different frameworks and x86 vs x64 with widely differing results.Methodical
Please note this is true only for reference types. And the speed difference is not that significant. Given the boxing penalty in case of value types for GetType, is is always a safer choice as far as performance is concerned. Of course they do different things.Lugubrious
If you put that in Resharper suggests changing it to "is"!Logging
@nawfal, I initially thought your point about the boxing penalty made sense for struct types, but given that we're testing an object obj; variable, isn't it already boxed when this tends to be tested? Is there a case where you need to test the type of something and it isn't already boxed as an object?Counterpoint
@RobSedgwick, I wonder if the situation has changed since .NET 2.0 when the specially-optimized GetType() == typeof approach was the fastest (least wasteful) and FxCop warned not to use is (as is better, but not possible for struct types; FxCop may not have warned for is with a struct type). Perhaps is is more optimized in .NET 4.x than it used to be in .NET 2.0?Counterpoint
@RobParker if the value type is already boxed as an object variable, then you are right, calling GetType makes no difference. But I am not sure if that is the case in the given link.Lugubrious
@nawfal, what other declared type could the variable in question be that could possibly hold a struct type? Can a struct inherit, or implement an interface (I'm thinking "no", but maybe the latter is allowed)?Counterpoint
@RobParker Structs can implement interfaces but if you declare them as their interface type, then yes they will be boxed. For e.g. IInterfaceForMyStruct myStruct = new MyStruct(); leads to a boxed variable. Of course all structs and enums inherit from a common base class ValueType (which in turn inherit from object leading to unified type system) but user defined value types cant inherit anything, only implement.Lugubrious
@nawfal, okay, good clarification. But how, then, could you have this problem and not already have a boxed variable? If it was a declared specific value type there'd be no need to test its type (especially with no inheritance possible). Can you declare a ValueType variable which can hold any value type, and would that not have to be boxed already? I would think that supporting the polymorphism would require it to be boxed in that case, too.Counterpoint
@RobParker you're right. If you declare your value type as ValueType the variable has to be boxed. If variable is declared as a specific value type you can always rely on mystruct is MyStruct rather than mystruct.GetType() which will cause boxing. Perhaps useful in generic contexts.Lugubrious
The link leads to some menu.Havstad
Dead link. Would someone have at least the reference of the article?Marra
U
27

They don't do the same thing. The first one works if obj is of type ClassA or of some subclass of ClassA. The second one will only match objects of type ClassA. The second one will be faster since it doesn't have to check the class hierarchy.

For those who want to know the reason, but don't want to read the article referenced in is vs typeof.

Unsought answered 8/10, 2008 at 20:22 Comment(1)
@amitjha I'm a little concerned that because that test was run under Mono that it doesn't include the JIT optimizations referenced in the article. Since the article shows the opposite, in my mind the question is an open one. In any event, comparing performance of operations that do different things depending on the type seems a worthless exercise. Use the operation that matches the behavior you need, not the one that is "faster"Unsought
L
19

I did some benchmarking where they do the same - sealed types.

var c1 = "";
var c2 = typeof(string);
object oc1 = c1;
object oc2 = c2;

var s1 = 0;
var s2 = '.';
object os1 = s1;
object os2 = s2;

bool b = false;

Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 10000000; i++)
{
    b = c1.GetType() == typeof(string); // ~60ms
    b = c1 is string; // ~60ms

    b = c2.GetType() == typeof(string); // ~60ms
    b = c2 is string; // ~50ms

    b = oc1.GetType() == typeof(string); // ~60ms
    b = oc1 is string; // ~68ms

    b = oc2.GetType() == typeof(string); // ~60ms
    b = oc2 is string; // ~64ms


    b = s1.GetType() == typeof(int); // ~130ms
    b = s1 is int; // ~50ms

    b = s2.GetType() == typeof(int); // ~140ms
    b = s2 is int; // ~50ms

    b = os1.GetType() == typeof(int); // ~60ms
    b = os1 is int; // ~74ms

    b = os2.GetType() == typeof(int); // ~60ms
    b = os2 is int; // ~68ms


    b = GetType1<string, string>(c1); // ~178ms
    b = GetType2<string, string>(c1); // ~94ms
    b = Is<string, string>(c1); // ~70ms

    b = GetType1<string, Type>(c2); // ~178ms
    b = GetType2<string, Type>(c2); // ~96ms
    b = Is<string, Type>(c2); // ~65ms

    b = GetType1<string, object>(oc1); // ~190ms
    b = Is<string, object>(oc1); // ~69ms

    b = GetType1<string, object>(oc2); // ~180ms
    b = Is<string, object>(oc2); // ~64ms


    b = GetType1<int, int>(s1); // ~230ms
    b = GetType2<int, int>(s1); // ~75ms
    b = Is<int, int>(s1); // ~136ms

    b = GetType1<int, char>(s2); // ~238ms
    b = GetType2<int, char>(s2); // ~69ms
    b = Is<int, char>(s2); // ~142ms

    b = GetType1<int, object>(os1); // ~178ms
    b = Is<int, object>(os1); // ~69ms

    b = GetType1<int, object>(os2); // ~178ms
    b = Is<int, object>(os2); // ~69ms
}

sw.Stop();
MessageBox.Show(sw.Elapsed.TotalMilliseconds.ToString());

The generic functions to test for generic types:

static bool GetType1<S, T>(T t)
{
    return t.GetType() == typeof(S);
}
static bool GetType2<S, T>(T t)
{
    return typeof(T) == typeof(S);
}
static bool Is<S, T>(T t)
{
    return t is S;
}

I tried for custom types as well and the results were consistent:

var c1 = new Class1();
var c2 = new Class2();
object oc1 = c1;
object oc2 = c2;

var s1 = new Struct1();
var s2 = new Struct2();
object os1 = s1;
object os2 = s2;

bool b = false;

Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 10000000; i++)
{
    b = c1.GetType() == typeof(Class1); // ~60ms
    b = c1 is Class1; // ~60ms

    b = c2.GetType() == typeof(Class1); // ~60ms
    b = c2 is Class1; // ~55ms

    b = oc1.GetType() == typeof(Class1); // ~60ms
    b = oc1 is Class1; // ~68ms

    b = oc2.GetType() == typeof(Class1); // ~60ms
    b = oc2 is Class1; // ~68ms


    b = s1.GetType() == typeof(Struct1); // ~150ms
    b = s1 is Struct1; // ~50ms

    b = s2.GetType() == typeof(Struct1); // ~150ms
    b = s2 is Struct1; // ~50ms

    b = os1.GetType() == typeof(Struct1); // ~60ms
    b = os1 is Struct1; // ~64ms

    b = os2.GetType() == typeof(Struct1); // ~60ms
    b = os2 is Struct1; // ~64ms


    b = GetType1<Class1, Class1>(c1); // ~178ms
    b = GetType2<Class1, Class1>(c1); // ~98ms
    b = Is<Class1, Class1>(c1); // ~78ms

    b = GetType1<Class1, Class2>(c2); // ~178ms
    b = GetType2<Class1, Class2>(c2); // ~96ms
    b = Is<Class1, Class2>(c2); // ~69ms

    b = GetType1<Class1, object>(oc1); // ~178ms
    b = Is<Class1, object>(oc1); // ~69ms

    b = GetType1<Class1, object>(oc2); // ~178ms
    b = Is<Class1, object>(oc2); // ~69ms


    b = GetType1<Struct1, Struct1>(s1); // ~272ms
    b = GetType2<Struct1, Struct1>(s1); // ~140ms
    b = Is<Struct1, Struct1>(s1); // ~163ms

    b = GetType1<Struct1, Struct2>(s2); // ~272ms
    b = GetType2<Struct1, Struct2>(s2); // ~140ms
    b = Is<Struct1, Struct2>(s2); // ~163ms

    b = GetType1<Struct1, object>(os1); // ~178ms
    b = Is<Struct1, object>(os1); // ~64ms

    b = GetType1<Struct1, object>(os2); // ~178ms
    b = Is<Struct1, object>(os2); // ~64ms
}

sw.Stop();
MessageBox.Show(sw.Elapsed.TotalMilliseconds.ToString());

And the types:

sealed class Class1 { }
sealed class Class2 { }
struct Struct1 { }
struct Struct2 { }

Inference:

  1. Calling GetType on structs is slower. GetType is defined on object class which can't be overridden in sub types and thus structs need to be boxed to be called GetType.

  2. On an object instance, GetType is faster, but very marginally.

  3. On generic type, if T is class, then is is much faster. If T is struct, then is is much faster than GetType but typeof(T) is much faster than both. In cases of T being class, typeof(T) is not reliable since its different from actual underlying type t.GetType.

In short, if you have an object instance, use GetType. If you have a generic class type, use is. If you have a generic struct type, use typeof(T). If you are unsure if generic type is reference type or value type, use is. If you want to be consistent with one style always (for sealed types), use is..

Lugubrious answered 12/2, 2013 at 15:54 Comment(1)
In reality, dont care at all. Use what makes most sense.Lugubrious

© 2022 - 2024 — McMap. All rights reserved.