0

I'm making an HTTP API client class.

I want a Perform() method which takes a request object and returns an obejct that is expected from the API.

So a PostRequest object will describe how to get a Post.

Ultimately what I'm trying to do is the following:

Post post = client.Perform<PostRequest>(x => x.Id = 1);

I'm trying to infer the return type based on the Request generic.

My PostRequest class looks like this:

internal class PostRequest : Request<Post>
{
    internal int Id { get; set; }
}

The problem is I can't infer that the purpose of the PostRequest is to return a Post.

That is, I can only get this to work when my method looks like this, where Post is included in the generic method.

public TModel Perform<TRequest, TModel>(Action<TRequest> action) where TRequest : Request<TModel> where TModel : class

But I was hoping to simplify the method like this:

public TModel Perform<TRequest>(Action<TRequest> action) where TRequest : Request<TModel>

But I can't tell C# how to derive what a TModel is.

Is there any way to achieve what I want? Or will I always have to tell the generic method what return I'm expecting? (even though this is included in the PostRequest)

I'm open to suggestions of a better approach.

  • I'm not sure the where TModel : class is needed, at which point you are asking about removing the text that introduces the name TModel – Caleth Apr 4 at 14:33
2

First of all, you can not expect to get class which pass into Request as T type by using PostRequest because it could be implements more than one interface.

public interface Request<T>
{
    //...
}

public class PostRequest : Request<Post>, Request<AnotherPost>
{
    //..
}

Beyond, you can use Request<T> instead of concrete class. It is totally same for this logic.

And, I think you try to predicate(not assign) by doing (x => x.Id = 1). If so, I assume this code like (x => x.Id == 1). It means you are trying to determine prediction by passing parameter to method.

If I get correctly, you have two option to achieve this by using System.Linq.Expressions:

1. As method parameter:

public T Perform<T>(Expression<Func<T, bool>> expression) where T : class
{
   // your logic here.
}

public void DoSomethingByParameter()
{
   // This is *Ultimately what you are trying to do*
   Post post = Perform<Post>(x => x.Id == 1);
   Photo photo = Perform<Photo>(x => x.Description.Contains("Something"));
}

As you see, you don't need PostRequest or PhotoRequest classes for this usage.

2. Store in class properties

public class Post
{
    public int Id { get; set; }
}

public class Photo
{
    public string Description { get; set; }
}

public interface Request<T>
{
    Expression<Func<T, bool>> Exp { get; }
}

public class PostRequest : Request<Post>
{
    public int Id { get; set; }
    public Expression<Func<Post, bool>> Exp => (x) => x.Id == this.Id;
}

public class PhotoRequest : Request<Photo>
{
    public Expression<Func<Photo, bool>> Exp => (x) => x.Description.Contains("any description");
}

public T Perform<T>(Request<T> request) where T : class
{
   // your logic here.
   //  something like
   //context.Where(request.Exp);
}

And you can call Perform like :

public void DoSomethingByProperty()
{
   PostRequest postRequest = new PostRequest();
   PhotoRequest photoRequest = new PhotoRequest();

   // This is also *Ultimately what you are trying to do*
   Post post = Perform(postRequest);
   Photo photo = Perform(photoRequest);
}
1

As I understand it, you want the Perform method to take a generic class and return a type based on the generic class.

So you want:

public T Perform<T>(Action<Request<T>> actionRequest) where T : class

I think. If your TRequest is always a Request<T>, you might as well just use Request<T> instead.

  • Yeah that's pretty close. The only thing is I'm trying to pass a predicate as the search criteria. And the parameters change based on the request. So a PostRequest might search for a Post with id, whereas a PhotoRequest might search via a Description. And so the generic Request<> doesn't know about either of those. I'm not sure how to resolve this. – Rowan Freeman Mar 29 at 4:43
  • 1
    What's wrong with the generic you already posted? public TModel Perform<TRequest, TModel>(Action<TRequest> action) where TRequest : Request<TModel> where TModel : class looks fine to me. – Stephen Mar 29 at 4:51
  • Yeah maybe I'll just go with that. I was trying to simplify it by not having to specifiy the return type (i.e. Post) since I figured that it was already stated in the PostRequest. Thanks! – Rowan Freeman Mar 29 at 5:13
