Проблема с возвращаемыми типами Covariant из абстрактного метода

Я пытаюсь завершить двухдневную работу по абстрактным методам и возвращаемому типу ковариации, я уже опубликовал два похожих вопроса, и я бесконечно благодарен сообществу за предоставленную информацию, мне просто нужен последний толчок, чтобы добраться до финишная черта. Вот что я пытаюсь сделать: 2 абстрактных класса, RecruiterBase и CandidateBase, имеют конкретные реализации RecruiterA и CandidateA. RecruiterBase имеет абстрактный метод для получения всех завербованных кандидатов, возвращающих IQueryable. Моя реализация RecruiterA переопределяет метод GetCandidates() для возврата IQueryable.

public abstract class RecruiterBase
{ 
  // Constructors declared here

  public abstract IQueryable<CandidateBase> GetCandidates();
}

public abstract class CandidateBase
{  
  // Constructors declared here
}

и реализации:

public class CandidateA : CandidateBase
{
  // Constructors declared here
}

public class RecruiterA : RecruiterBase
{
  // Constructors declared here

  // ----HERE IS WHERE I AM BREAKING DOWN----
  public override IQueryable<CandidateA> GetCandidates()
  {
     return from c in db.Candidates
            where c.RecruiterId == this.RecruiterId
            select new CandidateA
            {
              CandidateId = c.CandidateId,
              CandidateName = c.CandidateName,
              RecruiterId = c.RecruiterId
            };
  }
}

Попытка скомпилировать это выдает ошибку времени компиляции, потому что в моей реализации RecruitreBase метод GetCandidates() возвращает IQueryable<CandidateA> вместо IQueryable<CandidateBase>.

После того, как вы не смогли получить предложения из предыдущего вопроса (Общие типы возврата из абстрактного /виртуальные методы) для работы, я НАМНОГО больше читал и наткнулся на следующий вопрос в SO

Как вернуть подтип в переопределенном методе подкласса в С#?

Что, наконец, заставило меня понять, что я искал способ реализовать ковариацию для моего возвращаемого типа. Я использовал фрагмент Марка Гравелла...

abstract class BaseClass
{
    public BaseReturnType PolymorphicMethod()
    { return PolymorphicMethodCore();}

    protected abstract BaseReturnType PolymorphicMethodCore();
}

class DerivedClass : BaseClass
{
    protected override BaseReturnType PolymorphicMethodCore()
    { return PolymorphicMethod(); }

    public new DerivedReturnType PolymorphicMethod()
    { return new DerivedReturnType(); }
}

... в качестве основы для моего решения. Итак, теперь мои классы RecruiterBase и RecruiterA выглядят так:

public abstract class RecruiterBase
{
  // Constructors declared here

  public IQueryable<CandidateBase> GetCandidates()
  {
     return GetCandidatesCore();
  }

  public abstract IQueryable<CandidateBase> GetCandidatesCore();
}

и моя реализация...

public class RecruiterA : RecruiterBase
{
  // Constructors

  protected override IQueryable<CandidateBase> GetCandidatesCore()
  {
    return GetCandidates();
  }

  public new IQueryable<CandidateA> GetCandidates()
  {
    return from candidates in db.Candidates
           select new CandidateA
           {
             CandidateId = candidates.CandidateId,
             RecruiterId = candidates.RecruiterId
           };
  }
}

Я надеялся, что это, наконец, даст мне то, что я искал, но я получил ошибку времени компиляции в следующем коде, потому что GetCandidates() не может неявно преобразовать CandidateA в CandidateBase:

  protected override IQueryable<CandidateBase> GetCandidatesCore()
  {
    return GetCandidates();
  }

поэтому я добавил актерский состав:

  protected override IQueryable<CandidateBase> GetCandidatesCore()
  {
    return ((IQueryable<CandidateBase>)GetCandidates());
  }

Затем все компилируется, но когда я действительно вызываю GetCandidates() в моем контроллере, он возвращает IQueryable<CandidateBase> вместо IQueryable<CandidateA>. Итак, я вернулся к тому, с чего начал.

Если вы прошли весь этот путь и можете мне помочь, я пришлю вам 12 упаковок вашего любимого пива!


person Community    schedule 26.08.2009    source источник
comment
Я пытался добавить тег Shootmenow, но я все еще n00b :)   -  person    schedule 27.08.2009
comment
с каким типом вы работаете в своем контроллере? Можете ли вы показать код, который вы используете там? Если вы работаете с RecruiterBase, вы никогда не увидите вариант, возвращающий IQueryable из CandidateA (этот вариант доступен только в производном классе).   -  person jeroenh    schedule 27.08.2009
comment
@jeroenh, мой код контроллера довольно прост: RecruiterBase рекрутер = новый RecruiterA(); Кандидаты IQueryable‹CandidateA› = рекрутер.GetCandidates(); ... который затем добавляется в ViewData   -  person    schedule 27.08.2009


Ответы (2)


Джастин, я немного сбит с толку, зачем тебе все эти неприятности.

