希望.NET泛型可以继承其中一个泛型参数类型的理由是什么?

时间:2009-12-04 19:24:57

标签: .net generics

此帖子继续this one

我试图了解我是否是唯一一个错过并需要.NET泛型类型才能继承其通用参数类型的人。

挑战是收集有利于此功能的令人信服的理由,或者,要知道没有。

我有理由将其作为这个问题的答案 - 见下文。

我要求那里的人添加他们作为这篇文章的答案。

如果您不同意该功能有用或没有充分理由支持 - 请不要在此处发布任何内容,但您可以在已启动该功能的原始帖子中进行此操作 - here

P.S。

某些C ++模式在.NET中无关紧要。例如,在他出色的着作Modern C++ Design中,Andrei Alexandrescu描述了如何创建在编译时评估的类型列表。当然,这种模式与.NET无关,如果我需要一个类型列表,我只需创建List<Type>并用类型填充它。因此,让我们尝试提出与.NET框架相关的原因,而不是盲目地将C ++编码技术转换为C#。

P.P.S。

当然,这个讨论严格来说是学术性的。即使有问题的一百个令人信服的理由浮出水面,它也不会被实施。

8 个答案:

答案 0 :(得分:7)

我偶尔偶然发现一个实施问题,我非常遗憾C<T>无法继承T。不幸的是,我从来没有记录过这些问题,因此我可以描述最近的问题 - 我现在偶然发现的问题。所以这就是:

我们的系统可通过元数据进行扩展,这些元数据在运行时可用。元数据将转换为使用Reflection.Emit在运行时动态生成的类型。不幸的是,动态生成的类型必须满足以下条件:

  1. 它必须从某些其他类型派生,作为动态类型创建者的参数提供。此类型称为祖先类型,并且始终是静态编译类型。
  2. 它必须实现多个接口,例如IDynamicObject(我们的界面),System.ComponentModel.INotifyPropertyChangedCsla.Core.IMobileObject。请注意,此条件会对祖先类型设置某些约束。例如,祖先类型可能不实现IDynamicObject接口,除非所有接口方法都是抽象实现的。还有其他限制,必须检查所有限制。
  3. 它应该(并且确实)覆盖object方法EqualsToStringGetHashCode
  4. 目前,我不得不使用Reflection.Emit发出所有IL代码来满足这些条件。当然,某些任务可能会转发到静态编译的代码。例如,object.Equals(object)方法的覆盖调用DynamicObjectHelper(IDynamicObject, IDynamicObject),这是一个静态编译的C#代码,执行最大的工作量 比较两个动态对象的相等性。但这更像是一个例外而不是规则 - 大多数实现都被发出,这是一个痛苦的屁股。

    能够编写泛型类型,如DynamicObjectBase<T>,它将用祖先类型实例化并作为动态生成类型的实际基类型,这不是很好吗?在我看来,泛型类型DynamicObjectBase<T>可以在静态编译的C#代码中实现大部分动态类型需求。动态发出的类型将继承它,并且可能只是覆盖一些简单的虚拟方法。

    总而言之,令人信服的理由是,让C<T>继承T会大大简化发布动态类型的任务。

答案 1 :(得分:1)

我正在使用GTK#3。有一个小工具&#39; class defined,它是所有其他GTK#小部件的基础,例如窗口,标签,框架。不幸的是,框架使得更改窗口小部件的背景颜色变得有点复杂,因为您需要覆盖Widget的方法来执行此操作(jeez ...)。

所以,如果我想 - 比如说 - 我可以任意设置背景颜色的标签,我应该这样做:

class BGLabel : Label
{
    private Color _bgColor;

    public Color BackgroundColor
    {
        get { return this._bgColor; }
        set { this._bgColor = value; this.QueueDraw(); /* triggers OnDraw */ }
    }

    protected override void OnDraw(...)
    {
        ... /* here we can use _bgColor to paint the background */
    }
}

