3

I know this theme was discussed many times: What are the good reasons to wish that .NET generics could inherit one of the generic parameter types?

But now I think that its really needed...

I will try to explain with simple example why do we need the inheritance from generic types.

The motivation in short: is to make much easier the development of something that I call Compile Time Order Enforcement, which is very popular in ORM Frameworks.

Assume that we are building Database Framework.

Here is the example how to build the transaction with such a framework:

public ITransaction BuildTransaction(ITransactionBuilder builder)
{
    /* Prepare the transaction which will update specific columns in 2 rows of table1, and one row in table2 */
    ITransaction transaction = builder
        .UpdateTable("table1") 
            .Row(12)
            .Column("c1", 128)
            .Column("c2", 256)
            .Row(45)
            .Column("c2", 512) 
        .UpdateTable("table2")
            .Row(33)
            .Column("c3", "String")
        .GetTransaction();

    return transaction;
}

Since every line returns some interface, we would like to return them such way, that developer can't make a mistake in operation order, and valid usage will be enforced at compile time, which also makes simplier the implementation and usage of TransactionBuilder, because developer just won't be able to make mistakes like:

    { 
        ITransaction transaction = builder
            .UpdateTable("table1") 
            .UpdateTable("table2")  /*INVALID ORDER: Table can't come after Table, because at least one Row should be set for previous Table */
    }
    // OR
    {
        ITransaction transaction = builder
            .UpdateTable("table1") 
                .Row(12)
                .Row(45) /* INVALID ORDER: Row can't come after Row, because at least one column should be set for previous row */
    }

Now let's look on the ITransactionBuilder interface today, WITHOUT inheritance from generic, which will enforce the desired order at compile time:

    interface ITransactionBuilder
    {
        IRowBuilder UpdateTable(string tableName);
    }
    interface IRowBuilder
    {
        IFirstColumnBuilder Row(long rowid);
    }
    interface IFirstColumnBuilder
    {
        INextColumnBuilder Column(string columnName, Object columnValue);
    }
    interface INextColumnBuilder : ITransactionBuilder, IRowBuilder, ITransactionHolder
    {
        INextColumnBuilder Column(string columnName, Object columnValue);
    }
    interface ITransactionHolder
    {
        ITransaction GetTransaction();
    }
    interface ITransaction
    {
        void Execute();
    }

As you can see we have 2 interfaces for Column builder "IFirstColumnBuilder" and "INextColumnBuilder" which actualy are not neccessary, and remember that this is very simple example of compile time state machine, while in more complex problem, the number of unneccessary interfaces will grow dramatically.

Now let's assume we can inherit from generics and prepared such interfaces

interface Join<T1, T2> : T1, T2 {}
interface Join<T1, T2, T3> : T1, T2, T3 {}
interface Join<T1, T2, T3, T4> : T1, T2, T3, T4 {} //we use only this one in example

Then, we can rewrite our interfaces to more intuitive style and with single column builder, and without affecting the order

interface ITransactionBuilder
{
    IRowBuilder UpdateTable(string tableName);
}
interface IRowBuilder
{
    IColumnBuilder Row(long rowid);
}
interface IColumnBuilder
{
    Join<IColumnBuilder, IRowBuilder, ITransactionBuilder, ITransactionHolder> Column(string columnName, Object columnValue);
}
interface ITransactionHolder
{
    ITransaction GetTransaction();
}
interface ITransaction
{
    void Execute();
}

So we used Join<...> to combine existing interfaces(or the "next steps"), which is very usefull in state machine development.

Ofcourse, this specific problem may be solved by adding possibility in C# for "joining" interfaces, but it's clear that if it was possible to inherit from generics, the problem was not exist at all, as well as it's clear that compile time order enforcement is very usefull thing.

BTW. For syntax like

    interface IInterface<T> : T {}

There are no any "what if" cases except inheritance loop, which may be detected at compile time.

I think that AT LEAST for interfaces this feature is 100% needed

Regards

Community
  • 1
  • 1
