Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Working around lack of partial generic type inference with constraints

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)?

like image 292
George Duckett Avatar asked May 10 '13 09:05

George Duckett


1 Answers

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.

like image 160
Marc Gravell Avatar answered Nov 15 '22 16:11

Marc Gravell