问题
For some fancy reflection stuff, I've got a function of type Func and need to pass it into a function that accepts type Func where T is not known until run time. For example:
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);
}
The issue is that since I've got a lambda of a weaker type, I can't pass it as an argument of a stronger type. So I tried to make a helper that will create a function that converts a lambda of a weaker type into a stronger type. For example, I could call
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"));
Of course in the actual code this the destination type is not known until run time, and the actual predicate is not some trivial test.
This is my attempt:
/// <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);
This actually works just fine, except that it runs really slowly since it's implicitly invoking CreateDelegate on each invocation. So I tried to call CreateDelegate myself by adding:
var destFunc = typeof(Func<,>).MakeGenericType(destType, typeof(bool));
var endType = typeof(Func<,>).MakeGenericType(typeof(Func<object, bool>), destFunc);
return (TransformPredicate)compiled.Method.CreateDelegate(endType);
This results in an error:
System.NotSupportedException: Derived classes must provide and implementation.
Any ideas how I can call CreateDelegate myself?
回答1:
Actually, as long long as the target type is a reference type, you don't have to do anything. The type parameter T
in Func<T, TResult>
is contravariant, which means you can do the cast directly. Because of this, the following code works fine:
Func<object,bool> asObject = o => o.ToString() == "a string";
Func<string,bool> asString = (Func<string,bool>)asObject;
asString("a string");
EDIT: It's not the compiler doing the conversion, it's the CLR that understands that Func<object, bool>
can be safely casted to Func<string, bool>
. So, the following code will work just fine:
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 });
}
}
EDIT 2: If you need this to work for value types too, you will need to box the parameter somehow. The boxing itself is simple:
private static Func<T, bool> BoxParameter<T>(Func<object, bool> op)
{
return x => op(x);
}
But then you need to invoke it, and since you don't know T
at compile time, you need to use reflection for that. Something like:
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 });
}
来源:https://stackoverflow.com/questions/19016898/creating-a-function-that-converts-functions-of-one-type-to-another