Initially I faced this issue when I was testing my code with UnitTest framework using Assert.AreEqual methods. I noticed that for UInt32 and UInt64 types different overload of AreEqual was selected (AreEqual(object, object) instead of AreEqual<T>(T, T)). I did some research and got the following simple code:
public struct MyInteger
{
public SByte SByte { get; set; }
public Byte Byte { get; set; }
public UInt16 UInt16 { get; set; }
public UInt32 UInt32 { get; set; }
public UInt64 UInt64 { get; set; }
public Int16 Int16 { get; set; }
public Int32 Int32 { get; set; }
public Int64 Int64 { get; set; }
}
public class MyGenericClass
{
public static void DoNothing<T>(T expected, T actual)
{
}
}
public class IntegerTest
{
public void TestIntegers()
{
var integer = new MyInteger
{
SByte = 42,
Byte = 42,
Int16 = 42,
Int32 = 42,
Int64 = 42,
UInt16 = 42,
UInt32 = 42,
UInt64 = 42
};
MyGenericClass.DoNothing(42, integer.SByte); // T is Int32
MyGenericClass.DoNothing(42, integer.Byte); // T is Int32
MyGenericClass.DoNothing(42, integer.Int16); // T is Int32
MyGenericClass.DoNothing(42, integer.Int32); // T is Int32
MyGenericClass.DoNothing(42, integer.Int64); // T is Int64
MyGenericClass.DoNothing(42, integer.UInt16); // T is Int32
MyGenericClass.DoNothing(42, integer.UInt32); // Error
MyGenericClass.DoNothing(42, integer.UInt64); // Error
MyGenericClass.DoNothing((UInt32)42, integer.UInt32); // T is UInt32
MyGenericClass.DoNothing((UInt64)42, integer.UInt64); // T is UInt64
}
}
The Error message I get is "The type arguments for method 'MyGenericClass.DoNothing<T>(T, T)' cannot be inferred from the usage. Try specifying the type arguments explicitly.". The workaround is relatively easy (use explicit cast), so I just want to know, what is so special about UInt32 and UInt64, what other types don't have (or have), and why UInt16 is not behaving the same way?
P.S. Oh, I almost forgot - I've found this table of type conversions, but first of all - it is for new "Roslyn" compiler, and second of all - I don't see the answer there anyway, maybe someone will point it out?
Because T
is shared for both arguments to DoNothing
, the compiler can only use it if there is a common base class or implicit operator to convert to a common class for both inputs.
The two errors come because there is no implicit conversion for UInt32
or UInt64
to/from Int32
(your 42
literal).
The reason there is no implicit conversion is because there can an information loss between the two. All the other conversions share a common range of values. For example, UInt16
has a range of 0 to 65535, which is within the range of a normal int
. Same goes for Byte
which can be represented as 0-7
. C# considers these kinds of conversions "safe" and so can be performed implicitly. But UInt32
can go from 0 to 4,294,967,295 which is twice as high as a normal int
can possibly go. C# considers these kinds of conversions not safe and requires you to perform an explicit
cast conversion. The semantics of which denote that you expect that this conversion might fail under certain circumstances (values being outside a compatible range).
DoNothing(42, integer.SByte); // Converts SByte to Int32
DoNothing(42, integer.Byte); // Converts Byte to Int32
DoNothing(42, integer.Int16); // Converts Int16 to Int32
DoNothing(42, integer.Int32); // Already the same
DoNothing(42, integer.Int64); // Converts Int32 to Int64
DoNothing(42, integer.UInt16); // Converts UInt16 to Int32
DoNothing(42, integer.UInt32); // Error - no implicit conversion
DoNothing(42, integer.UInt64); // Error - no implicit conversion
DoNothing((UInt32)42, integer.UInt32); // Explicitly converted to UInt32
DoNothing((UInt64)42, integer.UInt64); // Explicitly converted to UInt64
The explicit conversion worked because you purposely chose a number that was within the shared range of int
and Int32
and Int64
. If you change it to negative 42, it wouldn't work. (rather, it could if you ran it in an unchecked
context and let the number overflow/wraparound.)
When you just provide a literal, say 42
it always means an Int32
in c#. To answer your question
Why
integer.UInt32
cannot be inferred?
because integer.UInt32
is of type UInt32
where as 42
is of type Int32
so compiler can't make assumptions about what you meant, hence it produces error.
You need to use U
suffix which makes the compiler to infer it correctly as UInt32
MyGenericClass.DoNothing(42U, integer.UInt32);//No error
Why
integer.UInt64
cannot be inferred?
refer above answer, it is same expect you need to use UL
suffix :)
Why
integer.UInt16
can be inferred?
You'll see that integer.UInt16
is actually inferred as Int32
and not UInt16
because any UInt16
value can be implicitly converted to Int32
because its range fits the Int32
which is 0 - 65535
, and also 42 is a Int32
so compiler is happy to infer it as Int32
and not UInt16
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With