创建将一种类型的函数转换为另一种类型的函数

时间:2013-09-25 23:32:13

标签: c# reflection expression-trees

对于一些花哨的反射内容,我有一个Func类型的函数,需要将它传递给一个接受Func类型的函数,其中T在运行时才知道。例如:

public bool MyOperation(Func<string,bool> op) {
  return _myValues.Any(op);
}

public static bool InvokeOperationMethod(MethodInfo info, object obj,Func<object,bool> opAsObject)
{
   info.Invoke(obj, opAsObject);
}

问题在于,由于我有一个较弱类型的lambda,我无法将其作为更强类型的参数传递。所以我尝试创建一个帮助程序,它将创建一个将较弱类型的lambda转换为更强类型的函数。例如,我可以打电话

var converter = CreateConverter(typeof(string));
Func<object,bool> asObject = o => o.ToString() == "a string"; //Dump example
Func<string,bool> asString = (Func<string,bool>)converter(asObject);
Assert.IsTrue(asInt("a string"));

当然在实际代码中,目标类型直到运行时才知道,而实际谓词不是一些简单的测试。

这是我的尝试:

/// <summary>
/// Converts a predicate of Func<object,bool> to
/// Func<Type,bool> of the given type.
/// </summary>
/// <param name="destType">Type of the dest.</param>
/// <param name="predicate">The predicate.</param>
/// <returns></returns>
public static TransformPredicate CreateConverter(Type destType)
{
    // This essentially creates the following lambda, but uses destType instead of T
    // private static Func<Func<object, bool>, Func<T, bool>> Transform<T>()
    // { 
    //     return (Func<object,bool> input) => ((T x) => input(x));
    // }
    var input = Expression.Parameter(typeof(Func<object, bool>), "input");

    var x = Expression.Parameter(destType, "x");
    var convert = Expression.Convert(x, typeof(object));
    var callInputOnX = Expression.Invoke(input, convert);
    var body2 = Expression.Lambda(callInputOnX, x);
    var body1 = Expression.Lambda(typeof(TransformPredicate),body2, input);
    return (TransformPredicate) body1.Compile();
}

public delegate object TransformPredicate(Func<object,bool> weak);

这实际上运行得很好,除了它运行得非常慢,因为它在每次调用时隐式调用CreateDelegate。所以我尝试通过添加:

来自己调用CreateDelegate
var destFunc = typeof(Func<,>).MakeGenericType(destType, typeof(bool));
var endType = typeof(Func<,>).MakeGenericType(typeof(Func<object, bool>), destFunc);
return (TransformPredicate)compiled.Method.CreateDelegate(endType);

这会导致错误:

  

System.NotSupportedException:派生类必须提供和实现。

我可以自己调用CreateDelegate的任何想法吗?

1 个答案:

答案 0 :(得分:1)

实际上,只要目标类型是引用类型,您就不必执行任何操作。 T中的类型参数Func<T, TResult>逆变,这意味着您可以直接执行转换。因此,以下代码可以正常工作:

Func<object,bool> asObject = o => o.ToString() == "a string";
Func<string,bool> asString = (Func<string,bool>)asObject;
asString("a string");

编辑:编译器不是进行转换,而是CLR理解Func<object, bool>可以安全地投放到Func<string, bool>。因此,以下代码可以正常工作:

class Program
{
    static void Main()
    {
        InvokeOperationMethod(
            typeof(Program).GetMethod("MyOperation"),
            new Program(), o => o.ToString() == "42");
    }

    public bool MyOperation(Func<string, bool> op)
    {
        return op("43");
    }

    public static bool InvokeOperationMethod(
        MethodInfo info, object obj, Func<object, bool> opAsObject)
    {
        return (bool)info.Invoke(obj, new object[] { opAsObject });
    }
}

编辑2:如果您还需要它来处理值类型,则需要以某种方式对参数进行包装。拳击本身很简单:

private static Func<T, bool> BoxParameter<T>(Func<object, bool> op)
{
    return x => op(x);
}

但是你需要调用它,因为你在编译时不知道T,你需要使用反射。类似的东西:

public static bool InvokeOperationMethod(
    MethodInfo method, object obj, Func<object, bool> opAsObject)
{
    var targetType = method.GetParameters()
                           .Single()
                           .ParameterType
                           .GetGenericArguments()[0];

    object opAsT;
    if (targetType.IsValueType)
    {
        opAsT =
            typeof(Program).GetMethod("BoxParameter",
            BindingFlags.NonPublic | BindingFlags.Static)
                           .MakeGenericMethod(targetType)
                           .Invoke(null, new object[] {opAsObject});
    }
    else
    {
        opAsT = opAsObject;
    }

    return (bool)method.Invoke(obj, new[] { opAsT });
}