1

Define your response types first, then define your request types with a generic parameter indicating the type of response.

class BaseResponse
{
}

class PostResponse : BaseResponse
{
    //...
}

class PhotoResponse : BaseResponse
{
    //...
}

interface IBaseRequest<out TReply> where TReply : BaseResponse, new()
{
    //...
}

class PostRequest : IBaseRequest<PostResponse>
{
    //...
}

class PhotoRequest : IBaseRequest<PhotoResponse>
{
    //...
}

class Service
{
    public TReply Perform<TReply>(IBaseRequest<TReply> request) where TReply : BaseResponse, new()
    {
        return new TReply();
    }
}

Now the calls can look like this. Notice both request and response types are inferred:

var client =  new Service();

var request1 = new PostRequest();
var response1 = client.Perform(request1); 

var request2 = new PhotoRequest();
var response2 = client.Perform(request2); 

Console.WriteLine(response1.GetType().Name);
Console.WriteLine(response2.GetType().Name);

Output:

PostResponse
PhotoResponse

Example on DotNetFiddle

  • I like this solution. It's a bit complex but it does what OP expects it to. Minor caveat though: When a class implements IBaseRequest more than once (e.g. class FooAndBarRequest : IBaseRequest<Foo>, IBaseRequest<Bar>) then you will still be forced to specify the underlying type you wish to use (client.Perform<Foo>(fooAndBarRequest)) – Flater Apr 4 at 22:00
1

You cannot do this, because you cannot guarantee that there is only one generic type that could possibly be inferred.

Well, to be fair, you can guarantee that when your deriving from a class, but you cannot do that when dealing with interface, because a class can implement multiple interfaces.

public FooAndBarRequest: IRequest<Foo>, IRequest<Bar> { /* ... */ }

Now let's look again at your intended usage:

var result = client.Perform<FooAndBarRequest>(x => x.Id = 1);

So, should var result be a Foo or a Bar?

There's no way for the compiler to infer whether you're expecting a Foo or a Bar, and therefore the compiler deems your code ambiguous (and not logically consistent) and therefore rejects it.


You could argue that this could be inferred for inheritance. But it wouldn't work for interfaces. And that would mean that the compiler would have to treat generic classes and generic interfaces differently, which is going to considerably complicate everything (both for the developers who have to create the compiler and for the developers who have to work with the compiler and understand what it expects).

You could also hack together some sort of inference logic using reflection, but I don't think this is a good idea. Not that it won't work, but it just complicates everything and you still can't cover every fringe case anyway.


Some answers here have suggested using a generic method parameter next to a generic class parameter, but I strongly disagree here. The additional generic parameter you wish to identify (Post) is based on the class definition (PostRequest : Request<Post>) and is not freely choosable by whoever calls the method.

For example, you wouldn't want this to be possible:

var postRequestPerformer = new Performer<PostRequest>();

Post post = postRequestPerformer.Perform<Post>(); // this is okay
Foo foo = postRequestPerformer.Perform<Foo>(); // this is not okay

The second call should not be allowed because PostRequest does not actually implement Request<Foo>. But because you're using a separate class generic parameter and method generic parameter, it's still possible that after instantiating your object (thereby setting the class generic parameter to a fixed type) to perform different method calls with different generic method parameters.

The only way to prevent this is to define both generic parameters on the same level, which is what you already discovered. This can be done on the method level:

public TModel Perform<TRequest, TModel>(Action<TRequest> action) where TRequest : Request<TModel> where TModel : class

or on the class level:

public class Performer<TRequest, TModel> where TRequest : Request<TModel> where TModel : class
{
    public TModel Perform(Action<TRequest> action)
    {
        // ...
    }
}

The main point is that these two generic parameters are intended to be decided at the same time (since one cannot change without the other then also changing), which means they have to be defined on the same scope.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.