现在,如果我想为更多小部件提供这个不错的功能,我必须为每个执行上述操作。如果&#34; C类&lt; T> :T&#34;可以编译,我可以改为:

class C<T> : T where T : Widget
{
    private Color _bgColor;

    public Color BackgroundColor
    {
        get { return this._bgColor; }
        set { this._bgColor = value; this.QueueDraw(); /* triggers OnDraw */ }
    }

    protected override void OnDraw(...)
    {
        ... /* here we can use _bgColor to paint the background */
    }
}

而不是&#34; BGxxx bgw = new BGxxx();&#34;我可以使用&#34; C&lt; xxx&gt; bgw =新C&lt; xxx&gt;();&#34;。

答案 2 :(得分:1)

我曾经希望有一个数据库对象(例如MyEntity)和一个相应的历史对象(例如MyEntityHistory)。这些实体共享完全相同的属性,只是历史记录中仅存在两个属性:ValidFromValidTo

因此,为了显示对象的当前状态以及历史记录数据,我可以在它们之间获取这段文字UNION,并使用Dapper或EF Core的{strong>映射功能 {1}}以将结果映射到FromSql的历史记录。

当然,我想避免重复属性,因此理想情况下,我将继承实体类以获取其属性。而且,我想从其他一些基类中获取List<MyEntityHistory>ValidFrom属性,因此最终遇到了钻石问题。会有帮助的是,我将定义一个这样的历史记录类:

ValidTo

class MyEntityHistory : HistoryEntity<MyEntity> 的定义如下:

HistoryEntity

不幸的是,class HistoryEntity<TEntity> : TEntity where TEntity: class { public DateTime ValidFrom { get; set; } public DateTime ValidTo { get; set; } } 的这种实现是不可能的。

请注意,通过组合结构嵌入实体类(HistoryEntity<TEntity>)是不可行的,因为Dapper或EF Core的映射器将无法处理嵌套对象。

答案 3 :(得分:0)

虽然我看到了你所看到的内容,但它看起来像一个特殊情况,通过.NET中的组合对类型构造的支持不足这一更普遍的问题。像https://connect.microsoft.com/VisualStudio/feedback/details/526307/add-automatic-generation-of-interface-implementation-via-implementing-member所述的方法是否适合你?

答案 4 :(得分:0)

防范这种情况的泛型的基本规则是“泛型类型的内容必须在泛型参数上明确定义”。让我们看看它如何适用于以下代码:

public abstract class AbstractBase
{
    public abstract string MyMethod();
}

public class SomeType<T> : T
{
}

public class SomeUsage
{
    void Foo()
    {
        // SomeType<AbstractBase> does not implement AbstractBase.MyMethod
        SomeType<AbstractBase> b = new SomeType<AbstractBase>();
    }
}

因此我们尝试实施MyMethod()

public class SomeType<T> : T
{
    public override string MyMethod()
    {
        return "Some return value";
    }
}

public class SomeUsage
{
    void Foo()
    {
        // SomeType<string> does not inherit a virtual method MyMethod()
        SomeType<string> b = new SomeType<string>();
    }
}

因此,我们要求T来自AbstractBase

public abstract class DerivedAbstractBase : AbstractBase
{
    public abstract string AnotherMethod();
}

public class SomeType<T> : T
    where T : AbstractBase
{
    public override string MyMethod()
    {
        return "Some return value";
    }
}

public class SomeUsage
{
    void Foo()
    {
        // SomeType<DerivedAbstractBase> does not implement DerivedAbstractBase.AnotherMethod()
        SomeType<DerivedAbstractBase> b = new SomeType<DerivedAbstractBase>();
    }
}

要点:

当你考虑基类型中的所有限制时,你受到限制,从泛型参数派生是没有意义的。

答案 5 :(得分:0)

我将尝试用简单的例子来解释为什么我们需要从泛型类型继承。

简单的动机是:更容易开发我称之为编译时间顺序执行的东西,这在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")  /*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 */
    }

