使用 "TryParse()" 方法构建可以处理任何 type/class 的表达式树

Build an expression tree that can handle any type/class with a "TryParse()" method

我有以下方法,return根据输入类型是一个“值解析器”委托。它工作正常,但我想摆脱 switch 语句和类型检查,并能够 return 具有 TryParse() 方法的任何类型的值解析器委托。

internal static Func<object?, (bool isSuccess, object value)>? ValueParser(this Type type)
{
    type = Nullable.GetUnderlyingType(type) ?? type;

    if (type.IsEnum)
        return input => (Enum.TryParse(type, ToString(input), out var res), res!);

    switch (Type.GetTypeCode(type))
    {
        case TypeCode.String:
            return input => (true, input!);

        case TypeCode.Boolean:
            return input => (bool.TryParse(ToString(input), out var res), res);

        case TypeCode.DateTime:
            return input => (DateTime.TryParse(ToString(input), out var res), res);

        //other supported types go here...

        case TypeCode.Object:
            if (type == Types.Guid)
            {
                return input => (Guid.TryParse(ToString(input), out var res), res);
            }            
            else if (type == Types.TimeSpan)
            {
                return input => (TimeSpan.TryParse(ToString(input), out var res), res!);
            }
            break;
    }

    return null; //unsupported types will cause a null return

    static string? ToString(object? value)
    {
        if (value is string x)
            return x;

        return value?.ToString();
    }
}

我相信解决方案是构建一个如下所示的表达式树。但我对如何正确构建表达式树一无所知。到目前为止,我只有以下内容:

internal static Func<object?, (bool isSuccess, object value)>? ValueParser(this Type type)
{
    type = Nullable.GetUnderlyingType(type) ?? type;

    var inputParam = Expression.Parameter(typeof(string), "input");

    if (type == Types.String)
    {
        //no need for conversion if input type is string. so delegate should simply return a tuple (true,"inputValue").
        var returnTarget = Expression.Label(type);
        var returnCall = Expression.Return(returnTarget, inputParam);
    }

    var parsedVar = Expression.Variable(type, "parsed");

    var tryParseCall = Expression.Call(
        type,
        "TryParse",
        null,
        inputParam,
        parsedVar);

    //need to compile the expression and return here.
    //if the input type doesn't have a TryParse() method, null should be returned.
    //also not sure if we need Expression.Convert() to handle value types.
}

几天来,我一直在用头撞墙,但没有成功。非常感谢您提供的任何帮助。谢谢!

这似乎可以解决问题:

private static readonly MethodInfo toStringMethod = typeof(object).GetMethod("ToString")!;
private static readonly ConstructorInfo valueTupleConstructor = typeof(ValueTuple<bool, object>).GetConstructor(new[] { typeof(bool), typeof(object) })!;

internal static Func<object?, (bool isSuccess, object value)>? ValueParser(Type type)
{
    type = Nullable.GetUnderlyingType(type) ?? type;

    if (type == typeof(string))
        return input => (true, input!);
    if (type.IsEnum)
        return input => (Enum.TryParse(type, input?.ToString(), out var res), res!);
    
    // Try and find a suitable TryParse method on Type
    var tryParseMethod = type.GetMethod("TryParse", BindingFlags.Static | BindingFlags.Public, new[] { typeof(string), type.MakeByRefType() });
    // None found or returns the wrong type? Return null.
    if (tryParseMethod == null || tryParseMethod.ReturnType != typeof(bool))
        return null;
    
    // The 'object' parameter passed into our delegate
    var inputParameter = Expression.Parameter(typeof(object), "input");
    // 'input == null ? (string)null : input.ToString()'
    var toStringConversion = Expression.Condition(
        Expression.ReferenceEqual(inputParameter, Expression.Constant(null, typeof(object))),
        Expression.Constant(null, typeof(string)),
        Expression.Call(inputParameter, toStringMethod));
    
    // 'res' variable used as the out parameter to the TryParse call
    var resultVar = Expression.Variable(type, "res");
    // 'isSuccess' variable to hold the result of calling TryParse
    var isSuccessVar = Expression.Variable(typeof(bool), "isSuccess");
    // To finish off, we need to following sequence of statements:
    //  - isSuccess = TryParse(input.ToString(), res)
    //  - new ValueTuple<bool, object>(isSuccess, (object)res)
    // A sequence of statements is done using a block, and the result of the final
    // statement is the result of the block
    var tryParseCall = Expression.Call(tryParseMethod, toStringConversion, resultVar);
    var block = Expression.Block(new[] { resultVar, isSuccessVar },
        Expression.Assign(isSuccessVar, tryParseCall),
        Expression.New(valueTupleConstructor, isSuccessVar, Expression.Convert(resultVar, typeof(object))));
    
    // Put it all together
    var lambda = Expression.Lambda<Func<object?, (bool, object)>>(block, inputParameter).Compile();
    return lambda;
}

See it on dotnetfiddle.net.

希望内联评论能解释发生了什么。如果没有,请告诉我,我会改进它们。

请注意,如果输入是 null,这将遵循您的代码调用 TryParse(null, out var res) 的约定。这似乎不太明智。