如何使用“Expression.Parameter”和“Expression.Call”API调用通用方法

时间:2015-11-12 02:33:15

标签: c# generics expression-trees

使用 Expression Tree API 我想为代码生成代码,如下所示:

FieldInfo enumFieldInfo = enumFieldInfoSet.SingleOrDefault(fieldInfo => fieldInfo.Name == enumName);

我已经编写了这段代码,但它无效:

var enumerableType = typeof(Enumerable);
var enumerableGenericType = typeof(IEnumerable<>).MakeGenericType(typeof(FieldInfo));
var predicateGenericType =  typeof(Func<>).MakeGenericType(typeof(Func<FieldInfo, bool>));

ParameterExpression fieldInfoSource = Expression.Parameter(enumerableGenericType, "fieldInfoSource");
ParameterExpression predicateSource = Expression.Parameter(predicateGenericType, "funcPredicateOnFieldInfo");

var arrayOfTypes = new Type[] { enumerableGenericType, predicateGenericType };

MethodCallExpression SingleOrDefaultMethodCall = Expression.Call(enumerableType, "SingleOrDefault",arrayOfTypes, fieldInfoSource, predicateSource);

这是运行时错误:类型'System.Linq.Enumerable'上没有泛型方法'SingleOrDefault'与提供的类型参数和参数兼容。如果方法是非泛型的,则不应提供类型参数。

我尝试了许多类型强制的组合,但仍然没有偶然发现正确的组合。我知道SingleOrDefault是Enumerable类的Extension方法,它需要两个参数;我通过调试器查看代码并编写代码在运行时检查其属性;我错过了什么。

2 个答案:

答案 0 :(得分:1)

问题在于您使用的是带有类型的Expression.Call重载,对于静态方法,您需要使用MethodInfo重载。

void Main()
{
    Expression<Func<IEnumerable<FieldInfo>, Func<FieldInfo,bool>, FieldInfo>> singleOrDefaultExpr = (l,p) => l.SingleOrDefault(p);
    var callSource = (MethodCallExpression)singleOrDefaultExpr.Body;

    var method = callSource.Method;

    var collectionParameter = Expression.Parameter(typeof(IEnumerable<FieldInfo>), "enumFieldInfoSet");
    var enumNamePredicateParameter = Expression.Parameter(typeof(Func<FieldInfo,bool>), "enumNamePredicate");

    var body = Expression.Call(method, collectionParameter, enumNamePredicateParameter);

    var lambda = Expression.Lambda<Func<IEnumerable<FieldInfo>, Func<FieldInfo, bool>, FieldInfo>>(body, collectionParameter, enumNamePredicateParameter);
    var f = lambda.Compile();

    Console.WriteLine(f(typeof(Apple).GetFields(), fi => fi.Name == "color").Name);
}

class Apple
{
    public string color;
}

此外,您可以使用其他方法来查找所需的MethodInfo:

var method = typeof(Enumerable)
    .GetMethods()
    .Single(m => m.Name == "SingleOrDefault" && m.GetParameters().Count() == 2)
    .MakeGenericMethod(new[] {typeof(FieldInfo)});

更新:

实际上有一种更简单的方法,而且你在正确的轨道上,但是你的代码出错了。

var collectionParameter = Expression.Parameter(typeof(IEnumerable<FieldInfo>), "enumFieldInfoSet");
var enumNamePredicateParameter = Expression.Parameter(typeof(Func<FieldInfo,bool>), "enumNamePredicate");
var body = Expression.Call(typeof(Enumerable), "SingleOrDefault", new[] { typeof(FieldInfo)}, collectionParameter, enumNamePredicateParameter);
var lambda = Expression.Lambda<Func<IEnumerable<FieldInfo>, Func<FieldInfo, bool>, FieldInfo>>(body, collectionParameter, enumNamePredicateParameter);

问题是SingleOrDefault只有一个泛型类型参数:在这种情况下为'FieldInfo':

SingleOrDefault<FieldInfo>(....

不要将其与方法参数混淆,其中有两个:

SingleOrDefault<GenericParameter>(
    this IEnumerable<GenericParameter> firstMethodParameter,
    Func<GenericParameter, bool> secondMethodParameter
)

答案 1 :(得分:0)

乔治做得很好,让我走上了正确的轨道,并为我提供了部分答案;我重新设计了我的代码,这对我来说很清楚。这是我想在测试元编程运行时时生成的代码示例。我提供的代码多于特定问题所需的代码,但我希望其他代码能够看到更大的背景。

// Code to Generate Enum Field Metadata ...

string enumName   = Enum.GetName(theEnumType, theOrdinalEnumValue);
Array  enumValues = Enum.GetValues(theEnumType);
object enumValue  = enumValues.GetValue(theOrdinalEnumValue);
object enumObject = Enum.ToObject(theEnumType, theOrdinalEnumValue);

// Create Local variables of the types targeted for assignment expressions that we will make in the generated code

var enumVariables = Expression.RuntimeVariables(Expression.Variable(typeof(string), "gcEnumName"),
                                                Expression.Variable(typeof(Array), "gcEnumValues"),
                                                Expression.Variable(typeof(object), "gcEnumValue"),
                                                Expression.Variable(theEnumType, "gcEnumObject"),
                                                Expression.Variable(typeof(FieldInfo), "gcFieldInfoOnEnum"));

// Setup the Input Parameters for calls into Enum and Array Types in preperation for Assignments

ParameterExpression theInputOfEnumType     = Expression.Variable(typeof(Type), "theInputOfEnumType");
ParameterExpression theEnumFieldNameValue  = Expression.Variable(typeof(string), "theEnumFieldNameValue");
ParameterExpression aEnumObjectIndexValue  = Expression.Variable(typeof(int), "aEnumObjectIndexValue");
ParameterExpression anArrayOfEnumObjects   = Expression.Variable(typeof(Array), "anArrayOfEnumObjects");
ParameterExpression anEnumerableObject     = Expression.Variable(typeof(Enumerable), "anEnumerableObject");
ParameterExpression directEnumTypeResolved = Expression.Variable(theEnumType, "directEnumTypeResolved");
ParameterExpression fieldInfoOnEnum        = Expression.Variable(typeof(FieldInfo), "fieldInfoOnEnum");

var fieldInfoEnumerableRepresentation = typeof(Enumerable);

进行调用&#34; Expression.Call&#34;我们需要获得一些MethodInfo数据

// We need to generate MethodInfo on the Methods we want to call in the generated code. This is metadata
// we need to call the Expression.Call API.

MethodInfo enumGetNameMethodInfo   = enumMetadata.GetMethods().FirstOrDefault(info => info.Name == "GetName");
MethodInfo enumGetValuesMethodInfo = enumMetadata.GetMethods().FirstOrDefault(info => info.Name == "GetValues");

MethodInfo enumGetValueMethodInfo  = arraryMetadata.GetMethods()
  .FirstOrDefault(methodInfo => (methodInfo.Name == "GetValue") && methodInfo.GetParameters().Any(param =>param.ParameterType == typeof(int)));
MethodInfo enumToObjectMethodInfo  = enumMetadata.GetMethods()
                                    .FirstOrDefault(info => info.Name == "ToObject");

// We know that there exist a number of polymorphic instances of the "SingleOrDefault" Extension method
// so we use the name of the parameter named "predicate" to locate our method. **FYI Can't use the MethodInfo data in a call to Expression.Call - It's a generic definition**.

MethodInfo enumerableSingleOrDefaultInfo = fieldInfoEnumerableRepresentation.GetMethods()
  .FirstOrDefault(methodInfo => (methodInfo.Name == "SingleOrDefault") &&
                                 methodInfo.GetParameters()
                                   .Any(param => param.Name == "predicate"));

以下是调用通用方法的最终代码:

// An approach to setup a Generic method call that will be used in an Expression.Assign call

// I decompose this code so for debugging purposes

// I create the "inputOfTSourceType" as a Generic Type because in the Definition of "SingleOrDefault" method there is only one Generic Parameter;
// also, take special note that in the assemblage of Expression.Call methods any of the methods that take a MethodInfo instance you can't pass in
// a "Generic" method definition. If you take a look at the code that is called it will throw and exception if it detects the IsGenericMethodDefinition
// flag is true.
var inputOfTSourceType  = typeof(IEnumerable<>).MakeGenericType(typeof(FieldInfo));  // This is the type on the "source" TSource parameter
var predicateOfFuncType = typeof(Func<FieldInfo, bool>);   // This is the type on the "predicate" parameter
// Take note there that we only define one(1) type here ("inputParameterType"); this is the type we wish apply to the Generic Type TSource
// declaration: public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
var inputParameterType  = new[] {typeof(FieldInfo)};       // This is the type we must match to "source" parameter

// The "SingleOrDefault" method can take two parameters and in this case I need to pass in a lambda expression as the predicate.
// Se we need two parameters. 

ParameterExpression fieldInfoSource = Expression.Parameter(inputOfTSourceType, "fieldInfoSource");           // This is: this IEnumerable<TSource>
ParameterExpression predicateSource = Expression.Parameter(predicateOfFuncType, "funcPredicateOnFieldInfo"); // This is: Func<TSource, bool> predicate

 MethodCallExpression SingleOrDefaultMethodCall = 
     Expression.Call(fieldInfoEnumerableRepresentation, // This is the Object Instance for which the 
                     "SingleOrDefault", // The Name of the Generic Method
                     inputParameterType, // The Generic Type
                     fieldInfoSource,    // this is the "this" source parameter
                     predicateSource);   // this the "predicate" parameter

 Expression localEnumNameAssignment =
   Expression.Assign(enumVariables.Variables[0], EnumGetNameMethodCall);
 Expression localEnumObjectsAssignment =
   Expression.Assign(enumVariables.Variables[1], EnumGetValauesMethodCall);
 Expression localEnumObjectAssignment =
   Expression.Assign(enumVariables.Variables[2], ArrayGetValueMethodCall);
 Expression localEnumTypeAssignment = 
   Expression.Assign(enumVariables.Variables[3], Expression.Convert(EnumToObjectMethodCall, theEnumType));
 Expression localFieldInfoAssignment =
   Expression.Assign(enumVariables.Variables[4], Expression.Convert(SingleOrDefaultMethodCall, typeof(FieldInfo)));

 BlockExpression blockExpression =
   Expression.Block(enumVariables, 
   localEnumNameAssignment, 
   localEnumObjectsAssignment, 
   localEnumObjectAssignment, 
   localEnumTypeAssignment, 
   localFieldInfoAssignment, 
   enumTypeToReturn);

这是生成的代码:

$gcEnumName = .Call System.Enum.GetName(
   $theInputOfEnumType, $gcEnumName = (System.String)$theEnumFieldNameValue)

$gcEnumValues = .Call System.Enum.GetValues($theInputOfEnumType)

$gcEnumValue = .Call $anArrayOfEnumObjects.GetValue((System.Int32)  $theOrdinalEnumValue)

$gcEnumObject = (WorkflowMessagingCommands).Call System.Enum.ToObject(
   $theInputOfEnumType,
   (System.Object)$theEnumInstance)

// Here is the generated code to call "SignleOrDefault"
$gcFieldInfoOnEnum = .Call System.Linq.Enumerable.SingleOrDefault(
    $thisSourceType,
    $predciateType)