One of my older posts on Stackoverflow listed some of what I consider to be flaws of C# and/or the .NET runtime. A recent reply to my post posed a good question about one of those flaws, which was that sealed classes should be allowed as type constraints. That seems like a sensible restriction for C# at first, but there are legitimate programs that it disallows.
I figured others would have run into this problem at some point, but a quick Google search didn't turn up much, so I will document the actual problem with this rule. Consider the following interface:
interface IFoo<T> { void Bar<U>(U bar) where U : T; }
The important part to notice here is the type constraint on the method, U : T. This means whatever T we specify for IFoo<T>, we should be able to list as a type constraint on the method Bar. Of course, if T is a sealed class, we cannot do this:
class Foo : IFoo<string> { public void Bar<U>(U bar) where U : string //ERROR: string is sealed! { } }
In this case, there's a workaround by allowing the compiler to infer the constraint by making the method private and visible only when coerced as an interface:
class Foo : IFoo<string> { void IFoo<string>.Bar<U>(U bar) { } }
But this means that you cannot call Bar on a Foo, you need to first cast it to an IFoo<string>, a completely unnecessary step.
In principle, every type constraint that the compiler can infer implicitly, we should be able to specify explicitly. This is clearly not the case here, and there is no reason for it. It's a purely an aesthetic restriction, not a correctness restriction, that the C# compiler devs took extra effort to implement.
And that is why we should allow sealed classes as type constraints.
Comments
Loss of type information isn't always a problem obviously, but sometimes it is. Consider you have a static class:
public static class State<T>
{
public static T value;
}
Your Bar without U can no longer access a different state for each U via State<U>.value, it can only access State<T>.value.
This is again a trivial example, but the point is simply to illustrate that throwing away type information has real consequences. If the only reason to forbid sealed classes as type constraints are aesthetic and not for correctness, then that's not a good enough reason.
The point is that you don't know the types U and T when you define IFoo<T>. Any implementation conforming to that interface should be allowed without jumping through hoops, including using a sealed class for T.
I do a lot of very high-level abstract programming, so I've run into this limitation a few times. It's probably not common in typical programs. A general purpose programming language should support both simple and advanced uses though.
This is just another example of C# making aesthetic restrictions instead of sticking to correctness restrictions. Another example that is widely considered to be a mistake, is not being able to specify Delegate or Enum as type constraints, ie. where T : Delegate. The sealed class type constraint is the same type of problem, just not widely regarded as a problem.
The other day I ran into that bizarre restriction that you can't use "where D:Delegate". It's hard to imagine why they added that restriction when .NET itself supports the constraint.
Just FYI, the CLR supports type constraints on Enum and Delegate, it's only C# that does not, so it's strictly a C# limitation. That's why my Sasa class library has an IL rewriter that adds these type constraints to compiled C# class libraries.
Consider a more strongly typed version of Enum.GetValues; given a call to a hypothetical Enum.GetValuesOfType<Enum> method there's no reasonable behavior other than throwing an exception (which is of course what Enum.GetValues(typeof(Enum)) does today). However, a naive user is likely to expect all calls to the strongly typed version to succeed without throwing exceptions, and the error message ("Type provided must be an Enum") might be a bit mystifying since most people don't stop to consider whether System.Enum is itself an Enum.
Of course you're right that the CLR supports these types in constraints (or rather, doesn't explicitly disallow them), and there are plenty of cases where they are useful. I wouldn't be upset to see them added to the language (and in fact, in the System.Enum and System.ValueType cases, you can effectively create a strict subtype constraint by additionally constraining the type to be a value type). I just think that the C# team's decision to restrict them makes sense in the context of their goals (e.g. eliminating code with corner cases that are almost certainly broken).
By comparison, I don't see any real downsides to allowing sealed types in constraints.
Your other points involving casting and reflection already violate any type properties your code may have. I wouldn't disallow a useful typing property X simply because typing properties Y and Z were violated elsewhere in the language.
Finally, I don't think there's an expectation that typed code generates no runtime errors on the CLR, merely certain types of errors are ruled out. For instance, you can easily generate implicit errors from within static class constructors, and this is very, very surprising to developers. We just have to live with it; runtime errors on the CLR are simply too pervasive to simply ignore just because of alleged type safety.