Inference from generic question type


I suppose this is more of a public rant, but why can't I get c# to infer my Id's type?

public EntityT Get<EntityT>(IdT id) where EntityT : EntityObject<IdT>

and a defined EntityObject with a Guid as an Id as follows:

public Foo : EntityObject<Guid>

Inheriting from the abstract EntityObject class defined as follows:

public abstract class EntityObject<IdT>
    public IdT id { get; set; }

Usage of the get method would be as follows:

IRepository repository = new Repository();
var hydratedFoo = repository.Get<Foo>(someGuidId);

edited to provide further clarification.

It's hard to say given that you've only given two declarations, not how you're using them. Is IdT another type parameter somewhere? (If it were TId, that would suggest it is - but the fact that you're using EntityT for another type parameter, contrary to conventions, suggests that maybe IdT is as well...)

Now, assuming IdT is actually Guid in your case, how should the compiler work out that you mean Foo? There could be other types deriving from EntityObject<Guid>.

In short, you haven't given us enough information to tell anything for sure, but it sounds like you're basically making unreasonable demands on the compiler.

EDIT: Okay, here's my guess at what you have, using normal naming conventions:

public interface IRepository
    TEntity Get<TEntity, TId>(TId id) where TEntity : EntityObject<TId>

public abstract class EntityObject<TId>
    public IdT id { get; set; }

public class Foo : EntityObject<Guid> {}

You want to do:

IRepository repository = GetRepositoryFromSomewhere();
Foo foo = repository.Get<Foo>(someGuid);

Whereas currently you have to do:

Foo foo = repository.Get<Foo, Guid>(someGuid);

Yes, the compiler is making it very slightly harder for you than necessary. A whole 6 extra characters, for the sake of keeping the language simpler and the rules of type inference easier to understand.

Basically type inference is an all or nothing affair - either all type parameters are inferred or none of them is. That keeps it simple as you don't need to work out which ones are being specified and which aren't. That's part of the problem, and the other part is that you can only express constraints on the type parameters of the method - you can't have:

class Repository<TEntity>
    TEntity Get<TId>(TId id) where TEntity : EntityObject<TId>

because that's constraining TEntity, not TId. Again, this sort of thing makes type inference simpler.

Now you could potentially write:

Foo foo = repository.Get(someGuid).For<Foo>();

with an appropriate Get method and an extra interface. I think I'd personally prefer to just use Get<Foo, Guid> though.