Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

A declaration like

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

demands that IdT is an concrete type. If you want to parameterize IdT as well, you'd need to use

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

But that's probably not what you'd want.

This is why I've all but given up on generic key types with generic entities. I could not figure out how to get my entities to have generic key types without sprinkling the two all over the place. Now I've settled on integer keys (which is what I have everywhere anyway) but it feels wrong.

If your method signature looked like this:

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

The compiler would have something to work with...

You then call get with something like:

EDIT (I was wrong): Product p = Get(id);

Product p = Get<Product, Guid>(id);

Jon's nailed this answer with his post up top so I'll shut up and crawl back in my hole.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top