3

对于一些花哨的反射东西,我有一个 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 吗?

4

1 回答 1

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 });
}
于 2013-09-26T01:12:30.917 回答