4

This statement when run from a console application sets 'x' to true:

var x = 3.GetType().IsAssignableTo(typeof(INumber<>)); // x == true

The same statement when run inside a unit test sets x to false. Why?

var x = 3.GetType().IsAssignableTo(typeof(INumber<>));

rory.ap
  • 34,009
  • 10
  • 83
  • 174

3 Answers3

5

This was a bug that was introduced in .NET 6, and fixed in .NET 8.

The correct behaviour is that uninstantiated generics are not AssignableTo.

I guess this makes sense, as you can't write INumber<> x = 3.

canton7
  • 37,633
  • 3
  • 64
  • 77
  • _"I guess this makes sense"_ - note that according to docs it should work for generics despite you not being able to write `T x = ...; INumber<> y= x;` – Guru Stron Aug 02 '23 at 13:39
  • @GuruStron Where in the docs? I saw nothing about open generic types – canton7 Aug 02 '23 at 13:42
  • _"The current type is a generic type parameter, and `targetType` represents one of the constraints of the current type."_ - in the [returns `true` section](https://learn.microsoft.com/en-us/dotnet/api/system.type.isassignableto?view=net-7.0#returns) – Guru Stron Aug 02 '23 at 13:43
  • 1
    @GuruStron That's about generic type *parameters*, not open generic types. Specifically, I think this is saying if you have the method `Foo where T : IThing`, and get the Type object for the `T`, then `T` is assignable to `IThing`. [See `IsGenericTypeParameter`](https://learn.microsoft.com/en-us/dotnet/api/system.type.isgenerictypeparameter?view=net-7.0#system-type-isgenerictypeparameter). – canton7 Aug 02 '23 at 13:47
  • 1
    Agree, seems that I misunderstood it a bit. – Guru Stron Aug 02 '23 at 13:48
  • My guess was that it could handle open generics too i.e. when `where T : INumber`. Maybe the question I linked confused me=) – Guru Stron Aug 02 '23 at 13:54
2

The actual question should be "why it returns true" and that is targeted by @canton7, but in addition to it I would like to add corresponding quote from the Type.IsAssignableTo docs:

Returns

true if any of the following conditions is true:

  • The current instance and targetType represent the same type.
  • The current type is derived either directly or indirectly from targetType. > - The current type is derived directly from targetType if it inherits from targetType;
  • the current type is derived indirectly from targetType if it inherits from a succession of one or more classes that inherit from targetType.
  • targetType is an interface that the current type implements.
  • The current type is a generic type parameter, and targetType represents one of the constraints of the current type.
  • The current type represents a value type, and targetType represents Nullable<c>.

false if none of these conditions are true, or if targetType is null.

Since none of true ones match the case the check should always has returned false.

Change your code to:

var isINumber = 3.GetType()
   .GetInterfaces()
   .Any(i => i.IsConstructedGenericType && i.GetGenericTypeDefinition() == typeof(INumber<>))

Or

var isINumberOfInt32 = 3.GetType().IsAssignableTo(typeof(INumber<int>));
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
-1

I will assume that your question can be translated to "how may the result of false be logically explained", rather than to "what was the exact purpose of the developers of the language/framework".

I am not a developer of the language/framework, so I can only make sense of the logic based on what is publicly available.

https://learn.microsoft.com/en-us/dotnet/api/system.numerics.inumber-1?view=net-8.0

We can see that INumber<TSelf>

enter image description here

is basically a "number of TSelf". And TSelf can be as follows:

enter image description here

Now, your System.Int32 is a signed value stored on 32 bits. It can overflow a System.Int16, for example, which is a perfectly valid possibility for TSelf, so, potentially you have overflows, which, as a possibility makes a false more correct than a true for your unit test.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
  • I'm not sure that this is correct or even relevant. This behaviour can be seen with *any* open generic type -- it has nothing to do with `INumber` specifically. Therefore, any answer which is based solely on `INumber` cannot be correct. – canton7 Aug 02 '23 at 13:32
  • @canton7 I'm firmly convinced that the possibility of having overflows is relevant. – Lajos Arpad Aug 02 '23 at 13:41
  • How do you explain the change in behaviour of `typeof(int).IsAssignableTo(typeof(IComparable<>))`, then? – canton7 Aug 02 '23 at 13:43
  • @canton7 what was its result previously and what is its result now? – Lajos Arpad Aug 02 '23 at 13:57
  • Exactly the same as `INumber`. `IsAssignableFrom` returned `false` pre-.NET 5, was erroneously changed to return `true` in .NET 6 and 7, and was fixed to return `false` in .NET 8. – canton7 Aug 02 '23 at 13:59
  • @canton7 then this is a particular case of a larger pattern. But the possibility of overflow is still relevant and something to watch out for in all versions where it's possible. – Lajos Arpad Aug 02 '23 at 14:01
  • It's not relevant to the question being asked, is my point. I'm not sure it's relevant to overflows either: an `INumber` will never magically turn into an `INumber` (or vice versa) because of an overflow. Overflows in C# wrap (or throw an exception), they don't cause promotion to a larger type – canton7 Aug 02 '23 at 14:02
  • @canton7 I understand your position, even though I disagree. Thank you for providing the reason for the down-vote. – Lajos Arpad Aug 02 '23 at 14:04