While reading Microsoft documentation, I stumbled on such an interesting code sample:
interface ISomeInterface
{...}
class SomeClass
{...}
class MyClass<T>
{
void SomeMethod(T t)
{
ISomeInterface obj1 = (ISomeInterface)t;//Compiles
SomeClass obj2 = (SomeClass)t; //Does not compile
}
}
It means you can cast your generic to the interface explicitly but not to the class unless you have a constraint. Well, I still cannot understand the logic behind the decision as both interface and class type castings are throwing exceptions, so why would one protect against only one of these exceptions?
BTW- there is a way around the compile error but this does not remove the logic mess in my head:
class MyOtherClass
{...}
class MyClass<T>
{
void SomeMethod(T t)
{
object temp = t;
MyOtherClass obj = (MyOtherClass)temp;
}
}
Object, you'll apply constraints to the type parameter. For example, the base class constraint tells the compiler that only objects of this type or derived from this type will be used as type arguments. Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class.
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.
That's exactly what you get in normal circumstances - without generics - when you try to cast between classes with no inheritance relationship:
public interface IA
{
}
public class B
{
}
public class C
{
}
public void SomeMethod( B b )
{
IA o1 = (IA) b; <-- will compile
C o2 = (C)b; <-- won't compile
}
So without a constraint, the generic class will behave as if there is no relationship between the classes.
Continued...
Well, let's say someone does this:
public class D : B, IA
{
}
And then calls:
SomeMethod( new D() );
Now you'll see why the compiler lets the interface cast pass. It really can't know at compile time if an interface is implemented or not.
Remember that the D class may very well be written by someone who is using your assembly - years after you compiled it. So there is no chance that the compiler can refuse to compile it. It must be checked at run time.
The big difference is that an interface is guaranteed to be a reference type. Value types are the trouble makers. It is explicitly mentioned in the C# Language Specification, chapter 6.2.6, with an excellent example that demonstrates the problem:
The above rules do not permit a direct explicit conversion from an unconstrained type parameter to a non-interface type, which might be surprising. The reason for this rule is to prevent confusion and make the semantics of such conversions clear. For example, consider the following declaration:
class X<T>
{
public static long F(T t) {
return (long)t; // Error
}
}
If the direct explicit conversion of t to int were permitted, one might easily expect that X.F(7) would return 7L. However, it would not, because the standard numeric conversions are only considered when the types are known to be numeric at compile time. In order to make the semantics clear, the above example must instead be written:
class X<T>
{
public static long F(T t) {
return (long)(object)t; // Ok, but will only work when T is long
}
}
This code will now compile but executing X.F(7) would then throw an exception at runtime, since a boxed int cannot be converted directly to a long.
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