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

StackOverflow https://stackoverflow.com/questions/1337327

  •  20-09-2019
  •  | 
  •  

Вопрос

Я пытаюсь завершить двухдневное изучение абстрактных методов и ковариантного типа возвращаемого значения. Я уже задавал два подобных вопроса и бесконечно благодарен сообществу за предоставленную информацию, мне просто нужен последний толчок, чтобы добраться до финишная черта.Вот что я пытаюсь сделать:Два абстрактных класса, 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 упаковок вашего любимого пива!

Это было полезно?

Решение

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

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

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

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

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

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

Другие советы

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top