2

Original Question

I tried to make a generic class for my web requests

internal class Request<TRequest, TResponse>
    where TRequest : class
    where TResponse : class
{
    public Uri RequestUri;
    public TRequest RequestItem;
    public TResponse ResponseItem;
    ...
}

The types TRequest and TResponse are used to serialize and deserialize with XML, JSON, etc.

Now I want to store those Request<> in a list for queuing, caching and error handling. So I tried to declare:

private List<Request<object, object>> requests;

But then, I can't add anything to this list of requests because when I try:

var a = new Request<FooRequest, BarResponse>();
requests.Add(a);

I get:

cannot convert from My.Services.Request<FooRequest,BarResponse> to My.Services.Request<object,object>

Is there a way to have a typed list of mixed typed requests (Request<a,b> and Request<c,d>)? Or a better approach to handle this?

I'm coding for .NET 4.5 (Windows Phone, Windows Store).


First Edit

After many comments, a couple of unsatisfying answers, and a duplicate vote, I will review below what I tried based on all this:

  • Using an interface IRequest<out T, out T1>

    Well, interfaces can't have a field. So I would have to cast the item of the list to an instance of Request<> to get access to what I want. I'm not sure how to write this cast to retrieve the two variants (TRequest and TResponse), help would be appreciated. But by systematically casting, isn't it the same as using List<object> in the end?

    Or I could set properties in the interface. But then it tells me I can't use the keyword out:

    Invalid variance: The type parameter 'TRequest' must be invariantly valid on My.Services.IRequest<TRequest,TResponse>.RequestItem. 'TRequest' is covariant.

    So I could remove the keyword out, but when trying to add an item to the list it tells me again:

    cannot convert from My.Services.Request<FooRequest,BarResponse> to My.Services.IRequest<object,object>.

  • Using an abstract class BaseRequest

    Almost same issue, my base class can't have a field of type TRequest or TResponse; how to retrieve the two variants? I'll need to cast the item of the list. And so List<object> would be just as good.

  • Using generics wildcards

    Doesn't exist and probably never will.

  • Check C# Covariance

    Too vague answer provided and nothing to achieve it. Not even a link to MSDN. I'm explicitly talking about List<> which is a covariant type. Solving this question is not easy as man google.

  • Checking thread: A generic list of anonymous class

    Absolutely not a duplicate, as question/answers are not meant for storing to a typed field/property:

    var list = new[] { a }.ToList();
    

    This is simply a List<Request<TRequest, TResponse>> and unfortunately, I can't declare anything with type var in a class, outside of a method.

  • List<object> or List<dynamic>

    Again, not an example of List<Class<T>>. But let's try List<Class<dynamic,dynamic>>... Failure:

    cannot convert from My.Services.Request<TRequest,TResponse> to My.Services.Request<dynamic,dynamic>

  • Checking thread: C# - Multiple generic types in one list

    This gets closer to my issue.

    -> Interesting, Saeb made the same comment as I regarding most answers with it's all the same as List<object>

    -> Now, Bryan Watts, with the least votes of all answers, found an interesting way to retrieve TRequest and TResponse. He declared this in the interface/baseclass:

    Type DataType { get; }
    object Data { get; }
    

    That solves the 'how to retrieve the variants' issue.

First conclusion

As I will need to get the variants, I will need to implement some Type RequestType and Type ResponseType. Which means the <TRequest,TResponse> part will be useless. Which means I will go with a List<Request> only and the answer to my Stackoverflow question is: no, you can't use/benefit from storing a List<Class<T>> in C#.


Second Edit