jenkas
  • 872
  • 14
  • 16
  • What exactly is your question? – nvoigt Oct 02 '14 at 09:19
  • 3
    I understand your point. No bad feelings, but, I think this post is off-topic on SO. According to the site's terms of use, you should ask a salvageable question that can have a working solution provided. Your post is more like a feature request to the CLR dev team, and I doubt you will get a working solution in the scope of this site and the lifespan of the very post you wrote. – Ivaylo Slavov Oct 02 '14 at 09:21
  • Actually I just want to hear what other ppl think about my thoughts? I am new in c#, about 4 weeks, and came from c++ world. – jenkas Oct 02 '14 at 10:33
  • I'm not sure cutting down on the number of interfaces required for large fluent interfaces is actually a good reason to add such a complex (in implementation terms) feature. If you really want to do this, use some metaprogramming approach like T4 to generate fluent interfaces from a DSL. – Ben Aaronson Oct 03 '14 at 08:55

3 Answers3

2

You're hugely underestimating the complexity of this feature. Just off the top of my head, what if I write

public class Fooer
{
    public void Foo();
}

public class Generic<T> : T
{
    public void Foo();
}

var genericFooer = new Generic<Fooer>();

Now I have an issue with conflicting member signatures.

Large numbers of interfaces to support a fluent api like you describe is a tiny fraction of use cases, and as I mentioned in my comment, relatively easily supported by metaprogramming. I'm not sure if anybody has created a T4 template to translate a DSL into interface declarations, but it is certainly doable. I don't think interfaces were ever created with representing every combination of transitions in a finite state machine in mind. It's certainly not worth this change which I'm sure would be extremely complex to implement and have all sorts of odd cases like this one.

Update: Another thing to keep in mind is that this would have a very large potential to break existing code that uses Reflection. This would include things like Entity Framework, IoC containers, Automapper, probably tons more. I'd be surprised if any of those examples didn't have new use cases introduced by a change like this which would cause bugs or unexpected behaviour.

Of course this is true to some extent for any language change, but one this significant is likely to have a large impact. Again, that's a big cost that has to be balanced against a relatively small benefit.

Update 2: If we restrict it to interfaces only, we still have this problem:

public interface IFooer
{
    public void Foo();
}

public interface IGeneric<T> : T
{
    public int Foo();
}

These are incompatible because you can't have two methods which differ only by return type, even on an interface.

Ben Aaronson
  • 6,955
  • 2
  • 23
  • 38
  • I agree this feature problematic for classes, and may be it should be "opened" only for interfaces, so we can write interface I1: T1 where T: interface. if all types are interfaces, what is the problem? – jenkas Oct 03 '14 at 10:36
  • Well, then see Ivaylo Slavov's answer below, which addresses the problems of implementing the feature if you limit it only to interfaces. – Ben Aaronson Oct 03 '14 at 10:49
  • Yeah, may be you are right and for easy fluent API, it will be much easy to add only support declaration of types from combined interfaces...which I hope is not so complex – jenkas Oct 04 '14 at 20:59
  • I anwsered to the post with my conclusions and how did I solve it. – jenkas Oct 07 '14 at 21:33
1

I reviewed the comments and here is my conclusions (merged with my opinion).

  1. It's not as simple as I thought to allow inheritance from generics, even only for interfaces.
  2. The solution for Fluent API is a must(may be not through generics inheritance, but through new syntax for "joining interfaces" suggested later), since it is very easy to use, and it allows to deny many developer's errors at compile time - it's popularity will only grow.

I beleive 1 and 2 are more than possible, but currently I decided to print my best solution to the problem I described

So, I changed the interfaces to this ( the "new syntax" is commented )

    public interface ITransactionBuilder
    {
        IRowBuilder UpdateTable(string tableName);
    }
    public interface IRowBuilder
    {
        IColumnBuilder Row(long rowid);
    }

    public interface INextColumnBuilder : IColumnBuilder, IRowBuilder, ITransactionBuilder, ITransactionHolder { }
    public interface IColumnBuilder
    {
        INextColumnBuilder Column(string columnName, Object columnValue);
        //I still want to write something like this here!!! But currently we may only to comment it :(
        //<IColumnBuilder, IRowBuilder, ITransactionBuilder, ITransactionHolder> Column(string columnName, Object columnValue)
    }
    public interface ITransactionHolder
    {
        ITransaction GetTransaction();
    }
    public interface ITransaction
    {
        //Execute func declaration
    }

