Reputation: 209
I am attempting to build a dynamic transformation pipeline driven by config.
I have a collection of static methods which return Func<SourceType,TargetType> delegates, of the following format. The idea being that the parameters I pass into these methods (s1 in the example below) are parameters. The Func itself will then take a source object and convert to target object.
public static Func<string, Result<string>> Concat(string s1) => (string s) =>
Result<string>.Success(s1 + s);
I will not not know the source / target types until runtime (they will be read from a config json).
However, I want to be able to chain funcs and have some form of type checking that the function being called is compatible with the object being returned from the previous func.
My current approach is as follows:
I have a method that attempts to return a Func<T, Result> but this is invalid as I cannot cast an object type return from the invoked method to a Func<T,Result> due to no covariance.
GetConverterMethod - returns a Func<T,Result> (where T and U are non generic) based on the name of the function picked up from a config file.
AssignParameters - returns an object array with values picked up from config matching the converter signature. I.e. for concat it would return an array with a single element corresponding to "s1".
public static Func<T,Result<object>> GetFunc<T>(T sourceObject, string methodName, Dictionary<string, object> paramsDict)
{
MethodInfo method = GetConverterMethod(sourceObject.GetType(), methodName);
object[] assignedParameters = AssignParameters(method, paramsDict);
return (Func<T, Result<object>>)method.Invoke(null, assignedParameters);
}
My plan was to apply the functions as follows in an execution pipeline:
public static object ExecutePipeline<T>(T input, List<TransformationStep> steps)
{
object result = input;
foreach (var step in steps)
{
var func = GetFunc(result, step.Name, step.Parameters );
var funcResult = func.DynamicInvoke(result);
if(funcResult is Result<object> resultObject && resultObject.IsSuccess)
{
result = resultObject.Value;
}
else
{
throw new Exception();
}
}
return result;
}
My question is whether there is a cleaner approach / functional pattern that can handle calling funcs were the intermediate types are not known until runtime.
Note that Result is a simple Result monad:
public class Result<T>
{
public bool IsSuccess { get; }
public T Value { get; }
public string Error { get; }
private Result(T value, bool isSuccess, string error)
{
this.IsSuccess = isSuccess;
this.Value = value;
this.Error = error;
}
public static Result<T> Success(T value) => new Result<T>(value, true, null);
public static Result<T> Failure(string error) => new Result<T>(default, false, error);
public Result<U> Bind<U>(Func<T, Result<U>> func)
{
return this.IsSuccess ? func(this.Value) : Result<U>.Failure(this.Error);
}
public static implicit operator Result<T>(T value) => Success(value);
public static explicit operator T(Result<T> result) => result.Value;
}
Upvotes: 0
Views: 31