ADringer
ADringer

Reputation: 2834

Creating generic with subclass

I'm trying to create a generic way to hold what property I should search against given a class.

I have created the following struct to hold this information:

public struct Lookup<T> where T: BaseEntity
{            
    public Expression<Func<T, object>> Parent { get; set; }
}

And then have created a dictionary as followed:

readonly Dictionary<string, Lookup<BaseEntity>> _dict =
    new Dictionary<string, Lookup<BaseEntity>>();

I have a class that uses the BaseEntity as a base class:

public class Agency : BaseEntity
{
    public int AgencyId { get; set; }
}

I create a new Lookup struct with this information:

new Lookup<Agency>
{
    Parent = x => x.Id
};

But if I try adding this to my dictionary I get the following error:

Cannot convert from Lookup<Agency> to Lookup<BaseEntity>

I would have thought that this would work as they are of the same base type, what is the way round this.

I have quite a number of entities to add so was hoping to do it generically rather than having to do if/switch etc.

Thanks

Upvotes: 3

Views: 103

Answers (1)

Luaan
Luaan

Reputation: 63772

No, Lookup<BaseEntity> is not a base type of Lookup<Agency>. The fact that Agency is derived from BaseEntity isn't enough. But first, let's look at something else.

Think about the usual variance rules. Your method corresponds to

object DoSomething(T x) { ... }

Now, in Lookup<BaseEntity>, this becomes

object DoSomething(BaseEntity x) { ... }

So far, so good - it's perfectly legal to pass Agency in place of BaseEntity, since Agency derives from BaseEntity. However, that's not the case you have. You created this:

object DoSomething(Agency x) { ... }

Obviously, you can't pass BaseEntity instead of Agency - that would break the typing completely. And unlike Java, C#'s (and .NET's in general) generics are real in both compile-time and runtime, they don't fall back to Lookup<BaseEntity>.

Additionally, C#/.NET limits the use of variance. You get variance for free in simple expressions and delegates, but not in classes or generic classes. You need to be explicit about the kind of variance you want, and use an interface. For example, in your case, you'd have something like this:

public interface ILookup<out T>

This would allow you to do the cast from ILookup<Agency> to ILookup<BaseEntity>. However, it's not enough for your case, since T is both co-variant and contra-variant in your case - in other words, you can't cast in either direction.

However, this is not a big deal, since you're overcomplicating things a bit. While you need strong typing to create Lookup<Agency>, you don't need it in the type itself - you're going to be working with the untyped Expressions anyway:

public sealed class Lookup
{
  private readonly Expression parent;

  private Lookup(Expression parent)
  {
    this.parent = parent;
  }

  public Expression Parent { get { return parent; } }

  public static Lookup For<T>(Expression<Func<T, object>> member)
    where T : BaseEntity
  {
    return new Lookup(member);
  }
}

Upvotes: 3

Related Questions