我将尝试用简单的例子解释为什么我们需要从通用类型继承。
简要概述动机:是为了更轻松地开发我所谓的“编译时顺序强制执行”,这在 ORM 框架中非常流行。
假设我们正在构建数据库框架。
以下是使用这种框架构建事务的示例:
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;
}
由于每行都返回一些接口,我们希望以这种方式返回它们,使得开发人员无法在操作顺序上犯错误,并且可以在编译时强制执行有效使用,这也使得TransactionBuilder的实现和使用更加简单,因为开发人员将无法犯错,例如:
{
ITransaction transaction = builder
.UpdateTable("table1")
.UpdateTable("table2")
}
{
ITransaction transaction = builder
.UpdateTable("table1")
.Row(12)
.Row(45)
}
现在让我们今天看一下ITransactionBuilder接口,没有从泛型继承,这将在编译时强制执行所需的顺序:
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();
}
正如您所看到的,我们有两个列构建器的接口:“IFirstColumnBuilder”和“INextColumnBuilder”,实际上它们并不是必需的。请记住,这只是编译时状态机的非常简单的示例,而在更复杂的问题中,不必要的接口数量将会大幅增加。
现在让我们假设我们可以从泛型中继承并准备这些接口。
interface Join<T1, T2> : T1, T2 {}
interface Join<T1, T2, T3> : T1, T2, T3 {}
interface Join<T1, T2, T3, T4> : T1, T2, T3, T4 {}
然后,我们可以将接口重写为更直观的样式,并使用单列构建器,而不会影响顺序。
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();
}
因此,我们使用Join<...>来组合现有接口(或“下一步骤”),这在状态机开发中非常有用。
当然,这个特定的问题可能通过在C#中添加“连接”接口的可能性来解决,但很明显,如果可以从泛型继承,那么问题根本不存在,编译时顺序强制执行也是非常有用的事情。
顺便说一句,对于像
interface IInterface<T> : T {}
除了可能在编译时检测到的继承循环之外,没有任何“如果”的情况。
我认为至少对于接口,这个功能是百分之百必要的。
敬礼