Home > Mobile >  How can I cast an object to a Func<In, Out> without knowing the generics?
How can I cast an object to a Func<In, Out> without knowing the generics?

Time:10-25

I have a ton of functions that all accept a class (Request) and return a class (Response) and I'm trying to write a generic handler.

I can't change the definition of my class JobParametersModel to accept generics or my method DoWorkAsync, but I can modify everything else.

Given these classes:

public class JobRequestModel<Request, Response>
    where Request : class
    where Response : class
{
    public Func<Request, Task<Response>> CallDelegate { get; set; }

    public async Task<Response> DoCall(Request request)
    {
        return await CallDelegate(request);
    }
}

public sealed class JobParametersModel
{
    // Stores class JobRequestModel and other params
    public object RequestModel { get; set; }
}

This is the method I'm trying to solve. How can I get the object to something I can interact with?

// What I'm trying to do, that does not work.
public async Task<JobResultModel> DoWorkAsync(JobParametersModel work, object request)
{
    var jobRequestModel = (JobRequestModel)work.RequestModel; // Does NOT work.
    
    // I can cast request using "ChangeType". Simplified code for example
    var results = await jobRequestModel.DoCall(request);
    
    // I package/return results later
}

That way I can call it like this:

var result1 = await DoWorkAsync(
    new JobParametersModel()
    {
        RequestModel = new JobAXRequestModel<CustomRequestType, CustomResponseType>()
        {
            CallDelegate = _client.getMyCustomDelegate
        }
    },
    new CustomRequestType()
    {
        CustomField1 = "something",
        CustomField2 = 4
    }
);

var result2 = await DoWorkAsync(
    new JobParametersModel()
    {
        RequestModel = new JobAXRequestModel<OtherCustomRequestType, OtherCustomResponseType>()
        {
            CallDelegate = _client.getSomeOtherCustomDelegate
        }
    },
    new OtherCustomRequestType()
    {
        DifferentField1 = "other things"
    }
);

CodePudding user response:

In scenarios where a method must be implemented using a pre-defined signature, but you need to access some other type, then the option of duck-typing within the method exists.

For reference, see: Using type dynamic (C# Programming Guide)

In you sample code, the following method must be implemented with the following signature:

Task<JobResultModel> DoWorkAsync(JobParametersModel work, object request)

Where the JobParametersModel type contains an object with the model:

public object RequestModel { get; set; }

As you cannot change either the signature of the DoWorkAsync method, nor the structure of the JobParametersModel, then you can take advantage of dynamic programming and bypass compile time checking with runtime validation:

dynamic jobRequestModel = work.RequestModel

Where the final implementation will be:

public async Task<JobResultModel> DoWorkAsync(JobParametersModel work, object request)
{
    // cast the the model to a dynamic type
    // could also be defined as: 
    // dynamic jobRequestModel = work.RequestModel
    var jobRequestModel = (dynamic)work.RequestModel;
    
    // invoke the method; type checking will only during runtime
    var results = await jobRequestModel.DoCall(request);
    
    // other code...
}

As long as the method exists on the dynamic type, then the runtime will execute the method. However, if the method does not exist or has a different signature, then a runtime exception will be thrown.

If you expect to have types other than a JobRequestModel be passed in as the RequestModel, then you'll want to perform additional checking to verify the type before invoking the method (could be as simple as reflection to find a matching method).

  • Related