Reputation: 32468
I have an interface (which is used by repositories) that has this member:
T FindById<T, TId>(TId id)
where T : class, IEntity<TId>
where TId : IEquatable<TId>;
This allows the caller to specify an entity type (T
) and the type of it's Id
field (TId
). The implementor of this interface would then find the entities of type T
and use the id
parameter to filter them according to their id (which is defined on IEntity<TId>
).
Currently I'm calling it like this:
int id = 123;
var myApproval = PartsDC.FindById<Approval, int>(id);
Ideally I'd like to do this:
int id = 123;
var myApproval = PartsDC.FindById<Approval>(id);
I've read the answers for this question:
Partial generic type inference possible in C#?
I understand I can't get the syntax I want, but can get close. I can't quite get it setup right in my case though because of my generic parameter constraints.
Here's what I have so far:
public class FindIdWrapper<T> where T : class
{
public readonly IDataContext InvokeOn;
public FindIdWrapper(IDataContext invokeOn)
{
InvokeOn = invokeOn;
}
T ById<TId>(TId id) where TId : IEquatable<TId>
{
return InvokeOn.FindById<T, TId>(id);
}
}
public static class DataContextExtensions
{
public static FindIdWrapper<T> Find<T>(this IDataContext dataContext) where T : class, IEntity
{
return new FindIdWrapper<T>(dataContext);
}
}
The compilation error I get is:
The type 'T' cannot be used as type parameter 'T' in the generic type or method 'PartsLegislation.Repository.IDataContext.FindById<T,TId>(TId)'. There is no implicit reference conversion from 'T' to 'PartsLegislation.Repository.IEntity<TId>'.
I understand what it's saying because the T
in my wrapper class is only constrained to be a reference type, but the FindById
function wants it to be IEntity<TId>
, but I can't do that as the TId
is in the method (otherwise I'm back at square one).
How can I get around this issue (or can't I)?
Upvotes: 13
Views: 1258
Reputation: 1064324
That can't work the usual way around because you can't convince the compiler of the TId
constraint after the fact. You can, however, reverse the sequence, i.e.
var obj = ById(id).Find<SomeType>();
Not as elegant, but it works. Implementation:
public Finder<TId> ById<TId>(TId id) where TId : IEquatable<TId>
{
return new Finder<TId>(this, id);
}
public struct Finder<TId> where TId : IEquatable<TId>
{
private readonly YourParent parent;
private readonly TId id;
internal Finder(YourParent parent, TId id)
{
this.id = id;
this.parent = parent;
}
public T Find<T>() where T : class, IEntity<TId>
{
return parent.FindById<T, TId>(id);
}
}
caveat: it is probably easier just to tell it both the parameter types explicitly.
Upvotes: 8