Если ваш абстрактный метод имеет возвращаемый тип IQueryable<CandidateBase>, то это то, что вы получите. Я не вижу в этом проблемы, так как позже вы все еще можете вернуть его к CandidateA или CandidateB.

Так чего именно вы пытаетесь достичь? Может быть, я не понимаю вашего вопроса.

Изменить, чтобы добавить:

Джастин, что насчет этого?

public abstract class RecruiterBase<T>
    {
        // Constructors declared here

        public abstract IQueryable<CandidateBase> GetCandidates();
    }

    public abstract class CandidateBase
    {
        // Constructors declared here
    }


    public class CandidateA : CandidateBase
    {

    }

    public class RecruiterA : RecruiterBase<RecruiterA>
    {
        // Constructors declared here

        // ----HERE IS WHERE I AM BREAKING DOWN----
        public override IQueryable<CandidateBase> GetCandidates()
        {
            return db.Candidates.Where(cand => cand.RecruiterId == this.RecruiterId)
                         .Select(x => new CandidateA
                                          {
                                             CandidateId = c.CandidateId,
                                             CandidateName = c.CandidateName,
                                             RecruiterId = c.RecruiterId
                                           })
                         .Cast<CandidateBase>()
                         .AsQueryable();
        }
    }
person Stan R.    schedule 26.08.2009
comment
Стэн, моя реализация твоего кода не компилируется. Ошибка при вызове .AsQueryable‹TempCandidateBase›();. Я получаю аргумент экземпляра, что невозможно выполнить преобразование из IQueryable‹CandidateA› в IEnumerable‹CandidateBase› - person ; 27.08.2009
comment
Джастин, дай мне минуту, я воспроизведу твой код и вернусь к тебе. - person Stan R.; 27.08.2009
comment
Джастин. открытый абстрактный IQueryable‹CandidateBase› GetCandidates(); не будет компилироваться в вашем примере кода, потому что CandidateBase является универсальным классом. - person Stan R.; 27.08.2009
comment
Стэн, прошу прощения за путаницу. Вы правы в том, что у меня есть CandidateBase, помеченный как абстрактный, то есть из кроличьей норы, в которую я спустился вчера, которая не оправдалась. CandidateBase не является универсальным. Буква «Т» должна быть удалена. - person ; 27.08.2009
comment
Стэн, я отредактировал код, чтобы удалить общие объявления. Извините, если это заставило вас тратить время впустую. Но я верну свой код и попробую, если он заработает, и отчитаюсь. - person ; 27.08.2009
comment
убедитесь, что вы делаете .Cast‹CandidateBase›().AsQueryable(); - person Stan R.; 27.08.2009
comment
Стэн, если я пойду по пути дженериков, то в конечном итоге мне придется переплетать различные универсальные классы для всех моих бизнес-объектов, что вызывает опасения по поводу раздувания. Это было затронуто в моем первом вопросе (должно быть, я не смог дать ссылку на него). stackoverflow.com/questions/1330473 / - person ; 27.08.2009
comment
Джастин, взгляните на приведенный выше код. Нет необходимости в дженериках, и приведенный выше код будет работать легко. Вы слишком усложняете простую задачу, подумайте сами... нужны ли вам дженерики? зачем вам дженерики в этом случае? это важные вопросы. Вам не нужно усложнять код, потому что технологии позволяют это делать. Сохраняйте простоту, и в дальнейшем у вас будет лучший код. - person Stan R.; 27.08.2009
comment
Стэн, ты прав, вчера я отказался от дженериков, я просто вставил старый код. Yoooder также указал ниже, что я потерял контроль над своей первоначальной проблемой. Спасибо за ваш код. Я просто собираюсь пересмотреть все это. - person ; 27.08.2009
comment
Хорошо мыслит Джастин. Не нужно отмечать меня как ответ, это не важно. Что важно, так это хороший код и хорошее планирование. - person Stan R.; 27.08.2009

Я думаю, что ваши намерения хороши, но конечным результатом является то, что вы упускаете суть полиморфного кода, а также теряете ценность.

Цель работы с объектами по их абстрактному типу или их интерфейсам состоит в том, чтобы позволить вам работать с любой конкретной реализацией без необходимости знать какие-либо подробности конкретной реализации. Я думаю, вы считаете, что, возвращая конкретные типы, вы производите более качественный код, хотя на самом деле вы начинаете сводить на нет значение абстрактного базового класса, скрывая абстракцию.

У правильно построенного набора производных классов должно быть очень мало потребностей, которые должны решаться их конкретными типами; абстрактного класса должно быть достаточно для работы со всеми реализациями, и он должен выполнять большую часть работы с этими классами — исключения должны быть в меньшинстве.

person STW    schedule 26.08.2009
comment
Я начинаю соглашаться, Юдер, я серьезно сомневаюсь в своем мыслительном процессе, не является ли то, что я пытаюсь сделать, необычным. По крайней мере, я уловил это в начале процесса проектирования. Спасибо за ваши мысли по этому вопросу и вчера. - person ; 27.08.2009
comment
Без проблем; это нормально - попытаться получить что-то идеальное только для того, чтобы понять, что несколько часов назад вы превзошли совершенство :) - person STW; 27.08.2009