8

Suppose we write a system for tests. Test contains the list of tasks and each task contains question and the list of answers. Also we assume that question or answer can be not only the text, but image for example. So we use generics:

public interface IQuestion<T>
{
    T Content { get; }
}

public interface IAnswer<T>
{
    T Content { get; }
    bool IsCorrect { get; }
}

And the problem occurs when we creating the Task:

interface ITask<TQuestion, TAnswer> 
{
    TQuestion Question { get; }
    List<TAnswer> Answers { get; }
}

How to write that TQuestion should be subtype of IQuestion and TAnswer - subtype of IAnswer?

I've tryed:

interface ITask<TQuestion, TAnswer> 
    where TQuestion : IQuestion<object>
    where TAnswer : IAnswer<object>

But when I created:

class TextQuestion : IQuestion<string> {...}
class TextAnswer : IAnswer<string> {...}

This did not work:

class TextTask : ITask<TextQuestion, TextAnswer>

Becouse, in fact, IQuestion<string> don't inherited from IQuestion<object>.

In Java, I would use wildcards in restriction of ITask generic types, in Kotlin, the above approach would have worked.

But how to solve it using C#?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Letfar
  • 3,253
  • 6
  • 25
  • 35

2 Answers2

4

In the way I understand the question, you can formulate the constraint by introduction of an additional type parameter in ITask as follows, omitting the type parameters TQuestion and TAnswer used before.

interface ITask<T> 
{
    IQuestion<T> Question { get; }
    List<IAnswer<T>> Answers { get; }
}
Codor
  • 17,447
  • 9
  • 29
  • 56
4

You need a third parameter:

interface ITask<TQuestion, TAnswer, T> 
    where TQuestion : IQuestion<T>
    where TAnswer : IAnswer<T>

As you know, IQuestion<string> don't inherited from IQuestion<object>, but this way you can have TQuestion be IQuestion<string>.


Addendum: having TQuestion be IQuestion<object> is only a problem because IQuestion doesn't have variance defined (so, it is invariant by default). If you define as I show below, you can useIQuestion<object> (the same goes for IAnswer).

public interface IQuestion<out T>
{
    T Content { get; }
}

public interface IAnswer<out T>
{
    T Content { get; }
    bool IsCorrect { get; }
}

interface ITask<TQuestion, TAnswer> 
    where TQuestion : IQuestion<object>
    where TAnswer : IAnswer<object>
{
    TQuestion Question { get; }
    List<TAnswer> Answers { get; }
}
Theraot
  • 31,890
  • 5
  • 57
  • 86
  • 1
    Interesting answer; Jon Skeet explained why you have to explicitly say you want variance in generics in his book C# in depth (btw, it's a must read). In short, it's to avoid type mismatch at execution time. You can see this answer too: http://stackoverflow.com/a/246101/574059 and this article by Eric Lippert : https://blogs.msdn.microsoft.com/ericlippert/2007/10/16/covariance-and-contravariance-in-c-part-one/ – gobes Feb 23 '17 at 08:57
  • @gobes I'm aware of the reason behind. This falls back to what OP wants, for instance using variance in this case will be less restrictive than using a third parameter (more akin to what Java does), Codor's solution is somewhere in the middle. And thanks for the links. – Theraot Feb 23 '17 at 09:04
  • That's it! Using variance, I got what I wanted initially: `class TextTask : ITask`. – Letfar Feb 23 '17 at 09:11