8

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?

stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
  • I *think* that's fine - and note that the JIT can actually do much better tricks with the `is` version (for value-types at least: it can throw away entire chunks of code while it JITs, since it knows what the result of `is` will be). If in doubt: add unit tests that check it for combinations you expect to see. – Marc Gravell Jun 05 '17 at 11:56
  • @Reniuz that question seems to mostly focus on the object vs type semantics, which aren't the issue here – Marc Gravell Jun 05 '17 at 11:56
  • @MarcGravell: Thanks for the reply. I'd love to add unit tests, but therein lies the problem. I need to expect anything and everything. The code in question is found in a very widespread library (Moq) that gets exposed to pretty much everything possible. I probably wouldn't know at which point my unit tests cover all possibilities. – stakx - no longer contributing Jun 05 '17 at 12:08
  • I don't get the belly-ache. If you have confidence that IsAssignableFrom() does what you want it to do then just use it. Perf is irrelevant in a unit test and brevity is irrelevant in a library. – Hans Passant Jun 05 '17 at 12:14
  • @HansPassant: `IsAssignableFrom` does what I want, *mostly*. I found one case where it fails (namely with COM objects), which is why I'd like to get rid of it. But I want to be sure that by changing the test to use the `is` operator, I'm not introducing other problems. – stakx - no longer contributing Jun 05 '17 at 12:16
  • 4
    There is at least one corner case where `is` and `IsAssignableFrom` returns different results: `typeof(int[]).IsAssignableFrom(typeof(uint[]))` returns true but `new uint[] { } is int[]` returns false – Ňuf Jun 05 '17 at 12:42
  • 2
    @Nuf: And `((object)new uint[0])) is int[]` is true! Your example is one of the cases that the C# compiler tries to optimize but gets wrong. I'm surprised that bug has never been fixed. – Eric Lippert Jun 05 '17 at 13:58
  • @EricLippert: Would I be right in assuming that we've got a difference here between what the CLR allows and what C# allows (without any type casting)? `new uint[0] is int[]` gives us C#'s perspective (`false`, therefore `uint[] uints = new int[0];` is illegal), while `typeof(int[]).IsAssignableFrom(typeof(uint[]))` gives us the CLR's perspective (`true`, which is why `uint[] uints = (uint[])(object)new int[0];` doesn't throw). – stakx - no longer contributing Jun 05 '17 at 14:19
  • @Nuf: if you'd like to turn the few comments on the point you've raised into an answer, I'd happily upvote & accept it. – stakx - no longer contributing Jun 05 '17 at 23:24
  • 1
    @stakx: That's correct. – Eric Lippert Jun 06 '17 at 21:09

2 Answers2

8

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

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.

Ňuf
  • 6,027
  • 2
  • 23
  • 26
4
    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.

mjwills
  • 23,389
  • 6
  • 40
  • 63