现在让我们看看今天的ITransactionBuilder接口,没有继承自generic,它将在编译时强制执行所需的顺序:

    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();
    }

正如您所看到的,我们为Column builder“IFirstColumnBuilder”和“INextColumnBuilder”提供了2个接口,这些接口实际上并不是必需的,并且记住这是编译时状态机的一个非常简单的例子,而在更复杂的问题中,不必要的接口将大幅增长。

现在让我们假设我们可以继承泛型并准备这样的接口

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

然后,我们可以将界面重写为更直观的样式并使用单列构建器,而不会影响订单

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&lt; ...&gt;组合现有接口(或“后续步骤”),这在状态机开发中非常有用。

当然,这个特定的问题可以通过在C#中添加“加入”接口的可能性来解决,但很明显,如果可以继承泛型,问题根本不存在,而且很明显编译时间执行是非常有用的事情。

顺便说一句。对于像

这样的语法
    interface IInterface<T> : T {}

除了继承循环之外,没有任何“假设”情况,可以在编译时检测到。

我认为至少接口这个功能是100%需要的

此致

答案 6 :(得分:0)

我写了CodeFirstWebFramework,这是一个dll,提供编写数据库驱动的Web服务器的所有工具,表格自动从代码中的类生成,并将URL粘贴到AppModule类中的方法调用。

DLL提供了一些基本的AppModule类 - AdminModule和FileSender。这些实现管理(登录,维护用户,更新设置),以及当url与任何其他AppModule无关时返回文件。

DLL的消费者可以覆盖抽象的AppModule类,为所有自己的AppModule添加附加功能(将从覆盖中派生出来)。

我还希望他们能够覆盖AdminModule和/或FileSender。然而,它们能够覆盖这些模块中的一个(因此可以访问其中的默认代码),同时也是它们自己的AppModule实现的子类,这将是有用的(并且在某些情况下是必要的)。

我以为我有一个很棒的主意,如下:

在我的DLL中:

abstract class AppModule {
    // Contains base class implementation, including virtual Database property, code to 
    // retrieve files, code to call appropriate method depending on url, code to collect 
    // result and return it to the web browser, etc.
}

abstract class AdminModule<T> where T:AppModule, new() : T {
    // Contains implementation of Admin methods - create/edit users, 
    // edit settings, backup database, etc.
}

class AdminModule : AdminModule<AppModule> {
    // No code needed - inherits implementation from AdminModule<AppModule>
}

在消费者计划(不同的名称空间)中:

abstract class AppModule : AppModule {
    // Contains additional properties and methods. Also overides some virtual methods,
    // e.g. returns a subclass of Database with additional methods, or retrieve files
    // from the database instead of the file system.
}

class AdminModule : AdminModule<AppModule> {
    // Additional code for Admin methods specific to this application
    // Does not need code for methods in base class - inherits implementation from 
    // AdminModule<AppModule>
}

在这个特定的例子中,由于T是AppModule的约束,并且因为AdminModule&lt; T&gt;这本身就是抽象的,我无法理解为什么到目前为止提出的任何异议都适用。

编译器可以检查派生类中是否实现了任何抽象方法。构建AdminModule&lt; T&gt;没有危险,因为它是抽象的。

编写AdminModule&lt; T&gt;是编译时错误。如果T是密封类,或者是从AdminModule&lt; T&gt;派生的,或者其可访问性低于AdminModule&lt; T&gt;。

答案 7 :(得分:0)

之所以能够从类型参数中继承,是因为以下情况:

我想拥有一个抽象的ViewModel类,该类继承自Entity类,并且具有一些其他属性和方法。

public abstract class ViewModel<TEntity> : TEntity, IViewModel<TEntity> 
where TEntity : class, IEntity, new() {
    public void SomeMethod() {

    }
}

然后我可以创建一个特定的ViewModel

public class EmployeeViewModel : ViewModel<Employee> {

}

好!它继承了实体的所有字段,并具有抽象viewmodel的标准属性和方法! ..

可悲的是现在无法完成。