简体   繁体   中英

Redefine method return type in derived class without generics

TL;DR:

Is there some way to add an abstract method to a base class that allows derived classes to override the method's return type, without the use of generics, and without the use of the new keyword?


I'm working on developing some custom templates for LLBLGen Pro. In the process, I refuse to change the default templates that LLBLGen Pro offers, so that my approach doesn't overwrite other peoples' files if they choose to implement my templates.

One task I've started working on (and made good headway toward) is developing a template that generates a DTO for each entity. Along those lines, one objective is to provide my entities with a ToDTO() method. In the interest of generic programming, I've decided to define this method within a common base class, and this is where my trouble starts.


Keep in mind that the purpose of defining the ToDTO() method in the base class is because I'm looking to create a generic repository (with a Fetch() method, for example) that I'd like to have work off the CommonEntityBase , as opposed to a specific entity.


LLBLGen defines its CommonEntityBase class like so:

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

My original plan was to add my method to another partial class like so:

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

I thought that the inherited classes would be able to define the return type in their methods as a type derived from the base class's return type, like so:

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

but I was wrong .


My second attempt was to define the class using generics, as such:

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

Simple enough. All I'd have to do is have my generated entity classes inherit from this new entity base. Just one caveat. As I don't want to overwrite LLBLGen's templates, it's back to partial classes.

LLBLGen's individual entities have this definition:

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

And herein lies my problem. In order for my method to work, I would have to create my own partial class with this definition:

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

Of course, this isn't possible, because, as I now know ,

All of the parts [of a partial class] that specify a base class must agree , but parts that omit a base class still inherit the base type.


The third thing I was going to attempt was simply overriding the base class's function definition with the new keyword:

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

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

However, this defeats the purpose of my approach completely, as I want to be able to access PersonEntity 's ToDTO() method when it's cast as a CommonEntityBase . With this approach, doing:

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

would result in dto being null, which I don't want.


I've come across various links discussing my first approach, and why it won't work, and typically pointing to my generic approach as a solution in the general sense. However, in my situation, generics do not appear to work.


All this to ask whether or not what I'm trying to accomplish is possible.

Is there some way to add an abstract method to a base class that allows derived classes to override the method's return type, without the use of generics, and without the use of the new keyword?

Or perhaps I'm approaching this from the wrong angle, and there's some other technique that could solve my problems?


EDIT

Here's a use-case for what I'd like to accomplish with the entities, taking Porges's approach:

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

Instead of:

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

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

Why are you not just returning a DTO like this:

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

I think that's more idiomatic for OO code. Is there a reason you need to know the exact type of the DTO?

I don't know LLBLGen, but I believe you could solve your problem this way, by introducing an interface to hold the type parameter:

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

And then for your entity classes, do this:

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

Because partial classes can introduce different interfaces, this works. The only sadness is that a cast is required to get access to the method via the base type:

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

Of course, you can call ToDTO directly without the cast when you have a reference of one of the entity derived types:

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

If you are using .NET Framework 4, you can use generic variance to make the DTOProvider interface easier to use from code that just cares about working with CommonDTOBase by declaring the DTO type covariant:

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

(Notice the 'out'.) Then your DoSomethingWithDTO method doesn't need the type parameter:

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

It is tempting to try and declare : CommonBaseEntity, DTOProvider<T> on the CommonBaseEntity partial class. Unfortunately that doesn't work, because when the partial definitions are merged the type parameter is carried over and your CommonBaseEntity type ends up being a generic type, which it looks like is what got you into a bind in the first place.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM