I have a situation where I'd like the behaviour of the compiler explained. Given a little code:
interface IFoo<T>
{
T Get();
}
class FooGetter : IFoo<int>
{
public int Get()
{
return 42;
}
}
The following compiles and runs:
static class FooGetterGetter
{
public static IFoo<T> Get<T>()
{
return (IFoo<T>)new FooGetter();
}
}
If we make a change to the signature of the Foo
class and add the sealed
keyword:
sealed class FooGetter : IFoo<int> // etc
Then I get a compiler error on the following line:
return (IFoo<T>)new FooGetter();
Of:
Cannot convert type 'MyNamespace.FooGetter' to 'MyNamespace.IFoo<T>'
Can someone explain what is happening here with regards to the sealed
keyword? This is C# 4 against a .NET 4 project in Visual Studio 2010.
Update: interestingly enough I stumbled on that part of the behaviour when I was wondering why the following code fixes it when sealed
is applied:
return (IFoo<T>)(IFoo<int>)new FooGetter();
Update: just for clarification, it all runs fine when the type of T
requested is the same as the type of T
used by the concrete type. If the types differ, the cast fails at runtime with something like:
Unable to cast object of type 'MyNamespace.StringFoo' to type 'MyNamespace.IFoo`1[System.Int32]'
In the above example, StringFoo : IFoo<string>
and the caller asks to get an int
.
Because FooGetter
is an explicit implementation of IFoo<int>
instead of implementing IFoo<T>
generically. Since it is sealed, the compiler knows there's no way to cast it to a generic IFoo<T>
if T
is anything other than an int
. If it were not sealed, the compiler would allow it to compile and throw an exception at runtime if T
was not an int
.
If you try to use it with anything other than an int
(e.g. FooGetterGetter.Get<double>();
) you get an exception:
Unable to cast object of type 'MyNamespace.FooGetter' to type 'MyNamespace.IFoo`1[System.Double]'.
What I'm not sure of is why the compiler does not generate an error for the non-sealed version. How could your sub-class FooGetter
such that new FooGetter()
give you anything that implements IFoo<{something_other_than_int}>
?
Update:
Per Dan Bryant and Andras Zoltan there are methods to return a derived class from a constructor (or possibly more precisely for the compiler to return a different type by analyzing attributes). So technically this is feasible if the class is not sealed.
When a class in unsealed any derived class could implement IFoo<T>
:
class MyClass : FooGetter, IFoo<double> { }
When FooGetter
is marked as sealed, the compiler knows that it cannot be possible for any additional implementations of IFoo<T>
other than IFoo<int>
could exist for FooGetter
.
This is good behavior, it allows you to catch problems with your code at compile time instead of at runtime.
The reason that (IFoo<T>)(IFoo<int>)new FooGetter();
works is because you are now representing your sealed class as IFoo<int>
which could be implemented by anything. It is also a nice work around as you are not accidentally, but purposefully overriding the compiler check.
Just to add to the existing answers: This really has nothing to do with the generics used.
Consider this simpler example:
interface ISomething
{
}
class OtherThing
{
}
Then saying (inside a method):
OtherThing ot = XXX;
ISomething st = (ISomething)ot;
works just fine. The compiler does not know if an OtherThing
might be an ISomething
, so it believes us when we say it will succeed. However, if we change OtherThing
to a sealed type (namely sealed class OtherThing { }
or struct OtherThing { }
), then the cast is no longer allowed. The compiler knows it can't go well (except if ot
were to be null
, but the rules of C# still disallow the cast from a sealed type to an interface not implemented by that sealed type).
Regarding the update of the question: Writing (IFoo<T>)(IFoo<int>)new FooGetter()
is not much different than writing (IFoo<T>)(object)new FooGetter()
. You can "make allowed" any cast (with generics or without) by going through some intermediate type that is certainly/possibly an ancestor to both of the types you want to convert between. It's very much similar to this pattern:
void MyMethod<T>(T t) // no "where" constraints on T
{
if (typeof(T) = typeof(GreatType))
{
var tConverted = (GreatType)(object)t;
// ... use tConverted here
}
// ... other stuff
}
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