Существует несколько вещей неправильно с тем, как Вы идете об этом.
Вы смешиваете уровни абстракции. Параметр T к GetAnyExpression<T>
мог отличаться от параметра типа, используемого для инстанцирования propertyExp.Type
. Параметр типа T является одним шагом ближе в стопке абстракции ко времени компиляции - если Вы не будете звонить GetAnyExpression<T>
через отражение, это будет определено во время компиляции - но тип, встроенный в выражение, передал, поскольку propertyExp
определяется во времени выполнения. Ваша передача предиката как Expression
является также путаницей абстракции - который является следующим вопросом.
предикат Вы являетесь передающими к GetAnyExpression
, должно быть значение делегата, не Expression
из любого вида, так как Вы пытаетесь звонить Enumerable.Any<T>
. Если бы Вы пытались назвать версию дерева выражений [1 112], то необходимо передать LambdaExpression
вместо этого, который Вы заключили бы в кавычки и являетесь одним из редких случаев, где Вы могли бы быть выровнены по ширине мимоходом более определенный тип, чем Выражение, которое приводит меня к моему следующему вопросу.
В целом, необходимо раздать Expression
значения. При работе с деревьями выражений в целом - и это применяется через все виды компиляторов, не только LINQ и его друзей - необходимо сделать так же способом, это - агностик относительно непосредственного состава дерева узла, с которым Вы работаете. Вы предположение , что Вы звоните Any
на MemberExpression
, но Вы не делаете на самом деле потребность знать , что Вы имеете дело с MemberExpression
, просто Expression
из типа некоторое инстанцирование [1 119]. Это - частая ошибка для людей, не знакомых с основами компилятора ASTs. Frans Bouma неоднократно делал ту же ошибку, когда он сначала начал работать с деревьями выражений - думающий в особых случаях. Думайте обычно. Вы сохраните себя много стычки в носителе и долгосрочной перспективе.
И здесь прибывает суть Вашей проблемы (хотя второе и вероятно первые выпуски имели бы бит Вами, если Вы закончили его) - необходимо найти соответствующую универсальную перегрузку Любого метода, и затем инстанцировать его с корректным типом. Отражение не предоставляет Вам легкое здесь; необходимо выполнить итерации через и найти соответствующую версию.
Так, ломая его: необходимо найти общий метод (Any
). Вот служебная функция, которая делает это:
static MethodBase GetGenericMethod(Type type, string name, Type[] typeArgs,
Type[] argTypes, BindingFlags flags)
{
int typeArity = typeArgs.Length;
var methods = type.GetMethods()
.Where(m => m.Name == name)
.Where(m => m.GetGenericArguments().Length == typeArity)
.Select(m => m.MakeGenericMethod(typeArgs));
return Type.DefaultBinder.SelectMethod(flags, methods.ToArray(), argTypes, null);
}
Однако это требует аргументов типа и корректных типов аргумента. Получение этого от Вашего propertyExp
Expression
не совершенно тривиально, потому что эти Expression
может иметь List<T>
тип или некоторый другой тип, но мы должны найти IEnumerable<T>
инстанцирование и получить его аргумент типа. Я инкапсулировал это в несколько функций:
static bool IsIEnumerable(Type type)
{
return type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
}
static Type GetIEnumerableImpl(Type type)
{
// Get IEnumerable implementation. Either type is IEnumerable<T> for some T,
// or it implements IEnumerable<T> for some T. We need to find the interface.
if (IsIEnumerable(type))
return type;
Type[] t = type.FindInterfaces((m, o) => IsIEnumerable(m), null);
Debug.Assert(t.Length == 1);
return t[0];
}
Так, учитывая любой Type
, мы можем теперь вытащить IEnumerable<T>
инстанцирование из него - и утверждать, нет ли (точно) один.
С той работой из пути, решая настоящую проблему не является слишком трудным. Я переименовал Ваш метод к CallAny и изменил типы параметра, как предложено:
static Expression CallAny(Expression collection, Delegate predicate)
{
Type cType = GetIEnumerableImpl(collection.Type);
collection = Expression.Convert(collection, cType);
Type elemType = cType.GetGenericArguments()[0];
Type predType = typeof(Func<,>).MakeGenericType(elemType, typeof(bool));
// Enumerable.Any<T>(IEnumerable<T>, Func<T,bool>)
MethodInfo anyMethod = (MethodInfo)
GetGenericMethod(typeof(Enumerable), "Any", new[] { elemType },
new[] { cType, predType }, BindingFlags.Static);
return Expression.Call(
anyMethod,
collection,
Expression.Constant(predicate));
}
Вот Main()
стандартная программа, которая использует весь вышеупомянутый код и проверяет, что это работает на тривиальный случай:
static void Main()
{
// sample
List<string> strings = new List<string> { "foo", "bar", "baz" };
// Trivial predicate: x => x.StartsWith("b")
ParameterExpression p = Expression.Parameter(typeof(string), "item");
Delegate predicate = Expression.Lambda(
Expression.Call(
p,
typeof(string).GetMethod("StartsWith", new[] { typeof(string) }),
Expression.Constant("b")),
p).Compile();
Expression anyCall = CallAny(
Expression.Constant(strings),
predicate);
// now test it.
Func<bool> a = (Func<bool>) Expression.Lambda(anyCall).Compile();
Console.WriteLine("Found? {0}", a());
Console.ReadLine();
}