Переопределить тип возвращаемого метода в производном классе без обобщений - PullRequest
0 голосов
/ 27 июня 2010

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

, но Я был не прав .


Моя вторая попытка была определить класс с использованием обобщений, как таковых:

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

Конечно, это невозможно, потому что как я теперь знаю ,

Все части [частичного класса], которые задают базовый класс , должны согласовываться , но части, которые опускают базовый класс, все еще наследуют базовый тип.


Третья вещь, которую я собирался попробовать, это просто переопределить определение функции базового класса с помощью ключевого слова new :

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

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

Однако это побеждаетцель моего подхода полностью, так как я хочу иметь доступ к методу PersonEntity ToDTO(), когда он приведен как CommonEntityBase.При таком подходе выполнение:

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

приведет к тому, что dto будет нулевым, чего я не хочу.


Я встречал различных links обсуждают мой первый подход и почему он не работает, и обычно указывают на мой общий подход как решение в общем смысле.Однако в моей ситуации генерики не работают.


Все это, чтобы спросить, возможно ли то, что я пытаюсь выполнить.

Есть ликакой-нибудь способ добавить абстрактный метод к базовому классу, который позволяет производным классам переопределять тип возвращаемого значения метода, без использования обобщений и без использования ключевого слова new?

Или, возможно,Я подхожу к этому с неправильной точки зрения, и есть какая-то другая техника, которая может решить мои проблемы?


РЕДАКТИРОВАТЬ

Вотсценарий использования того, что я хотел бы выполнить с сущностями, используя подход Поргеса :

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

Ответы [ 2 ]

4 голосов
/ 27 июня 2010

Вместо:

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

Я думаю, что это более идиоматично для ОО-кода. Есть ли причина, по которой вам нужно знать точный тип DTO?

0 голосов
/ 27 июня 2010

Я не знаю 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, вы можете использовать общее отклонение, чтобы упростить использование интерфейса DTOProvider из кода, который просто заботится о работе с CommonDTOBase, объявив ковариант типа DTO:

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, DTOProvider<T> в частичном классе CommonBaseEntity. К сожалению, это не работает, потому что, когда частичные определения объединяются, параметр типа переносится, и ваш тип CommonBaseEntity становится универсальным типом, который, по-видимому, и попал в привязку.

...