views:

202

answers:

4

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.

+3  A: 

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.

Jon Skeet
Jon, I apologize for not adding more details. Again this was a more off the cuff rant rather than a truly legitimate question. But imho the compiler should be able to determine IdT from the foo object at compile time.Most likely it is my assumptions for generics that is leading me to a flawed interpretation of how the compiler could/should read this, but I assumed that the generic type was not determined until compile time at which point the compiler would then link the templated object. Assuming that, wouldn't it be one step further to determine the type of the referenced object?
Raspar
Generics != Templates. You could probably get a C++ compiler to "infer" that sort of thing, but as long as generics are runtime I don't see it happening without a more explicit generic definition.
sixlettervariables
IdT isn't a type parameter of `Get` - that only has one type parameter, `EntityT`. You haven't given the declaration of IRepository, or what isn't working for you. Please give a *complete* example, showing what you're *trying* to do and telling us what happens instead of what you want.
Jon Skeet
I prefer to use something likepublic class RepositoryBase<TEntity, TKey> { }public class FooRepository : RepositoryBase<Foo, Guid> { }
KeeperOfTheSoul
@KeeperOfTheSoul: Agreed - but how would you then use type inference? I was trying to disturb the original design as little as possible.
Jon Skeet
A: 

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.

grenade
This certainly works, but makes it painfully obvious to everyone that Foo's key is a Guid.
n8wrl
@n8wrl I don't understand your comment.
grenade
True Rob, I appreciate your answer however Repository.Get<Foo, Guid>(someGuid) would require the developer to know the Id type of each and every entity. Also it would lack the syntactic sweetness that Repository.Get<Foo>(someGuid) would have.
Raspar
I don't see how this is a problem...even in C++ you'd need that.
sixlettervariables
@Rob: What Raspar said :)
n8wrl
grenade
usage: Product p = Get(id);
grenade
@Rob: I'm not 100% sure but I don't think that the compiler can infer the type through Product p = Get(id); It will have to be as follows: Product p = Get<Product, Guid>(id); I'll throw up a unit test to see but I'm pretty sure that this is the case.
Raspar
Make sure you post your learnings from the unit test!
grenade
@Rob: The type arguments for method 'Repository.Get<ENTITY,IdT>(IdT)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
Raspar
A: 

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.

Ruben
A: 

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.

n8wrl
Exactly! For the time being we've been using Guids, but now that we need to incorporate a legacy database we are now dealing with the idea of a composite-id. yuck.
Raspar
You want to discover type inference. You don't need to be explicit when calling a generic method.
grenade
@Rob: Well sometimes you don't, and sometimes you do. It depends on the exact situation.
Jon Skeet
As ever Jon, you are right.
grenade