C# Generics – How to Infer Generic Type from a Generic Method

apicgenericsnet

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.

Best Answer

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);
}
Related Topic