Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to have a generic constraint requiring an open generic interface?

I'm implementing a repository and keep wondering about making it a bit more friendly to the user. Right now I have an IEntity interface that specifies an Id field:

public interface IEntity<T>
{
    T Id { get; set; }
}

And my repository allows users to get an new instance by that id. Now the types it can handle need to implement the IEntity interface, so I have a generic constraint on the repository Get method:

public class Repository
{
    public T Get<T, U>(U id) where T: IEntity<U>
    {
        // fake implementation, but T is required at compile time
        var result = Activator.CreateInstance<T>();
        result.Id = id;
        return result;
    }
}

There's an obvious relation between T and U and compiler understands it well enough to flag miss-usages, but not enough to enable type inference - each call to Get requires specifying the generic parameters explicitly. I know there's no way around specifying T, but how can I improve the method signature so that specifying U is not required? Right now I have an overload for the most common usage:

public T Get<T>(int id) where T : IEntity<int>
{
    return Get<T, int>(id);
}

I'm wondering if it is possible to somehow specify an open generic interface as a constraint or what would be a better method signature for the general case.

like image 470
skolima Avatar asked Aug 14 '14 08:08

skolima


2 Answers

After reading Partial generic type inference possible in C#? and Working around lack of partial generic type inference with constraints, I'm thinking that Marc Gravell's solution is the closest to any reasonable. Taking his partial generic parameter application via a helper class (which is used to capture the type of the first parameter) and the extension method inference that Grax suggested, I end up with a Repository implementation of

public class Repository
{
    public T Get<T, TId>(TId id) where T: IEntity<TId>
    {
        // fake implementation, but T is required at compile time
        var result = Activator.CreateInstance<T>();
        result.Id = id;
        return result;
    }

    public GetHelper<T> Get<T>()
    {
        return new GetHelper<T>(this);
    }
}

with a helper

public struct GetHelper<T>
{
    internal readonly Repository Repository;

    public GetHelper(Repository repository)
    {
        Repository = repository;
    }
}

public static class RepositoryExtensions
{
    public static T ById<T, TId>(this GetHelper<T> helper, TId id)
      where T : IEntity<TId>
    {
        return helper.Repository.Get<T, TId>(id);
    }
}

The usage then looks like that:

var intEntity = repository.Get<IntEntity>().ById(19);
var guidEndtity = repository.Get<GuidEntity>().ById(Guid.Empty);

As far as I understand how generic parameter inference works in C# right now, it's not possible to get a partial inference.

like image 89
skolima Avatar answered Oct 22 '22 15:10

skolima


You can do something fun with extension methods here.

public static class Extensions
{
    public static T Get<T>()
    {
        // fake implementation, but T is required at compile time
        var result = Activator.CreateInstance<T>();
        return result;
    }

    public static T AssignId<T, U>(this T entity, U id)
        where T : IEntity<U>
    {
        entity.Id = id;
        return entity;
    }
}

This would be called as follows.

var result = Extensions.Get<EntityInt>().AssignId(34);

var result2 = Extensions.Get<EntityString>().AssignId("WALKEN");

You can put your get method anywhere you like but the AssignId method would have to be in a class that is qualified to have extension methods, i.e. it must be a static non-generic class and you may need a using statement that refers to the namespace containing the extension method.

like image 24
Grax32 Avatar answered Oct 22 '22 16:10

Grax32