Проблема с ковариантными типами возврата из абстрактного метода - PullRequest
2 голосов
/ 27 августа 2009

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

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

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

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 пачек вашего любимого пива!

Ответы [ 2 ]

1 голос
/ 27 августа 2009

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

Если ваш абстрактный метод имеет тип возврата 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();
        }
    }
0 голосов
/ 27 августа 2009

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

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

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

...