This post is in continuation of this one.
I am trying to understand if I am the only one who misses and needs the ability of a .NET generic type to inherit one of it
Once when I wanted this was when I had database objects (e.g. MyEntity
) that also had a corresponding history object (e.g. MyEntityHistory
). These entities shared exactly the same properties, except for 2 that were only present on the history one: ValidFrom
and ValidTo
.
So, in order to display the current state of an object as well as history data, I could fetch this writing UNION
between them and use mapping capabilities of Dapper or EF Core's FromSql
to map the result to List<MyEntityHistory>
history.
Of course I would like to avoid duplicating the properties, so Ideally I'd inherit the entity class to get it's properties. But also, I'd like to get the ValidFrom
and ValidTo
properties from some other base class, so I'd end up with a diamond problem. What would help, is that I'd have a history class defined like this:
class MyEntityHistory : HistoryEntity<MyEntity>
Where HistoryEntity
would be defined as such:
class HistoryEntity<TEntity> : TEntity where TEntity: class
{
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
}
Unfortunately such implementation of HistoryEntity<TEntity>
is not possible.
Note that embedding entity class (MyEntity
) through composition is not an option, because Dapper or EF Core's mapper wouldn't be able to work around the nested object.
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