Как я создаю дерево выражений, звоня IEnumerable <TSource>.Any (…)?

37
задан IAbstract 10 November 2013 в 17:24
поделиться

1 ответ

Существует несколько вещей неправильно с тем, как Вы идете об этом.

  1. Вы смешиваете уровни абстракции. Параметр T к GetAnyExpression<T> мог отличаться от параметра типа, используемого для инстанцирования propertyExp.Type. Параметр типа T является одним шагом ближе в стопке абстракции ко времени компиляции - если Вы не будете звонить GetAnyExpression<T> через отражение, это будет определено во время компиляции - но тип, встроенный в выражение, передал, поскольку propertyExp определяется во времени выполнения. Ваша передача предиката как Expression является также путаницей абстракции - который является следующим вопросом.

  2. предикат Вы являетесь передающими к GetAnyExpression, должно быть значение делегата, не Expression из любого вида, так как Вы пытаетесь звонить Enumerable.Any<T>. Если бы Вы пытались назвать версию дерева выражений [1 112], то необходимо передать LambdaExpression вместо этого, который Вы заключили бы в кавычки и являетесь одним из редких случаев, где Вы могли бы быть выровнены по ширине мимоходом более определенный тип, чем Выражение, которое приводит меня к моему следующему вопросу.

  3. В целом, необходимо раздать Expression значения. При работе с деревьями выражений в целом - и это применяется через все виды компиляторов, не только LINQ и его друзей - необходимо сделать так же способом, это - агностик относительно непосредственного состава дерева узла, с которым Вы работаете. Вы предположение , что Вы звоните Any на MemberExpression, но Вы не делаете на самом деле потребность знать , что Вы имеете дело с MemberExpression, просто Expression из типа некоторое инстанцирование [1 119]. Это - частая ошибка для людей, не знакомых с основами компилятора ASTs. Frans Bouma неоднократно делал ту же ошибку, когда он сначала начал работать с деревьями выражений - думающий в особых случаях. Думайте обычно. Вы сохраните себя много стычки в носителе и долгосрочной перспективе.

  4. И здесь прибывает суть Вашей проблемы (хотя второе и вероятно первые выпуски имели бы бит Вами, если Вы закончили его) - необходимо найти соответствующую универсальную перегрузку Любого метода, и затем инстанцировать его с корректным типом. Отражение не предоставляет Вам легкое здесь; необходимо выполнить итерации через и найти соответствующую версию.

Так, ломая его: необходимо найти общий метод (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();
}
81
ответ дан Barry Kelly 27 November 2019 в 04:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: