Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why aren't generic type constraints inheritable/hierarchically enforced

Item class

public class Item {     public bool Check(int value) { ... } } 

Base abstract class with generic type constraint

public abstract class ClassBase<TItem>     where TItem : Item {     protected IList<TItem> items;      public ClassBase(IEnumerable<TItem> items)     {         this.items = items.ToList();     }          public abstract bool CheckAll(int value); } 

Inherited class without constraints

public class MyClass<TItem> : ClassBase<TItem> {     public override bool CheckAll(int value)     {         bool result = true;         foreach(TItem item in this.items)         {             if (!item.Check(value)) // this doesn't work             {                 result = false;                 break;             }         }         return result;     } } 

I would like to know why aren't generic type constraints inheritable? Because if my inherited class inherits from base class and passes over its generic type which has a constraint on the base class it automatically means that generic type in inherited class should have the same constraint without explicitly defining it. Shouldn't it?

Am I doing something wrong, understanding it wrong or is it really that generic type constraint aren't inheritable? If the latter is true, why in the world is that?

A bit of additional explanation

Why do I think that generic type constraints defined on a class should be inherited or enforced on child classes? Let me give you some additional code to make it bit less obvious.

Suppose that we have all three classes as per above. Then we also have this class:

public class DanteItem {     public string ConvertHellLevel(int value) { ... } } 

As we can see this class does not inherit from Item so it can't be used as a concrete class as ClassBase<DanteItem> (forget the fact that ClassBase is abstract for now. It could as well be a regular class). Since MyClass doesn't define any constraints for its generic type it seems perfectly valid to have MyClass<DanteItem>...

But. This is why I think generic type constraints should be inherited/enforced on inherited classes just as with member generic type constraints because if we look at definition of MyClass it says:

MyClass<T> : ClassBase<T> 

When T is DanteItem we can see that it automatically can't be used with MyClass because it's inherited from ClassBase<T> and DanteItem doesn't fulfill its generic type constraint. I could say that **generic type on MyClass depends on ClassBase generic type constraints because otherwise MyClass could be instantiated with any type. But we know it can't be.

It would be of course different when I would have MyClass defined as:

public class MyClass<T> : ClassBase<Item> 

in this case T doesn't have anything to to with base class' generic type so it's independent from it.

This is all a bit long explanation/reasoning. I could simply sum it up by:

If we don't provide generic type constraint on MyClass it implicitly implies that we can instantiate MyClass with any concrete type. But we know that's not possible, since MyClass is inherited from ClassBase and that one has a generic type constraint.

I hope this makes much more sense now.

like image 447
Robert Koritnik Avatar asked Dec 22 '11 15:12

Robert Koritnik


People also ask

Can generic classes be constrained?

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. The code below constrains a class to an interface.

What are generic constraints in c#?

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.

What is the purpose of the class constraints on a type parameter?

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.

Can a generic class have multiple constraints?

There can be more than one constraint associated with a type parameter. When this is the case, use a comma-separated list of constraints. In this list, the first constraint must be class or struct or the base class.


1 Answers

ANOTHER UPDATE:

This question was the subject of my blog in July 2013. Thanks for the great question!

UPDATE:

I've given this some more thought and I think the problem is that you don't want inheritance at all. Rather, what you want is for all constraints that must be placed on a type parameter in order for that type parameter to be used as a type argument in another type to be automatically deduced and invisibly added to the declaration of the type parameter. Yes?

Some simplified examples:

class B<T> where T:C {} class D<U> : B<U> {} 

U is a type parameter that is used in a context where it must be C. Therefore in your opinion the compiler should deduce that and automatically put a constraint of C on U.

What about this?

class B<T, U> where T : X where U : Y {} class D<V> : B<V, V> {} 

Now V is a type parameter used in a context where it must be both X and Y. Therefore in your opinion the compiler should deduce that and automatically put a constraint of X and Y on V. Yes?

What about this?

class B<T> where T : C<T> {} class C<U> : B<D<U>> where U : IY<C<U>> {} class D<V> : C<B<V>> where V : IZ<V> {} 

I just made that up, but I assure you that it is a perfectly legal type hierarchy. Please describe a clear and consistent rule that does not go into infinite loops for determining what all the constraints are on T, U and V. Don't forget to handle the cases where type parameters are known to be reference types and the interface constraints have covariance or contravariance annotations! Also, the algorithm must have the property that it gives exactly the same results no matter what order B, C and D appear in source code.

If inference of constraints is the feature you want then the compiler has to be able to handle cases like this and give clear error messages when it cannot.

What is so special about base types? Why not actually implement the feature all the way?

class B<T> where T : X {} class D<V> { B<V> bv; } 

V is a type parameter used in a context where it must be convertible to X; therefore the compiler should deduce this fact and put a constraint of X on V. Yes? Or no?

Why are fields special? What about this:

class B<T> { static public void M<U>(ref U u) where U : T {} } class D<V> : B<int> { static V v; static public void Q() { M(ref v); } } 

V is a type parameter used in a context where it can only be int. Therefore the C# compiler should deduce this fact and automatically put a constraint of int on V.

Yes? No?

You see where this is going? Where does it stop? In order to implement your desired feature properly the compiler must do whole-program analysis.

The compiler does not do this level of analysis because that is putting the cart before the horse. When you construct a generic, you are required to prove to the compiler that you've satisfied the constraint. It's not the compiler's job to figure out what you meant to say and work out what further set of constraints satisfy the original constraint.

For similar reasons, the compiler also does not attempt to automatically infer variance annotations in interfaces on your behalf. See my article on that subject for details.

http://blogs.msdn.com/b/ericlippert/archive/2007/10/29/covariance-and-contravariance-in-c-part-seven-why-do-we-need-a-syntax-at-all.aspx


Original answer:

I would like to know why aren't generic type constraints inheritable?

Only members are inherited. A constraint is not a member.

if my inherited class inherits from base class and passes over its generic type which has a constraint on the base class it automatically means that generic type in inherited class should have the same constraint without explicitly defining it. Shouldn't it?

You're just asserting how something should be, without providing any explanation of why it should be that way. Explain to us why you believe that the world should be that way; what are the benefits and what are the drawbacks and what are the costs?

Am I doing something wrong, understanding it wrong or is it really that generic type constraint aren't inheritable?

Generic constraints are not inherited.

If the latter is true, why in the world is that?

Features are "not implemented" by default. We don't have to provide a reason why a feature is not implemented! Every feature is not implemented until someone spends the money to implement it.

Now, I hasten to note that generic type constraints are inherited on methods. Methods are members, members are inherited, and the constraint is a part of the method (though not part of its signature). So the constraint comes along with the method when it is inherited. When you say:

class B<T>  {     public virtual void M<U>() where U : T {} }  class D<V> : B<IEnumerable<V>> {     public override void M<U>() {} } 

Then D<V>.M<U> inherits the constraint and substitutes IEnumerable<V> for T; thus the constraint is that U must be convertible to IEnumerable<V>. Note that C# does not allow you to restate the constraint. This is in my opinion a misfeature; I would like to be able to restate the constraint for clarity.

But D does not inherit any kind of constraint on T from B; I don't understand how it possibly could. M is a member of B, and is inherited by D along with its constraint. But T is not a member of B in the first place, so what is there to inherit?

I'm really not understanding at all what feature it is that you want here. Can you explain with more details?

like image 174
Eric Lippert Avatar answered Sep 22 '22 11:09

Eric Lippert