So, the overhead is not so big. Comparing to the "join possibility", we need only to declare dummy interfaces for return types, and later inherit from them in implementator, so let's look on the TransactionBuilder (the implementator)...

    class TransactionBuilder : INextColumnBuilder
    //I want to write this: class TransactionBuilder : IColumnBuilder, IRowBuilder, ITransactionBuilder, ITransactionHolder
    //But then we will fail on "return this;" from Column() func, because class TransactionBuilder not inherits from INextColumnBuilder, and again interface joining should solve it...
    {
        public virtual IRowBuilder UpdateTable(string tableName)
        {
            m_currentTable = new TableHolder(tableName);
            m_commands.Add(new UpdateCommand(m_currentTable));
            return this;
        }
        public virtual IColumnBuilder Row(long rowid)
        {
            m_currentRow = new RowHolder(rowid);
            m_currentTable.AddRow(m_currentRow);
            return this;
        }
        public virtual INextColumnBuilder Column(string columnName, Object columnValue)
        //And the dream is: <IColumnBuilder, IRowBuilder, ITransactionBuilder, ITransactionHolder> Column(string columnName, Object columnValue)
        {
            m_currentRow.AddColumn(columnName, columnValue);
            return this;
        }
        public virtual ITransaction GetTransaction()
        {
            return new Transaction(m_commands);
        }

        private ICollection<ICommand> m_commands = new LinkedList<ICommand>();
        private TableHolder m_currentTable;
        private RowHolder m_currentRow;
    }

As you see I have only one class who implements all the methods, and I really do not see any reason to implement many classes for every interface(so, Ivaylo tried to solve not existing for me problem ;) ). I think it's enough to have state machine on interface level, and the code may be much simplier and compact.

...Other classes used in code, just to complete the picture...

    public interface ICommand 
    {
        //Apply func declaration
    }
    public class UpdateCommand : ICommand 
    {
        public UpdateCommand(TableHolder table) { /* ... */ }
        //Apply func implementation
    }
    public class TableHolder
    {
        public TableHolder(string tableName) { /* ... */ }
        public void AddRow(RowHolder row) { /* ... */ }
    }
    public class RowHolder
    {
        public RowHolder(long rowid) { /* ... */ }
        public void AddColumn(string columnName, Object columnValue) { /* ... */ }
    }
    public class Transaction : ITransaction
    {
        public Transaction(IEnumerable<ICommand> commands) { /* ... */ }
        //Execute func implementation
    }

Hope somebody from .NET team in Microsoft will see it and like it...

jenkas
  • 872
  • 14
  • 16
  • 1
    I'd honestly be surprised if there was any real-world DSL where the number of combining interfaces like `INextColumnBuilder` is a significant part of the overall fluent api code in terms of size or complexity. I understand how in theory the number could be very large, but my estimation is that it almost always won't be. And in the situations where it is very large, the overall code would probably be very complicated anyway – Ben Aaronson Oct 07 '14 at 22:38
0

Update

I have revisited the question, your last comments and I came up with an idea, based on my interpretation of your issue.

I agree with your last statement, indeed there would be no difference between the Join<...> interface and a hypothetical IOddAndPrimeCounter : IOddCounter, IPrimeCounter interface. What I meant with point 2 is that you would be unable to distinguish whether to call the resulting join interface as T1 or T2.

I would suggest a slightly different approach I am myself using in such scenarios. I would preserve the two interfaces IFirstColumnBuilder and INextColumnBuilder, but modify slightly the latter:

interface INextColumnBuilder : 
    IFirstColumnBuilder, ITransactionBuilder, IRowBuilder, ITransactionHolder {}

Then I will go for one single implementation of the IFirstColumnBuilder and INextColumnBuilder that would look like this:

public class ColumnBulder : INextColumnBuilder // IFirstColumnBuilder is already implemented
{
}

A typical implementation of a IRowBulder will then look like this:

public class RowBulder : IRowBulder
{
    public IFirstColumnBuilder Column(...)
    {
         // do stuff
         return new ColumnBuilder(...);
    }
}

The trick is that while ColumnBuilder implements both IFirstColumnBuilder and INextColumnBuilder, the result from rowBuilder.Column() will have access only to the methods exposed by the IFirstColumnBuilder.

I know this does not resolve the issue with interfaces much, but still you have one single implementation, meaning less code to write, and less duplicated code in your interfaces. Also, your chain of calls will be constrained as you initially wanted.


Original response

