如何使用“;表达式参数“;以及“;表达式调用“;API
本文关键字:表达式 调用 API 以及 参数 何使用 | 更新日期: 2023-09-27 18:21:39
使用表达式树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方法,它需要两个参数;我通过调试器查看了代码,并编写了在运行时检查其属性的代码;我错过了什么。
问题是您使用的是采用类型的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
)
George做得很好,让我走上了正轨,并为我提供了部分答案;我重新设计了我的代码,这样我就清楚了。这是一个我想在测试Meta编程运行时机器生成的代码示例。我提供的代码超过了特定问题所需的数量,但我希望其他人看到更大的上下文。
// 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);
在进行对"Expression.Call"的调用时,我们需要获得一些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)