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).