Since I had some thoughts on your question, and being a C# developer myself, I came up with some reasons to why this will probably not happen within the CLR.

  1. Your suggestion needs additional compile-time checking for T being an interface. In C#, generics can be a subject of certain compile-time constraints. For instance

    public class ConstrainedList<T> : List<T> 
        where T: class, IEquatable<T>, new() { }
    

    The class constraint will restrict T to only reference types, the IEquatable<T> constraint will allow only types that implement the IEquatable<T> interface and the new() constraint implies that T must have a public parameterless constructor. Multiple restrictions, as in the example, must all be satisfied at once.

    For your suggestion, the CLR must support a new constraint, lets call it interface, so that T is restricted to being an interface type. This constraint will be incompatible with the new() and class constrains, and will result in additional compile-time validation, that the CLR teams would need to implement. It is arguable if such change will be backwards-compatible or not, I think it will not happen, since both class and value types hidden behind interfaces behave as reference types. Thus, the interface constraint will act in a way similar to the class constraint, and yet still allow value types that implement the interface to be seen as acceptable - in other words the constraint will contradict itself and may cause confusion and or unexpected code behavior.

  2. The second argument I have against this suggestion is more practical. In C#, and in the CLR languages in general, you can implement interfaces explicitly. (If you don't know what explicit interface implementation is and how it is used, I've explained that in the end of the post). When you have this Join<T1, T2> interface, and the stuff from point 1 is implemented, then you will have trouble when T1 and T2 provide a method with the same signature, and it requires (and has) explicit implementations. So, when you have this:

    IColumnBuilder columnBuilder = ...; // Assume a valid column builder is set
    var joinResult = columnBuilder.Column("col", "val");
    joinResult.ConflictingMethod(); // Oops!
    

    If the ConflictingMethod() is defined in both T1 and T2 that the joinResult happens to inherit from, and the T1 and T2 are explicitly implemented, then at this line the compiler will not know which implementation (T1's or T2's) is being invoked.

The above example may require a more significant and non-backwards-compatible changes to the CLR, if at all possible; so it now seems even less-likely to happen.


A note on explicit interfaces

This is a really useful feature, if you have a class that must implement a number of interfaces, and at least two of the interfaces enforce a method with the same signature. The problem is that you may need to supply different implementations per interface. Here is a dummy example to illustrate this:

    public interface IOddNumberCounter
    {
        int Count(params int[] input);
    }
    public interface IPrimeNumberCounter
    {
        int Count(params int[] input);
    }

Imagine a test that verifies these:

    public void TestOddCounter(IOddNumberCounter counter)
    {
        AssertTrue(counter.Count(1, 2, 3, 15) == 3);
    }

    public void TestPrimeCounter(IPrimeNumberCounter counter)
    {
        AssertTrue(counter.Count(2, 3, 15, 16) == 2);
    }

Now, for some reason you need this class:

    public class OddAndPrimeCounter : IOddNumberCounter, IPrimeNumberCounter
    {
        public int Count(params int[] input)
        {
            // Now what ?
        }
    }

The solution to make the tests pass for the OddAndPrimeCounter class is to implement at least one (or both) of the conflicting methods explicitly. Explicit implementations have slightly different syntax:

    public class OddAndPrimeCounter : IOddNumberCounter, IPrimeNumberCounter
    {
        int IOddNumberCounter.Count(params int[] input)
        {
            //count the odds
        }
        int IPrimeNumberCounter.Count(params int[] input)
        {
            //count the primes
        }
    }
Ivaylo Slavov
  • 8,839
  • 12
  • 65
  • 108
  • I do not understand how the existing problems caused by same method in "class OddAndPrimeCounter : IOddNumberCounter, IPrimeNumberCounter" differs from "class OddAndPrimeCounter : Join" Isn't allowing inheritance for interfaces from other interfaces via generics, is just another way to declare an interface? – jenkas Oct 03 '14 at 11:15
  • When you have an instance of the `Join` interface, which logic will be used when you call `Count(...)` ? I mean, how would the compiler know which exact member of the joined interfaces to call? – Ivaylo Slavov Oct 03 '14 at 11:33
  • I think the answer is the same as for IOddAndPrimeCounter where "public interface IOddAndPrimeCounter : IOddNumberCounter, IPrimeNumberCounter"... And it's ambigious call error, but this case is not something new in C#... – jenkas Oct 03 '14 at 11:56
  • @EvgenyMamontov, see my update, while I could not find a way to make your generics suggestion to work, I proposed a work-around for the code boilerplate in the implementation code. – Ivaylo Slavov Oct 06 '14 at 12:31
  • 1
    I anwsered to the post with my conclusions and how did I solve it. – jenkas Oct 07 '14 at 21:30