I failed at implementing Bryan Watts solution: apparently it is not possible to use a runtime-type for generic methods. So I can't write Deserialize<request.RequestDataType, request.ResponseDataType>(request). So I'm stuck: if I add my requests to a list of requests, I'm having runtime types and I cannot use the generic methods anymore. I didn't know generic methods were so inconvenient. :(

Second conclusion

I will have a List<Request> and each Request will hold an enum for the kind of request, then I will switch on this enum and write down manually all possible request calls:

switch (@req.RequestType)
{
    case RequestType.FirstKindOfRequest:
        await Deserialize<FirstKindOfRequest, FirstKindOfResponse>(req);
        break;
    case RequestType.SecondKindOfRequest:
        ...
}

That's sadly the only way I could find to hold a list of Class<T> where T was supposed to be anything.

Community
  • 1
  • 1
Cœur
  • 37,241
  • 25
  • 195
  • 267
  • 1
    See [A generic list of anonymous class](http://stackoverflow.com/questions/612689/a-generic-list-of-anonymous-class) – huMpty duMpty May 23 '14 at 10:33
  • 2
    What are you trying to do? Why do you want a strongly-typed Request but a weakly typed list of requests? What is the original problem you are trying to solve? – Panagiotis Kanavos May 23 '14 at 10:36
  • @PanagiotisKanavos everything about what I want to achieve is already written in the question, if you could take the time to read it carefully: "web requests with types for serialization, and I want a list of web requests for queuing, caching and error handling". ^_^ – Cœur May 23 '14 at 10:39
  • Turn `new Request()` into `new Request(Foo, Bar)`. – H H May 23 '14 at 10:41
  • @huMptyduMpty that will still create a list of a single class. That's not want is wanted here. – Dennis_E May 23 '14 at 10:42
  • @HenkHolterman this probably won't even compile. – rosko May 23 '14 at 10:42
  • @mrida yeah, this may be the problem. Good point. – rosko May 23 '14 at 10:43
  • If you let `Request` implement some interface, you could make a list of that interface-type. List is strongly typed, so what you want is not possible. `Request` is a different type than `Request` – Dennis_E May 23 '14 at 10:44
  • 1
    @rosko - you could make it compile, by changing `class Request{...}` – H H May 23 '14 at 10:45
  • @HenkHolterman if you change it to `class Request{...}` then yes you are right but you didn't include this change in your comment. – rosko May 23 '14 at 10:46
  • I voted to open this q. The ones linked are not close duplicates. – nawfal Jun 28 '14 at 13:14
  • I don't fully understand what you're saying that the beginning of 'Later Edition'. But your problem may be here: "Or I could set properties in the interface." What you want to do is have the properties in the *interface* have only `get`, no `set`. Whereas the implementing property in the class can have both `get` and `set` – Ben Aaronson Jun 30 '14 at 11:35

3 Answers3

2

This is a classic situation. Asked many times before too. I see two approaches.

-> The obvious choice would be to have a non-generic base class/interface which can account for all your request types, and then carry around List<NonGenericRequest>.

-> The second approach would be utilizing covariance. I see you face a few problem with it. I will try to solve it, one by one:

Well, interfaces can't have a field. So I would have to cast the item of the list to an instance of Request<> to get access to what I want.

Do not use fields. Don't even think of it. Switch to properties. I gotta reiterate the point. Interfaces - in fact all public APIs - deal with properties.

Or I could set properties in the interface. But then it tells me I can't use the keyword out

The problem is the setters of your properties. It breaks type safety. See this thread for more.

Which means the <TRequest,TResponse> part will be useless. Which means I will go with a List only and the answer to my Stackoverflow question is: no, you can't use/benefit from storing a List<Class<T>> in C#.

By now you can write something like var requests = new List<IRequest<object, object>>(). But this means you dont get strongly typed TRequest and TResponse. The solution is to get rid of generic type arguments TRequest and TResponse (ie your constraint where T : class) and include a more usable constraint. Something that is useful to you.


Combining them all, here we go:

interface IMessage //I will call it IMessage for now
{
    //implement whatever that make deserializing possible
}

class FooRequest : IMessage
{

}

class BarResponse : IMessage
{

}

interface IRequest<out TRequest, out TResponse>
    where TRequest : IMessage 
    where TResponse : IMessage 
{
    TRequest RequestItem { get; }
    TResponse ResponseItem { get; }
    //whatever fits, but only getters for out parameter types
}

class Request<TRequest, TResponse> : IRequest<TRequest, TResponse>
    where TRequest : IMessage 
    where TResponse : IMessage 
{
    //you can extend interface properties with setters here
}

So now you can write:

var requests = new List<IRequest<IMessage, IMessage>>();
var a = new Request<FooRequest, BarResponse>();
requests.Add(a);
foreach (var request in requests)
{
    Deserialize(request);
}

void Deserialize<TRequest, TResponse>(IRequest<TRequest, TResponse> request)
    where TRequest : IMessage
    where TResponse : IMessage
{
    //have access to request.RequestItem, request.ResponseItem etc
}

Note: For simplicity I haved assumed both TRequst and TResponse are similar types and hence both of them have evolved from IMessage. You can have separate IRequest and IResponse type interfaces for them.

Community
  • 1
  • 1
nawfal
  • 70,104
  • 56
  • 326
  • 368
1

I think that generics wildcards could help you there: http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2633766-wildcard-generic

As this is not going to happen at least in the near future, I see three options:

  • You can vote, wait, and hope Microsoft will adopt wildcards in generics...
  • As Mrida commented, you can convert your code to use interfaces and then use C# covariance.
  • If covariance is not an acceptable solution, you could define a non generic base class RequestBase with only the non-generic functionality, and make a list of RequestBase (edit: I think that this is HenkHolterman 's comment). Alternatively if you don't want to change the Request class you can define a non generic wrapper with the non-generic functionality.
MaMazav
  • 1,773
  • 3
  • 19
  • 33
  • I don't see how wildcards could be used here in a way that maintains compile-time type safety. – Ben Aaronson Jun 30 '14 at 11:39
  • What exactly do you mean when you say "compile-time type safety"? I guess you don't say that Java does not preserve type safety. – MaMazav Jul 01 '14 at 16:50
  • How are you suggesting wildcards could be used here, if they were included in C#? As far as I'm aware, the lack of wildcards doesn't restrict functionality in C# because you can just make the class or method generic instead. – Ben Aaronson Jul 01 '14 at 23:08
  • The fact that you could bypass the lack of wildcards by generics, doesn't mean that wildcards are not a good solution too... Particularly, if you look at the first part of the question (the only one that was exist when my answer was written) you would see that his question is "Is there a way to have a typed List of mixed typed requests". Wildcards comes exactly to solve such problems. Notice that he is going to use it for serialization purpose, for which you don't have any cast and type safety. – MaMazav Jul 02 '14 at 04:14
1

You can take advantage of interface variance, by extracting a variant IRequest interface:

interface IRequest<out T, out T1>
{
}

and implement it in your Request class:

class Request<TRequest, TResponse> : IRequest<TRequest, TResponse>
    where TRequest : class
    where TResponse : class

This will allow you to create a List<IRequest< object, object>> that can store arbitrary IRequest instances:

var s = new List<IRequest< object, object>>();
s.Add(new Request<c1, c2>());

You'd still have to cast the contents of the list to a more specific class though if you wanted some type-specific behavior. This may or may not hurt performance in high traffic situations.

Another option would be to store the string contents of the Requests, or extract them to an abstract base class. A List of this abstract class would be more than sufficient for caching and logging. You could have something like this:

abstract class BaseRequest
{
    public string RequestContent { get; protected set; }
    public string ResponseContent { get; set; }
}

class Request<TRequest, TResponse> : BaseRequest
    where TRequest : class
    where TResponse : class

and you could still write:

var s = new List<BaseRequest>();
s.Add(new Request<c1, c2>());
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236