Search code examples
c#genericsnested-genericsgeneric-constraints

How to constrain nested generic types of a generic method


I'm trying to create a method which returns data from the database based on the given generic type.

The interface: (this definition compiles)

public interface IOrderPosition<TOrder, TArticle, TOrderPosition>
  where TOrder : IOrder
  where TArtile : IArticle
  where TOrderPosition : IOrderPosition<TOrder, TArticle, TOrderPosition>
{
  long? id { get; set; }
  TOrder order { get; set; }
  TArtile article { get; set; }
  List<TOrderPosition> subPositions { get; set; }
}

A possible concrete implementation: (this definition compiles)

public class OrderPosition : IOrderPosition<Order, Article, OrderPosition>
{
  public long? id { get; set; }
  public Order order { get; set; }
  public Article article { get; set; }
  public List<OrderPosition> subPositions { get; set; }
}

Trying to write a generic method based on the interface: (this definition DOES NOT compile)

public List<TOrderPosition> GetOrderPositionOfOrder<TOrderPosition>(long? id) 
  where TOrder : IOrder
  where TArticle : IArticle
  where TOrderPosition : IOrderPosition<TOrder, TArticle, TOrderPosition>
{
  ..
}

Errors:

'DataSourceOrder.GetOrderPositionOfOrder<TOrderPosition>()' does not define type parameter 'TOrder'
'DataSourceOrder.GetOrderPositionOfOrder<TOrderPosition>()' does not define type parameter 'TArticle'
The type or namespace name 'TOrder' could not be found (are you missing a using directive or an assembly reference?)
The type or namespace name 'TArticle' could not be found (are you missing a using directive or an assembly reference?)

To be used like this:

List<OrderPosition> positions = GetOrderPositionOfOrder<OrderPosition>(5);
List<TransferOrderPosition> transferPositions = GetOrderPositionOfOrder<TransferOrderPosition>(5);

Question:

Why does this compile for the interface, but not for the method?

I expected both to work or both to fail. I assumed that the compile could infer the types of TOrder and TArticle from the type given for TOrderPosition which defines concrete types for both the article and order.

I would like to know why this happens and if and how I can solve the problem without having to specify all types explicitly.


Solution

  • Why does this compile for the interface, but not for the method?

    Well, you are declaring TOrder and TArticle in IOrderPosition interface but not in GetOrderPositionOfOrder method.

    You need to declare these generic parameters in method declaration:

    public List<TOrderPosition> GetOrderPositionOfOrder<TOrder, TArticle, TOrderPosition>(long? id)
        where TOrder : IOrder
        where TArticle : IArticle
        where TOrderPosition : IOrderPosition<TOrder, TArticle, TOrderPosition>
    {
        ...
    }
    

    And call it like this:

    var list = GetOrderPositionOfOrder<Order, Article, OrderPosition>(5);
    

    But if you want to call GetOrderPositionOfOrder like:

    var list = GetOrderPositionOfOrder<OrderPosition>(5);
    

    You can make IOrderPosition covariant in TOrder and TArticle:

    interface IOrderPosition<out TOrder, out TArticle, TOrderPosition>
        where TOrder : IOrder
        where TArticle : IArticle
        where TOrderPosition : IOrderPosition<TOrder, TArticle, TOrderPosition>
    {
        long? id { get; set; }
        TOrder order { get; }
        TArticle Article { get; }
        List<TOrderPosition> subPositions { get; set; }
    }
    

    Note that Order and Article must be getter-only properties (but these properties in OrderPosition can have set accessor).

    And the method:

    public List<TOrderPosition> GetOrderPositionOfOrder<TOrderPosition>(long? id)
        where TOrderPosition : IOrderPosition<IOrder, IArticle, TOrderPosition>
    {
        ...
    }
    

    Doing this you can make the calls like GetOrderPositionOfOrder<OrderPosition>(5).