2015-06-09 2 views
2

Например, у меня есть класс с некоторым свойством:Как вызвать метод с экземпляром с помощью Expression

public class SomeClass 
{ 
    public Version Version { get; set; } 
} 

И у меня есть список этого типа с данными выборки:

var list = new List<SomeClass>(); 

for (var i = 0; i < 1000; i++) 
{ 
    list.Add(new SomeClass 
    { 
     Version = new Version(i, i/2, i/3, i/4), 
    }); 
} 

Я хочу написать метод, который фильтрует по версии с использованием метода Version.Equals:

var filterValue = new Version(12, 6, 4, 3); 

var modelType = typeof(SomeClass); 
var propertyType = typeof(Version); 
var arg = Expression.Parameter(modelType, "x"); 
var property = Expression.Property(arg, "Version"); 
var value = Expression.Convert(Expression.Constant(filterValue), propertyType); 
var versionEqualsMethod = typeof(Version).GetMethod("Equals", new[] { typeof(Version) }); 

///////// 
Expression inst = null; // <-- ??? 
///////// 

var expr = Expression.Call(inst, versionEqualsMethod, property, value); 

var delegateType = typeof(Func<,>).MakeGenericType(modelType, typeof(bool)); 
var delegateValue = Expression.Lambda(delegateType, expr, arg).Compile(); 

var genericMethod = 
    typeof(Enumerable).GetMethods() 
     .First(
      method => 
       method.Name == "Where" && method.IsGenericMethodDefinition 
       && method.GetGenericArguments().Length == 1 && method.GetParameters().Length == 2) 
     .MakeGenericMethod(modelType); 

var result = genericMethod.Invoke(null, new object[] { list, delegateValue }); 

Что я могу использовать в качестве примера в выражении .Вызов?

UPDATE

Решение:

var expr = Expression.Call(property, versionEqualsMethod, value); 
+0

Вы хотите протестировать или узнать выражение? или ваш пример - ваш случай? – Amir

+0

Я хочу написать расширение для фильтрации IEnumerable . Это просто случай использования Expression.Call с экземпляром. – Vitone

+0

Почему бы не использовать отражение? Было бы намного проще. См. Мой ответ ниже. –

ответ

4

Вы обычно бы:

var filterValue = new Version(12, 6, 4, 3); 

var modelType = typeof(SomeClass); 
var propertyType = typeof(Version); 
var arg = Expression.Parameter(modelType, "x"); 
var property = Expression.Property(arg, "Version"); 

// Changes from here onward 
var value = Expression.Constant(filterValue); 
var versionEqualsMethod = typeof(Version).GetMethod("Equals", new[] { typeof(Version) }); 
var expr = Expression.Call(property, versionEqualsMethod, value); 

Поскольку Equals будет использоваться как:

model.Version.Equals(filterValue); 

Я не обработки в model.Version == null дело!

Обратите внимание, что вам не нужен Expression.Convert.

И что вы делаете, это нормально, если «содержащий метод» (метод, в котором вы помещаете этот код) является не общим, но обычно это будет общий метод, который имеет как общий параметр modelType , поэтому последняя часть кода будет отличаться (начиная с var delegateType =), так как вы можете использовать общий тип TModelType.

+0

Мне нужно Expression.Convert для Nullable свойств. Когда я использую свойство как экземпляр, я получаю следующее исключение: Некорректное количество аргументов, предоставленных для вызова метода 'Boolean Equals (System.Version) ' – Vitone

+0

@Vitone Случай, который вы показали, предназначен для 'Version', то есть' class', который не может быть 'Nullable <>' ... Но если вы хотите использовать его с типами NULL, я вижу его использование. – xanatos

+0

Спасибо, это работает! – Vitone

0

Может быть, я не хватает на что-то, но будет это не работа:

var results = list.Where(sc => sc.Version == filterVersion); 
+0

Это просто пример. Я хочу динамически вызывать разные методы с помощью Expression. – Vitone

0

То, что вы пытаетесь сделать, гораздо легче сделать с отражением. Проверьте this running online example. (Если я правильно понимаю, что это ... Было бы полезно, если бы вы могли предоставить сигнатуру функции, которую вы пытаетесь писать.)

Реализация

public static class Extensions 
{ 
    public static IEnumerable<T> Filter<T>(
     this IEnumerable<T> enumerable, string propertyName, object filterValue) 
    { 
     var elementType = typeof (T); 
     var property = elementType.GetProperty(propertyName); 

     return enumerable.Where(element => 
     { 
      var propertyValue = property.GetMethod.Invoke(element, new object[] {}); 
      return propertyValue.Equals(filterValue); 
     }); 
    } 
} 

Использование

var list = new List<SomeClass>(); 

for (var i = 0; i < 1000; i++) 
{ 
    list.Add(new SomeClass {Version = new Version(i, i/2, i/3, i/4)}); 
} 

var filteredList = list.Filter("Version", new Version(12, 6, 4, 3)); 

Console.WriteLine(filteredList.Single().Version); 
+0

Да, это проще, но мой подход более функциональный. Например, ваш фильтр не работает в этом случае: var filterList = list.Filter («Version.Major», 12), в то время как мой фильтр работает. И я могу добавить столько фильтров, сколько захочу. – Vitone

+0

@Vitone, теперь мне любопытно. Можете ли вы, пожалуйста, обновить свой вопрос или принятый ответ с помощью своего решения? (Похоже, вы уже нашли решение.) То, что я вижу сейчас, выглядит просто ужасно с точки зрения удобства использования. Какова ваша подпись подписи? Требуется ли объект «Expression» и «object» в качестве значения фильтра? Уверен, что мои десять пальцев все же легче разбирать строку типа «Version.Major» и использовать отражение, чтобы получить значение свойства. –

Смежные вопросы