【问题标题】:Redefine method return type in derived class without generics在没有泛型的派生类中重新定义方法返回类型
【发布时间】:2011-03-08 17:42:16
【问题描述】:

TL;DR:

有没有办法在基类中添加一个抽象方法,允许派生类覆盖方法的返回类型,而不使用泛型,并且不使用new 关键字?


我正在为 LLBLGen Pro 开发一些自定义模板。在此过程中,我拒绝更改 LLBLGen Pro 提供的默认模板,以便我的方法不会覆盖其他人选择实施我的模板时的文件。

我已经开始着手(并取得了良好进展)的一项任务是开发一个为每个实体生成 DTO 的模板。按照这些思路,一个目标是为我的实体提供ToDTO() 方法。为了泛型编程,我决定在一个公共基类中定义这个方法,这就是我的麻烦开始的地方。


请记住,在基类中定义 ToDTO() 方法的目的是因为我希望创建一个我想要的通用存储库(例如,使用 Fetch() 方法)让CommonEntityBase 工作,而不是特定实体。


LLBLGen 像这样定义它的CommonEntityBase 类:

public abstract partial class CommonEntityBase : EntityBase2 {
   // LLBLGen-generated code
}

我最初的计划是将我的方法添加到另一个部分类中,如下所示:

public abstract partial class CommonEntityBase {
    public abstract CommonDTOBase ToDto();
}

我认为继承的类将能够在其方法中将返回类型定义为从基类的返回类型派生的类型,如下所示:

public partial class PersonEntity : CommonEntityBase {
    public override PersonDTO ToDto(){ return new PersonDTO(); }
}

但是I was wrong.


我的第二次尝试是使用泛型定义类,如下所示:

public abstract partial class CommonEntityBase<T> : CommonEntityBase 
      where T : CommonDTOBase {
   public abstract T ToDto();
}

足够简单。我所要做的就是让我生成的实体类继承自这个新的实体库。只有一个警告。由于我不想覆盖 LLBLGen 的模板,所以它回到了部分类。

LLBLGen 的各个实体有这样的定义:

public partial class PersonEntity : CommonEntityBase {
   // LLBLGen-generated code
}

这就是我的问题。为了使我的方法起作用,我必须使用以下定义创建自己的部分类:

public partial class PersonEntity : CommonEntityBase<PersonDTO> {
   public override PersonDTO ToDto(){ return new PersonDTO(); }
}

当然,这是不可能的,因为,as I now know

指定基类的[部分类]的所有部分必须同意,但省略基类的部分仍继承基类型。


我要尝试的第三件事是简单地用 new 关键字覆盖基类的函数定义:

public abstract partial class CommonEntityBase {
    public virtual CommonDTOBase ToDto(){ return null; }
}

public partial class PersonEntity : CommonEntityBase {
    public new PersonDTO ToDto(){ return new PersonDTO(); }
}

但是,这完全违背了我的方法的目的,因为当PersonEntityToDTO() 方法被转换为CommonEntityBase 时,我希望能够访问它。用这种方法,做:

CommonEntityBase e = new PersonEntity();
var dto = e.ToDto();

会导致dto 为空,这是我不想要的。


我遇到过variouslinks 讨论我的第一种方法,以及为什么它不起作用,并且通常将我的通用方法作为一般意义上的解决方案。但是,在我的情况下,泛型似乎不起作用。


所有这些都是为了问我想要完成的事情是否可能。

有没有办法在基类中添加抽象方法,允许派生类覆盖方法的返回类型,不使用泛型,也不使用new 关键字?

或者也许我从错误的角度来解决这个问题,还有其他一些技术可以解决我的问题?


编辑

这是我想用实体完成的用例,采用Porges's 方法:

public class BaseRepository<D,E> where D : CommonDTOBase where E : CommonEntityBase,new
    public D Get(Guid id){
        var entity = new E();
        entity.SetId(id);

        // LLBLGen adapter method; populates the entity with results from the database
        FetchEntity(entity);

        // Fails, as entity.ToDto() returns CommonDTOBase, not derived type D
        return entity.ToDto();
    }
}

【问题讨论】:

  • 您似乎对partial 类有一些概念上的问题。没有魔法。我认为考虑它们的最佳方式是,编译器只是在编译之前为部分类定义的所有代码填充到一个类定义中。
  • 谷歌“协变返回类型”。

标签: c# inheritance partial-classes llblgen


【解决方案1】:

代替:

public abstract partial class CommonEntityBase {
    public abstract CommonDTOBase ToDto();
}

public partial class PersonEntity : CommonEntityBase {
    public override PersonDTO ToDto(){ return new PersonDTO(); }
}

你为什么不只是像这样返回一个 DTO:

public abstract partial class CommonEntityBase {
    public abstract CommonDTOBase ToDto();
}

public partial class PersonEntity : CommonEntityBase {
    // changed PersonDTO to CommonDTOBase
    public override CommonDTOBase ToDto(){ return new PersonDTO(); }
}

我认为这对于 OO 代码来说更惯用。您是否有理由需要知道 DTO 的确切类型?

【讨论】:

    【解决方案2】:

    我不知道 LLBLGen,但我相信你可以通过这种方式解决你的问题,通过引入一个接口来保存类型参数:

    public interface DTOProvider<T> where T : CommonDTOBase {
        public T ToDTO();
    }
    

    然后对于你的实体类,这样做:

    public partial class PersonEntity : CommonEntityBase, DTOProvider<PersonDTO> {
        public PersonDTO ToDto() { return new PersonDTO(); }
    }
    

    因为部分类可以引入不同的接口,所以这是可行的。唯一的遗憾是需要强制转换才能通过基类型访问该方法:

    public void DoSomethingWithDTO<T>(CommonBaseEntity entity)
            where T : CommonDTOBase {
        T dto = ((DTOProvider<T>) entity).ToDTO();
        ...
    }
    

    当然,当您引用其中一种实体派生类型时,您可以直接调用 ToDTO 而无需强制转换:

    public void DoSomethingWithPersonDTO(PersonEntity entity)
    {
        PersonDTO dto = entity.ToDTO();
        ...
    }
    

    如果您使用的是 .NET Framework 4,则可以通过声明 DTO 类型协变来使用泛型变体,使 DTOProvider 接口更易于从只关心使用 CommonDTOBase 的代码中使用:

    public interface DTOProvider<out T> where T : CommonDTOBase {
        public T ToDTO();
    }
    

    (注意'out'。)然后你的 DoSomethingWithDTO 方法不需要类型参数:

    public void DoSomethingWithDTO(CommonBaseEntity entity) {
        CommonDTOBase dto = ((DTOProvider<CommonDTOBase>) entity).ToDTO();
        ...
    }
    

    尝试在 CommonBaseEntity 部分类上声明 : CommonBaseEntity, DTOProvider&lt;T&gt; 很诱人。不幸的是,这不起作用,因为当部分定义被合并时,类型参数会被继承,并且您的 CommonBaseEntity 类型最终会成为泛型类型,看起来这就是让您首先进入绑定的原因。

    【讨论】:

    • 使用这种方法,CommonBaseEntity 在哪里得到ToDto() 的定义?如果它使用: DTOProvider&lt;CommonDTOBase&gt; 继承,那么我们会遇到我原来无法更改返回类型的问题,如果它使用: DTOProvider&lt;T&gt; 继承,那么我们必须将泛型合并到CommonBaseEntity 中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-24
    • 2023-03-28
    • 2018-10-19
    相关资源
    最近更新 更多