3

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.

Holly
  • 1,305
  • 2
  • 15
  • 30
  • 3
    But you don't define TOrder and TArticle generic parameters in your method, otherwise it would compile. And for class you specify all three (TOrder, TArtile, TOrderPosition) – Evk Nov 24 '16 at 15:33
  • @Evk looking at the answers it becomes quite obvious that this could not have worked, I had just hoped that the compiler would somehow infer the missing types from the type of `OrderPosition` since I want to avoid having to list all types (there are actually more than those 3) – Holly Nov 24 '16 at 23:24

3 Answers3

5

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

Holly
  • 1,305
  • 2
  • 15
  • 30
Arturo Menchaca
  • 15,783
  • 1
  • 29
  • 53
  • thank yous for showing a way where I do not need to declare all the types explicitly, since that takes away the elegance of the whole solution, sadly it will likely not help me, since I need it for .Net 3.5 CF, I also need to set those values that method not read them – Holly Nov 24 '16 at 23:20
  • @Holly: Uhmm, then maybe you can create a base interface like `IOrderPosition where TOrderPosition: IOrderPosition` (with `id` and `subPositions` properties if you want) and your current interface inherits from this one. Then you can declare method like this: `GetOrderPositionOfOrder(long? id) where TOrderPosition : IOrderPosition` – Arturo Menchaca Nov 25 '16 at 16:22
  • 1
    thanks for the suggestion, I have been considering the same thing but am not sure it's then even worth the trouble in the first place since I would need to reflect all the types and cast and so on, I now read that nested generic types do not get inferred (which I assumed), I even tried providing an instance of the concrete type of OrderPosition as a parameter in hopes to infer all the type parameters, but that did not work either - now I kinda miss JAVAs `? extends ClassName` ;-) – Holly Nov 25 '16 at 16:42
2

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.

Dennis_E
  • 8,751
  • 23
  • 29
0

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>
{
 ..
}
Holly
  • 1,305
  • 2
  • 15
  • 30
KMoussa
  • 1,568
  • 7
  • 11