27

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.

Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
  • possible duplicate of http://stackoverflow.com/questions/1420581/inheritance-on-a-constrained-generic-type-parameter – Glory Raj Dec 22 '11 at 16:02
  • I'm looking at the language specificiation, and I see "Since type parameters are not inherited, constraints are never inherited either" (section 4.4.4). Which begs the question "why aren't type parameters inherited?" Which may lead into a deeper discussion of open types and closed types and things like that, also in the specification. From a practical standpoint, having to restate the constraints can be beneficial the farther you get away from the base type, particularly if it is in a closed-source assembly. – Anthony Pegram Dec 22 '11 at 16:17
  • 1
    @AnthonyPegram: Type parameters are not inherited (1) because they are not members, and (2) because that doesn't make any sense. You inherit from a *type*. An unconstructed generic type is not a type, so you cannot inherit from it. To inherit from a generic type, you have to construct the type, which means *substituting something for the type parameter*. A type parameter is not a *thing* that you get from your base class! It is a *hole* in the base class that you have to fill in before it is something you can inherit *from*. – Eric Lippert Dec 22 '11 at 16:28
  • @EricLippert I believe what is tripping the OP up is that while the constraint is not *de jure* inherited (and so code that relies on the constraint to compile, um, won't) the base type's constraint is nonetheless quite relevant to users of the derived type, since the derived type's type parameter must still satisfy the base type's constraint. – dlev Dec 22 '11 at 16:31
  • There are a lot of possible scenarios/combinations when doing inheritance with generic classes, especially when a class has multiple generic parameters. A thorough answer might provide a bunch of examples where the implicit nature of this behavior could cause confusion. I think a very applicable [quote from Eric Lippert](http://blogs.msdn.com/b/ericlippert/archive/2009/06/29/the-void-is-invariant.aspx) is the following: one of the design principles of C# is "if you say something wrong then we tell you rather than trying to guess what you meant" – Dr. Wily's Apprentice Dec 22 '11 at 16:41
  • @pratapchandra: Where did you find similarities with the other supposedly duplicate question is a mystery to me... – Robert Koritnik Dec 22 '11 at 17:02
  • BTW your code won't compile cause items are private. – Lukasz Madon Dec 22 '11 at 19:17
  • @lukas: Oh thanks for that. Typo. Corrected it to `protected` the way they should be in the first place. – Robert Koritnik Dec 22 '11 at 19:32

4 Answers4

43

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?

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Maybe I'm not understanding correctly, but my first thought would be that the base class is expecting T to be of a certain type specified in the constraint, so when inherited classes call base methods, they may not function correctly? does that make sense? – Erix Dec 22 '11 at 16:24
  • I see your point Eric. But I also see the Roberts point; if the concrete type is by definition constrained by the base type constraint is seems redundant to have to explicitly define this constraint on the concrete class. – Myles McDonnell Dec 22 '11 at 16:24
  • 1
    In an inheritance hierarchy, if you just propagate the generic and the constraint, you have to redefine the constraint for each level of inheritance. This leads to duplicate code. Couldn't the constraint be forced automatically for each level of inheritance instead of redefining? Or could Visual Studio add the constraint for you (with the famous Ctrl + .)? – Wouter de Kort Dec 22 '11 at 16:30
  • @Erix: I'm not following you, sorry. It is precisely because base class methods would not function correctly that the type argument is required to meet the constraints on the type parameter. – Eric Lippert Dec 22 '11 at 16:31
  • @wouterDeKort: OK, so suppose we have B where T : constraint1 where U : constraint2, and then D : B. Are you proposing that V "inherit" both constraints? – Eric Lippert Dec 22 '11 at 16:33
  • @EricLippert I see. I thought the OP was complaining that the derived class could be instantiated with ANY type parameter (even one which does not adhere to the base class contstraint) – Erix Dec 22 '11 at 16:34
  • @Erix: No, I think he's complaining that there is no inference engine that automatically works out what constraints must be placed on declarations of type parameters in order for them to be used correctly as type arguments in the construction of a base type. This business of "inheritance" is a red herring; inheritance isn't the relevant mechanism at all. – Eric Lippert Dec 22 '11 at 16:39
  • Check my additional explanation and reasoning why class level generic type constraints should be inherited. Does that make sense? – Robert Koritnik Dec 22 '11 at 16:51
  • Exactly Eric what you commented to @wouterDeKort. If you define `B : A` then yes. `T` should satisfy both constraints. If that's not possible, well then your `B` class is invalid. I understand that sometimes constraints may clash with each other (`class` and `struct`) but they're constraints for a reason. – Robert Koritnik Dec 22 '11 at 16:57
  • 1
    @EricLippert In the case of public class B where T : IConstraint1 where U : IConstraint2 { } public class D : B the compiler gives an error if you don't specify where V : IConstraint1, IConstraint2 If the compiler can already signal it as an error and instructs you what to do, then why do I have to insert that statement? In the case of D : B I understand why that would lead to a lot of 'compiler work', but not in the case of D : B? Or am I missing something? – Wouter de Kort Dec 22 '11 at 16:59
  • 1
    @WouterdeKort: A reasonable question. There are programming languages like that; JScript will automatically insert missing semicolons (and sometimes does so incorrectly). JScript will silently fail when you try to write to a read-only property rather than reporting an error! In short, **JScript will attempt to hide your bugs from you**. That is not a basic design principle of C#; C# is not a "make a guess on behalf of the user and hope for the best" language. C# is a "report the error and let the developer fix it as the developer sees fit" language. – Eric Lippert Dec 22 '11 at 17:07
  • @EricLippert Thanks for your explanation. "Report to developer and let him fix" is indeed a better way to make sure that you don't introduce subtle bugs by 'compiler guessing' (and tomorrow I'm going to tell all my colleagues that Eric Lippert knew my name for a few minutes!!! ;)) – Wouter de Kort Dec 22 '11 at 17:25
  • @EricLippert: I wouldn't agree with you saying *make a guess on behalf of the user and hope for the best* because in case of `B : A` there's simply no guessing. Type constraints on B should include **both** of the A constraints. Simple and straightforward as that. I don't see where these are being guesses? In other occasions where these definitions can be vague and uncertain **it's definitely better to report an error and leave it to developer to decide**. – Robert Koritnik Dec 22 '11 at 20:35
  • 2
    @RobertKoritnik: Of course there is a guess. You are proposing that the compiler *guess that the developer intended there to be a constraint on T in B*. What if the developer does *not* intend that users of B be restricted in any way? The developer has a choice of ways to fix the problem: put the constraint on B, remove the constraint on A, or have B not derive from A. You expect the compiler to guess that the correct solution is the first one. Now, suppose for the sake of argument that 99% of the time that *is* the right solution. **How many wrong programs is 1%?** – Eric Lippert Dec 22 '11 at 20:42
  • @EricLippert: Let me add something I left ou of the last comment: **...leave it to developer to decide**. So I suppose it's ok for compiler to be consistent and **always work the same way**. Thanks for your UPDATE. It give a very good insight into this issue. – Robert Koritnik Dec 22 '11 at 20:46
  • @EricLippert: But I hope we all agree that examples that you presented are not really common because they would be like completely unmaintainable and bad written code. Even though it may be valid it's hard to actually deduct inheritance and type parameters and their constraints. Thanks and have fun. – Robert Koritnik Dec 22 '11 at 21:17
  • 1
    @RobertKortinik: Though I agree that such examples are possibly unlikely (1) you might be surprised at how hard some people push on the type system in realistic code, and (2) regardless of whether the type topologies are *likely* or not, they are *legal* and therefore the compiler has to be able to deal with them. – Eric Lippert Dec 22 '11 at 22:21
0

Below is a scenario where the implicit nature of this behavior causes different behavior than expected:

I recognize that this scenario may seem extravagant in the amount of setup, but this is just one example of where this behavior might cause a problem. Software applications can be complicated, so even though this scenario may seem complicated, I wouldn't say that this can't happen.

In this example there is an Operator class that implements two similar interfaces: IMonitor and IProcessor. Both have a start method and an IsStarted property, but the behavior for each interface within the Operator class is separate. I.e. there is a _MonitorStarted variable and a _ProcessorStarted variable within the Operator class.

MyClass<T> derives from ClassBase<T>. ClassBase has a type constraint on T that it must implement the IProcessor interface, and according to the suggested behavior MyClass inherits that type constraint.

MyClass<T> has a Check method, which is built with the assumption that it can get the value of the IProcessor.IsStarted property from the inner IProcessor object.

Suppose someone changes the implementation of ClassBase to remove the type constraint of IProcessor on the generic parameter T and replace it with a type contraint of IMonitor. This code will silently work, but will produce different behavior. The reason is because the Check method in MyClass<T> is now calling the IMonitor.IsStarted property instead of the IProcessor.IsStarted property, even though the code for MyClass<T> hasn't changed at all.

public interface IMonitor
{
    void Start();

    bool IsStarted { get; }
}

