Just out of curiosity, why does the compiler treat an unconstrained generic type any differently than it would typeof(object)?
class Bar { }
class Foo
{
void foo(object thing)
{
((Bar)thing).ToString();
}
}
class Foo<T>
{
void foo(T thing)
{
((Bar)thing).ToString();
}
}
In the above, casting "T thing" to Bar results in a compiler error. Casting "object thing" to Bar however is something the compiler lets me do, at my own risk of course.
What I don't see is why. In .net object after all is a catch-all and the run-time type could be a boxed value or an object of any type. So I don't see what logical reason there is for the compiler to differentiate between the two cases. The best I can do is something like "the programmer would expect the compiler to do type checking with generic types, but not with object". :) Is that all there is to it?
Btw, I am aware that I can still get my cast done in the Foo case, by simply writing
((Bar)(object)thing).ToString();
I just want to understand why the compiler does this...
The where clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type.
Interface Type Constraint You can constrain the generic type by interface, thereby allowing only classes that implement that interface or classes that inherit from classes that implement the interface as the type parameter.
C# allows you to use constraints to restrict client code to specify certain types while instantiating generic types. It will give a compile-time error if you try to instantiate a generic type using a type that is not allowed by the specified constraints.
The significance here is object
. If the first example was anything other than object
it would behave the same. Basically, what you are saying at the moment here:
(Bar)thing
is: "convert a T
to a Bar
"; which is nowhere near legal in the general case. By adding object
you make it:
(Bar)(object)thing
which is "convert a T
to an object
..." - which is always legal, since object
is the root of all managed types; and note this may invove a box - "...and then cast the object
as a Bar
" - again; it is always legal at compile time, and involves a type-check ("unbox-any") at runtime.
For example: suppose T
is DateTime
...
DateTime thing = ...
Bar bar = (Bar)(object)thing;
is perfectly valid; sure it'll fail at runtime, but: this is the scenario you need to keep in mind.
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