Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can `typeof(T).IsAssignableFrom(x.GetType())` be safely rewritten as `x is T`?

Note: This is not a duplicate of "Use of IsAssignableFrom and “is” keyword in C#". That other question asks about typeof(T).IsAssignableFrom(type)), where type is not an object but a Type.

This seems trivial — I can hear you saying, "Just call x.GetType()!" — but due to the COM-related corner case mentioned below, that call causes problems, which is why I'm asking about the rewrite.

… or are there rare special cases where the two might give different results?

I stumbled upon a type check of the form:

typeof(TValue).IsAssignableFrom(value.GetType())

where TValue is a generic type parameter (without any constraints) and value is an object.

I am not entirely sure whether it is safe to rewrite the above simply as:

value is TValue

To my current knowledge, the two tests are equivalent with the exception of COM objects. is should trigger a proper QueryInterface, while IsAssignableFrom might get confused by the __ComObject RCW wrapper type and report a false negative.

Are there any other differences between is and the shown use of IsAssignableFrom?

like image 387
stakx - no longer contributing Avatar asked Jun 05 '17 11:06

stakx - no longer contributing


2 Answers

There are more cases where is and IsAssignableFrom returns different results, not just the one you mentioned for COM objects. For pair of array types with elements of type ElementType1 and ElementType2, where underlaying types of both element types are integer types of the same size but with opposite signedness, then

typeof(ElementType1[]).IsAssignableFrom(typeof(ElementType2[])) returns true but

new ElementType2[0] is ElementType1[] returns false

Specifically this includes arrays with these pairs of their element types:

  • byte / sbyte, short / ushort, int / uint, long / ulong

  • IntPtr / UIntPtr

  • any combination of enum type and either integer type or another enum type as long as underlaying types are of the same size

  • any combination of IntPtr / UIntPtr / int / uint in 32-bit process

  • any combination of IntPtr / UIntPtr / long / ulong in 64-bit process

This is due to differences in type system of C# and CLR as explained in

  • Why does my C# array lose type sign information when cast to object?
  • Why does “int[] is uint[] == true” in C#
  • Why is covariance of value-typed arrays inconsistent?

Different results of is and IsAssignableFrom in all cases mentioned above results from the fact that for new ElementType2[0] is ElementType1[] C# compiler simply emits False at compile-time (because it sees no way that for example int[] can be cast to uint[] as these are completly different types from C# perspective), completly omitting any runtime type checks. Fortunately casting array to object ((object)new ElementType2[0]) is ElementType1[] forces compiler to emit isinst IL instruction, which performs runtime type-check, that returns results consistent with IsAssignableFrom. This is also true for cases where target type is generic parameter, because it's type is not known at compile-time and C# compiler must emit isinst. So if you intend to replace IsAssignableFrom by is only in places where target type is generic parameter (as suggested in question title), I believe that these differences does not metter for you.

like image 115
Ňuf Avatar answered Nov 03 '22 00:11

Ňuf


    static void Main(string[] args)
    {
        int? bob = null;

        Test(bob);
    }

    private static void Test<T>(T bob)
    {
        Console.WriteLine(bob is T);
        Console.WriteLine(typeof(T).IsInstanceOfType(bob));
        Console.WriteLine(typeof(T).IsAssignableFrom(bob.GetType()));
        Console.ReadLine();
    }

Is an example where they act slightly differently (since bob is null). https://stackoverflow.com/a/15853213/34092 may be of interest.

Other than that (and the other exceptions you mention) they do appear to be equivalent.

like image 38
mjwills Avatar answered Nov 03 '22 00:11

mjwills