public interface IProcessor
{
    void Start();

    bool IsStarted { get; }
}

public class Operator : IMonitor, IProcessor
{
    #region IMonitor Members

    bool _MonitorStarted;

    void IMonitor.Start()
    {
        Console.WriteLine("IMonitor.Start");
        _MonitorStarted = true;
    }

    bool IMonitor.IsStarted
    {
        get { return _MonitorStarted; }
    }

    #endregion

    #region IProcessor Members

    bool _ProcessorStarted;

    void IProcessor.Start()
    {
        Console.WriteLine("IProcessor.Start");
        _ProcessorStarted = true;
    }

    bool IProcessor.IsStarted
    {
        get { return _ProcessorStarted; }
    }

    #endregion
}

public class ClassBase<T>
    where T : IProcessor
{
    protected T Inner { get; private set; }

    public ClassBase(T inner)
    {
        this.Inner = inner;
    }

    public void Start()
    {
        this.Inner.Start();
    }
}

public class MyClass<T> : ClassBase<T>
    //where T : IProcessor
{
    public MyClass(T inner) : base(inner) { }

    public bool Check()
    {
        // this code was written assuming that it is calling IProcessor.IsStarted
        return this.Inner.IsStarted;
    }
}

public static class Extensions
{
    public static void StartMonitoring(this IMonitor monitor)
    {
        monitor.Start();
    }

    public static void StartProcessing(this IProcessor processor)
    {
        processor.Start();
    }

}

class Program
{
    static void Main(string[] args)
    {
        var @operator = new Operator();

        @operator.StartMonitoring();

        var myClass = new MyClass<Operator>(@operator);

        var result = myClass.Check();

        // the value of result will be false if the type constraint on T in ClassBase<T> is where T : IProcessor
        // the value of result will be true if the type constraint on T in ClassBase<T> is where T : IMonitor
    }
}
Dr. Wily's Apprentice
  • 10,212
  • 1
  • 25
  • 27
-1

I think you're confused becuase you're declaring you derived class with TItem as well.

If you think about it if you were using Q instead so.

public class MyClass<Q> : BaseClass<Q>
{
 ...
}

Then how is it to be determined that Q is of the type item?

You need to add the constraint to the derived classes Generic Type as well so

public class MyClass<Q> : BaseClass<Q> were Q : Item { ... } 
msarchet
  • 15,104
  • 2
  • 43
  • 66
  • 2
    I don't see how changing the name of the type changes anything? The contraint is implied by the code, but the compiler does not honour this. Why is the point of the question. – Myles McDonnell Dec 22 '11 at 16:10
  • 1
    @MylesMcDonnell: Of course the compiler honours the constraint. It honours it *by producing an error when the constraint is violated*. The constraint is violated by the declaration of the derived class. – Eric Lippert Dec 22 '11 at 16:23
  • Yes, sorry, badly written. I should have said the compiler does not inherit the type constraint. I think the question is, if the concrete type is by definition constrained by the base type constraint is seems redundant to have to explicitly define this constraint on the concrete class again, so why is that the case? – Myles McDonnell Dec 22 '11 at 16:35
-2

Because the ClassBase has a constraint on his template (should by typeof Item), you have to add this constraint to MyClass too. If you don't do this, you could create a new instance of MyClass, where the template isn't a type of Item. When creating the base class, it will fail.

[edit] Hmm now a re-read your question, and I see your code does compile? Ok.

Well, im MyClass you don't know the basetype of this.items, so you can't call the Check method. this.items is of the type IList, and in your class, TItem isn't specified, thats why the class doesn't understand the Check method.

Let me counter your question, why don't you want to add the constraint to your MyClass class? Given any other class type as template to this class, would result in an error. Why not prevent this errors by adding a constraint so it will fail compiletime.

Michiel van Vaardegem
  • 2,260
  • 20
  • 35