简体   繁体   中英

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.

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) .

Take a look at the errors:

'DataSourceOrder.GetOrderPositionOfOrder()' does not define type parameter 'TOrder' 'DataSourceOrder.GetOrderPositionOfOrder()' does not define type parameter 'TArtile'

You are referring to type parameters that do not exist.
You're supposed to define them in the method, the same way you're defining them in the interface:

public static List<TOrderPosition> GetOrderPositionOfOrder<TOrder, TArticle, TOrderPosition>(long? id)

That means calling the method will be kinda ugly:

var positions = GetOrderPositionOfOrder<Order, Position, OrderPosition>(5);
var transferPositions = GetOrderPositionOfOrder<TransferOrder, TransferArticle, TransferOrderPosition>(5);

When you call the method, you must provide all the type parameters, or none (if they can be inferred). That's just the way it is.

In the interface, you define it as a generic accepting 3 types TOrder, TArticle, TOrderPosition , so you are able to constrain those types.

Your method only defines a single type, TOrderPosition , and the compiler cannot infer the fact that you need other types from the the constraint where TOrderPosition : IOrderPosition<TOrder, TArticle, TOrderPosition> in your method definition.

What you need to do is define all the types on your generic method in the same way you did for your interface:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM