proskor
proskor

Reputation: 1412

Generic Type Inference in C#

Let's say there are these generic types in C#:

class Entity<KeyType>
{
    public KeyType Id { get; private set; }
    ...
}

interface IRepository<KeyType, EntityType> where EntityType : Entity<KeyType>
{
    EntityType Get(KeyType id);
    ...
}

and these concrete types:

class Person : Entity<int> { ... }

interface IPersonRepository : IRepository<int, Person> { ... }

Now the definition of PersonRepository is redundant: the fact that the KeyType of Person is int is stated explicitly, although it can be deduced from the fact that Person is a subtype of Entity<int>.

It would be nice to be able to define IPersonRepository like this:

interface IPersonRepository : IRepository<Person> { ... }

and let the compiler figure out that the KeyType is int. Is it possible?

Upvotes: 9

Views: 577

Answers (3)

Martijn
Martijn

Reputation: 12102

No, C#'s type system is not sufficiently advanced to express what you want. The feature needed is called higher kinded type which are often found in strongly typed functional languages (Haskell, OCaml, Scala).

Working our way back, you want to be able to write

interface IRepository<EntityType<EntityKey>> {
  EntityType<EntityKey> Get(KeyType id);
}

interface PersonRepository : IRepository<Person> {
  Person Get(Int id);
}

but in C# there is no way to express the kind EntityType or, in other words, that the type parameter has some generic parameter and use that generic parameter in your code.

Side note: The Repository pattern is Evil and must die in a fire.

Upvotes: 2

Kris Vandermotten
Kris Vandermotten

Reputation: 10201

Let's say we want to declare

interface IPersonRepository : IRepository<Person> { }

That would require that there is a generic interface with one type parameter IRepository<EntityType>.

interface IRepository<EntityType> where EntityType : Entity<KeyType>
{
    EntityType Get(KeyType id);
}

At the end of the first line, you refer to a thing called KeyType, which hasn't been declared nor defined. There is no type called "KeyType".

This would work though:

interface IRepository<EntityType> where EntityType : Entity<int>
{
    EntityType Get(int id);
}

Or this:

interface IRepository<EntityType> where EntityType : Entity<string>
{
    EntityType Get(string id);
}

But you cannot have both conflicting definitions at the same time of course. Obviously, you're not happy with that, because you want to be able to define your IRpository interface in such a way that it works with other key types as well.

Well, you can, if you make it generic in the key type:

interface IRepository<KeyType, EntityType> where EntityType : Entity<KeyType>
{
    EntityType Get(KeyType id);
}

There is an alternative approach:

interface IRepository<KeyType>
{
    EntityType<KeyType> Get(KeyType id);
}

Now you can define

class PersonRepository : IRepository<int>
{
    public EntityType<int> Get(int id) { ... }
}

Obviously, you wouldn't be happy with that, because you would like to state that the Get method must return a Person, not just any Entity<int>.

The generic interface with two type parameters in the only solution. And indeed, there is a required relationship between them, as expressed in the constraint. But there is no redundancy here: specifying int for the type parameter doesn't carry enough information.

If we say

class PersonRepository : IRepository<int, Person>
{
    public Person Get(int id) { ... }
}

There is indeed redundancy: specifying the type parameter int is redundant when the type parameter Person has already been specified.

It would be possible to come op with a syntax that make it possible to infer the KeyType. For example, Patrick Hoffman suggested:

class PersonRepository : IRepository<EntityType: Person>. 
{
    public Person Get(int id) { ... }
}

While theoretically possible, I fear that this would add a lot of complexity to the language specification and the compiler, for very little gain. In fact, is there any gain at all? You certainly wouldn't be saving keystrokes! Compare these two:

// current syntax
class PersonRepository : IRepository<int, Person>
{
    public Person Get(int id) { ... }
}

// proposed syntax
class PersonRepository : IRepository<EntityType: Person>
{
    public Person Get(int id) { ... }
}

The language is what it is, and it doesn't look too bad to me.

Upvotes: 1

Patrick Hofman
Patrick Hofman

Reputation: 157118

No, that is not possible to write that, since it doesn't infer the type (the compiler doesn't).

It should be possible to do so (you need to be part of the C# compiler team though to get it in), since there is no other value possible that the value of KeyType put into the type parameter for Entity. You can't put in a derived type or a base class type.

As others commented though, it might over complicate your code. Also, this only works in the case that Entity<T> is a class, when it is an interface it can't infer the type since it can have multiple implementations. (Maybe that is the ultimate reason they didn't build this in)

Upvotes: -1

Related Questions