Good reason to inherit from generics in C #
I know this topic has been discussed many times: What are the good reasons why .NET developers might inherit from one of the typical parameter types?
But now I think it's really necessary ...
I will try to explain with a simple example why we need inheritance from generic types.
In short, the motivation is to make it much easier to develop what I call Compile Time Order Enforcement, which is very popular in ORM Framework.
Let's assume we are building a database.
Here is an example of building a transaction with such a card:
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 each line returns some interface, we would like to return them in such a way that the developer cannot commit an error in sequential order, and the actual usage will be applied at compile time, which will also simplify the implementation and use of TransactionBuilder, because the developer simply cannot be wrong , eg:
{
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 take a look at the ITransactionBuilder interface today, WITHOUT inheriting from generic, which will provide the required ordering 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 the column builder "IFirstColumnBuilder" and "INextColumnBuilder" which are not really required, and remember that this is a very simple example of a compile-time state machine, and in a more complex problem, a number of Optional interfaces will grow dramatically.
Now suppose we can inherit from generics and prepare interfaces like this
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
We can then rewrite our interfaces in a more intuitive style and with a single column constructor 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 "next steps"), which is very useful for developing state machines.
Of course, this particular problem could be solved by adding a capability in C # to "connect" interfaces, but it is clear that if one could inherit from generics, the problem would not exist at all, and it is also clear that compilation time ordering is very useful.
BTW. For syntax like
interface IInterface<T> : T {}
There are no "what if" cases other than the inheritance loop that can be found at compile time.
I think that AT LEAST for interfaces needs this function 100%
Hello
source to share
You grossly underestimate the complexity of this feature. Something is wrong as if I am writing
public class Fooer
{
public void Foo();
}
public class Generic<T> : T
{
public void Foo();
}
var genericFooer = new Generic<Fooer>();
Now I have a problem with conflicting member signatures.
The large number of interfaces to support the free api as you describe represents a small subset of the use cases and, as I mentioned in my comment, is relatively easy to support by metaprogramming. I'm not sure if anyone has created a T4 template to translate DSL into interface declarations, but it is certainly doable. I don't think interfaces have ever been created with every combination of transitions represented in the final destination machine. Of course, this is not worth the change, which I am sure would be extremely difficult to implement and have all sorts of odd cases like this.
Update . Another thing to keep in mind is that it has a very large potential for breaking existing code that uses Reflection. This will include things like Entity Framework, IoC containers, Automapper, maybe more. I would be surprised if there were no new use cases introduced in any of these examples, introduced by such a change that would cause errors or unexpected behavior.
Of course, this is true to some extent for any language change, but this one significant one is likely to have a big impact. Again, this is a large cost that must be balanced against the relatively small benefit.
Update 2 . If we restrict it to only interfaces, we still have this problem:
public interface IFooer
{
public void Foo();
}
public interface IGeneric<T> : T
{
public int Foo();
}
They are incompatible because you cannot have two methods that differ only in return type, even on an interface.
source to share
I looked through the comments and here are my findings (combined with my opinion).
- It is not as easy as I thought to allow inheritance from generics, even for interfaces.
- A solution for the Fluent API is a must (maybe not through generics inheritance, but through a new syntax for "interconnecting interfaces", which is proposed later), since it is very easy to use and allows you to refuse many developer errors during compilation time - popularity will only be grow.
I believe 1 and 2 are more than possible, but for the time being I decided to print my best solution to the problem I described
So I changed interfaces to this ("new syntax" 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 that big. Compared to being "mergeable" we only need to declare dummy interfaces for the return types and then inherit from the developer, so let's take a look at TransactionBuilder (the developer) ...
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 can see, I only have one class that implements all the methods, and I really don't see a reason to implement many classes for each interface (so Ivaylo was trying to solve a problem that didn't exist for me;)). I think it's enough to have a state machine at the interface level, and the code can be much simpler and more compact.
... Other classes used in the code just to complete the image ...
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 someone on the .NET team at Microsoft sees this and likes ...
source to share
Update
I revisited the question, your last comments, and I came up with an idea based on my interpretation of your problem.
I agree with your last statement, there really won't be any difference between an interface Join<...>
and a hypothetical interface IOddAndPrimeCounter : IOddCounter, IPrimeCounter
. What I had in mind with point 2 is that you cannot distinguish whether the resulting connection interface should be called like T1
or T2
.
I would suggest a slightly different approach, which I myself use in such scenarios. I would keep the two interfaces IFirstColumnBuilder
and INextColumnBuilder
, but change the latter a bit:
interface INextColumnBuilder :
IFirstColumnBuilder, ITransactionBuilder, IRowBuilder, ITransactionHolder {}
Then I'll move on to one implementation IFirstColumnBuilder
and INextColumnBuilder
which looks like this:
public class ColumnBulder : INextColumnBuilder // IFirstColumnBuilder is already implemented
{
}
A typical implementation IRowBulder
would 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 the INextColumnBuilder
result from rowBuilder.Column()
will only have access to public methods IFirstColumnBuilder
.
I know this doesn't solve the interface problem very much, but still you have one single implementation, which means less code to write and less duplicate code on your interfaces. In addition, your call chain will be limited at will.
Original Answer
Since I had some thoughts on your question and I was a C # developer myself, I came up with some reasons why this probably won't happen in the CLR.
-
Your suggestion requires additional compile-time checking for
T
being an interface. In C #, generics can be subject to certain compile-time constraints. For example,public class ConstrainedList<T> : List<T> where T: class, IEquatable<T>, new() { }
The restriction will
class
restrictT
only to reference types, the restrictionIEquatable<T>
will only allow types that implement the interface,IEquatable<T>
and the restrictionnew()
that T must have a public parameterless constructor. Multiple constraints, as in the example, must be executed immediately.Your suggestion is that the CLR should support the new constraint, let you call it
interface
, so it'sT
limited by the interface type. This constraint would be incompatible with the constraintsnew()
andclass
would result in additional compile-time checks that CLR teams would need to implement. Perhaps if such a change is backward compatible or not, I think it won't, since both typesclass
and value types hidden behind the interfaces behave like reference types. Thus, the restrictioninterface
will act the same as the restrictionclass
and yet allow value types that implement the interface to be considered acceptable, in other words, the constraint would contradict itself and could cause confusion and / or unexpected behavior in the code. -
The second argument I have against this proposal is more practical. In C # and CLR languages ββin general, you can explicitly implement interfaces. (If you don't know which is the explicit implementation of the interface and how it is used, I explained that at the end of the post). When you have this
Join<T1, T2>
interface, and the material from point 1 is implemented, then you will have problems whenT1
bothT2
provide a method with the same signature and require (and have) 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 it is
ConflictingMethod()
defined in both inT1
and inT2
what isjoinResult
inherited, butT1
and areT2
explicitly implemented, then in this line the compiler will not know which implementation (T1
orT2
's) is called.
The above example may require more significant and non-dispatch changes to the CLR, if at all possible; so now it seems even less likely.
A note on explicit interfaces
This is a really useful feature if you have a class that needs to implement multiple interfaces, and at least two of the interfaces use a method with the same signature. The problem is that you may need to provide different implementations for each interface. Here's an example to illustrate:
public interface IOddNumberCounter
{
int Count(params int[] input);
}
public interface IPrimeNumberCounter
{
int Count(params int[] input);
}
Imagine a test that tests them:
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 decision to pass tests for a class OddAndPrimeCounter
is to implement at least one (or both) conflicting methods explicitly. Explicit implementations have a 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
}
}
source to share