Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# generic method with interface type constraint

Let's assume I have:

  • A generic method Get<T>
  • A few interfaces IEntity, IValue
  • A few classes that respectively implements those interfaces ex: Entity -> IEntity, Value -> IValue etc.

=> Is there a way for the Get<T> method to allow the interfaces only as generic Types?

Get<IEntity>(42); //Allowed
Get<Entity>(42);  //Compiler error

My current solution looks like this:

  • A generic method Get<T> with a Type constraint where T: IPersistable (to prevent most of the types to be passed as a parameter)
  • The interfaces implement IPersistable

The function actively checks the type:

public T Get<T>(long id) where T : IPersistable
{
   if (typeof (T) == typeof (IEntity))
      return (T) EntityDao.Get(id);
   if (typeof (T) == typeof (IValue))
      return (T) ValueDao.Get(id);

   //...

   throw new TechnicalException("Type not supported");
}

=> The problem are:

  1. It is not clean... I could live with that since there are only very few types to check from
  2. The signature does not match what the function really does. It allows an IPersistable in, but not really <- that really bugs me :(

Edit: I'm considering such constraints to avoid surpopulation of my class.

I have something like 8 or 9 generic methods in that class that all pretty much work this way. The intuitive way of doing would be as @DanielHilgarth suggested to have 1 method per type only. The methods currently can be called with 4 or 5 types only. But still, that would mean 32-40 methods in that class.

If possible I'd like to avoid that.

Edit2: the need for preventing "real" classes to be called comes from a covariance/contravariance problem. The EntityDao and ValueDao Get<T> methods return IEntity and IValue objects. What works fine when I query for a single object fails when I call for a collection in a GetAll<T> methods since I cannot cast an IEnumerable<IValue> in an IEnumerable<Value>.

I just noticed this answer from @JonSkeets about casting of lists. That could be a workaround...

like image 643
Timothée Bourguignon Avatar asked Aug 16 '26 15:08

Timothée Bourguignon


2 Answers

You should just create dedicated methods. The sample code with the if shows that your current method doesn't do one thing. It does multiple.

Just go with:

GetEntity(42);
GetValue(13);

public IEntity GetEntity(long id)
{
    return EntityDao.Get(id);
}

public IValue GetValue(long id)
{
    return ValueDao.Get(id);
}

This is a lot cleaner on all layers:

  1. GetEntity vs. Get<IEntity>
  2. You clearly communicate what is possible. You don't have any runtime exceptions.
  3. Your get methods don't need any type switching.

If this will result in too many similar methods on your service, it is time to break out new classes, e.g. one for Entity and one for Value. Then you could give your service properties that return the new classes. That's the same I am doing when implementing my query objects. It could then look like this: service.Values.Get(13) and service.Entities.Get(42)

like image 130
Daniel Hilgarth Avatar answered Aug 19 '26 17:08

Daniel Hilgarth


This may be an alternative:

abstract class Persistable<T>
{
    protected static Func<long, T> mapFunction;
    public static T Get(long id)
    {
        return mapFunction(id);
    }
}

class Entity : Persistable<Entity>
{
    public static Entity()
    {
        Persistable<Entity>.mapFunction = input => EntityDao.Get(input);
    }
}
class Value : Persistable<Value>
{
    public static Value()
    {
        Persistable<Value>.mapFunction = input => ValueDao.Get(input);
    }
}

The your Get method will be something like this:

public T Get<T>(long id) // maybe you restrict the T to something
{
    Persistable<T>.Get(id);
}
like image 37
Hossein Narimani Rad Avatar answered Aug 19 '26 17:08

Hossein Narimani Rad