Reputation: 2834
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
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 Expression
s 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