The problem with covariant return types from an abstract method

I am trying to wrap up a two day hit on abstract methods and return the Covariance type, Ive posted two similar questions already and I am eternally grateful to the community for the information provided, I just need a final push to get to the Finish Line. Here's what I'm trying to do: 2 abstract classes, RecruiterBase and CandidateBase, both have concrete capabilities RecruiterA and CandidateA. RecruiterBase has an abstract method so that all recruited candidates will return IQueryable. My RecruiterA implementation overrides the GetCandidates () method to return an IQueryable.

public abstract class RecruiterBase
{ 
  // Constructors declared here

  public abstract IQueryable<CandidateBase> GetCandidates();
}

public abstract class CandidateBase
{  
  // Constructors declared here
}

      

and implementation:

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

      

Trying to compile, which throws a compile-time error because in my RecruitreBase implementation, the GetCandidates () method returns > IQueryable<CandidateA>

instead IQueryable<CandidateBase

.

After not being able to get any suggestions for the previous question ( Generic Return Types from Abstract / Virtual Methods ) I did MORE more reading and came across the following question on SO

How do I return a subtype in an overridden subclass method in C #?

What ultimately made me realize what I was looking for was a way to implement covariance for my return type. I used a fragment by Mark Gravell ...

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

      

... as the basis for my solution. So now my RecruiterBase and RecruiterA classes look like this:

public abstract class RecruiterBase
{
  // Constructors declared here

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

  public abstract IQueryable<CandidateBase> GetCandidatesCore();
}

      

and my implementation ...

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

      

I was hoping that I would finally get what I was looking for, but I have a compile-time error in the following code because GetCandidates () cannot implicitly convert CandidateA to CandidateBase:

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

      

so I added the cast:

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

      

Everything compiles, but when I actually call GetCandidates () in my controller, it returns IQueryable<CandidateBase>

instead IQueryable<CandidateA>

. So I went back to where I started.

If you've done it completely and you can help me, I'll send you 12 packs of your favorite beer!

+2


source to share


2 answers


Justin. I'm a bit confused as to why you need to go through all this trouble.

If the abstract method has a return type , then this is what you get. I don't see a problem with this, as later you can still return it to Candidate or CandidateB IQueryable<CandidateBase>

So what exactly are you trying to achieve? I may not understand you.



Edit to add:

Justin, how about this?

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

      

+1


source


I think your intentions are good, but the result is that you are missing the point of polymorphic code and will also lose meaning.

The purpose of working with objects by their abstract type or their interfaces is to allow you to work with any particular implementation without having to know any specific implementation details. I believe your belief is that by returning concrete types you create better code when in fact you begin to deny the value of the abstract base class by closing the abstraction.



A well-constructed set of derived classes should have very few specific type solution needs; an abstract class should be sufficient to work with all implementations, and should handle the vast majority of work with those classes - exceptions should be in the minority.

0


source







All Articles