7

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.

skolima
  • 31,963
  • 27
  • 115
  • 151
  • How about changing it into `T Get(T instance, U id) where T: IEntity` and call it like `return Get(default(T), id);` ? – Yuliam Chandra Aug 14 '14 at 08:35
  • 1
    See [this question](http://stackoverflow.com/questions/2893698/partial-generic-type-inference-possible-in-c) – Nico Schertler Aug 14 '14 at 08:50
  • 4
    Consider this - you could have a type, `T` that implements the `IEntity` interface *multiple* times, with different `U` types. How is the compiler to pick the bones out of that? – Damien_The_Unbeliever Aug 14 '14 at 09:24

2 Answers2

4

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.

Community
  • 1
  • 1
skolima
  • 31,963
  • 27
  • 115
  • 151
2

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.

Grax32
  • 3,986
  • 1
  • 17
  • 32