Селектор значений свойства Lambda в качестве параметра
У меня есть требование изменить метод, чтобы он имел дополнительный параметр, который будет принимать лямбда-выражение, которое будет использоваться на внутреннем объекте, чтобы вернуть значение данного свойства. Простите мое вероятное неправильное использование терминологии, так как это мой первый набег на выражения LINQ!
Я попытался найти ответ, но, как я уже упоминал, моя терминология, кажется, отключена, и примеры, которые я могу найти, слишком сложны или имеют дело с выражениями для функций сбора, такими как .Where()
, с которыми я знаком.
Что у меня до сих пор (вырезанная версия):
class MyClass
{
private MyObject _myObject = new MyObject() { Name = "Test", Code = "T" };
private string MyMethod(int testParameter, ??? selector)
{
//return _myObject.Name;
//return _myObject.Code;
return ???;
}
}
Я хотел бы назвать это примерно так:
string result = _myClassInstance.MyMethod(1, (x => x.Name));
или
string result = _myClassInstance.MyMethod(1, (x => x.Code));
Очевидно, что части, которые мне не хватает, это параметр selector
в MyMethod
, как применить его к локальной переменной и как передать требуемое свойство в метод, когда я его вызываю.
Любая помощь будет оценена, а также дополнительные бонусные баллы для решений VB.NET, а также, к сожалению, окончательная реализация должна быть в нашем одиночном проекте VB!
Ответы
Ответ 1
private string MyMethod(int testParameter, Func<MyObject, string> selector)
{
return selector(_myObject);
}
При использовании делегатов Func
последним параметром является тип возврата, а первый N-1 - типы аргументов. В этом случае для selector
есть один аргумент MyObject
и он возвращает string
.
Вы можете вызвать его так:
string name = _myClassInstance.MyMethod(1, x => x.Name);
string result = _myClassInstance.MyMethod(1, x => x.Code);
Так как тип возвращаемого значения MyMethod
соответствует типу возврата вашего делегата selector
, вы можете сделать его общим:
private T MyMethod<T>(int testParameter, Func<MyObject, T> selector)
{
MyObject obj = //
return selector(obj);
}
EDIT: я не знаю VB.Net, но похоже, что это будет:
Public Function MyMethod(testParameter as Integer, selector as Func(Of MyObject, String))
Return selector(_myObject)
End Function
а общая версия:
Public Function MyMethod(Of T)(testParameter as Integer, selector Func(Of MyObject, T))
Return selector(_myObject)
End Function
Ответ 2
Я покажу вам другой очень гибкий подход (см. DotNetFiddle внизу): вы можете легко написать свои собственные функции LINQ для расширения существующих функций или написать свои собственные функции и воспользоваться мощью запросов LINQ.
В этом примере я улучшаю функцию Linq Distinct
таким образом, чтобы вы могли указать поле, которое используется для группировки.
Использование (Пример):
var myQuery=(from x in Customers select x).MyDistinct(d => d.CustomerID);
В этом примере запрос группируется по CustomerID
и возвращается первый элемент каждой группы.
Декларация MyDistinct
:
public static class Extensions
{
public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query,
Func<T, V> f)
{
return query.GroupBy(f).Select(x=>x.First());
}
}
Вы можете видеть, что f
, второй параметр, объявлен как Func<T, V>
, поэтому он может использоваться оператором .GroupBy
.
Возвращаясь к коду в вашем вопросе, если вы объявили
class MyObject
{
public string Name;
public string Code;
}
private MyObject[] _myObject = {
new MyObject() { Name = "Test1", Code = "T"},
new MyObject() { Name = "Test2", Code = "Q"},
new MyObject() { Name = "Test2", Code = "T"},
new MyObject() { Name = "Test5", Code = "Q"}
};
Вы можете использовать это с вновь определенной функцией MyDistinct
следующим образом:
var myQuery = (from x in _myObject select x).MyDistinct(d => d.Code);
который вернется
Код имени
Test1 T
Test2 Q
или вы можете использовать .MyDistinct(d => d.Name)
в запросе, который возвращает:
Код имени
Test1 T
Test2 Q
Test5 Q
Обратите внимание, что, поскольку MyDistinct
объявлен с обобщениями T
и V
, он автоматически распознает и использует нужные типы объектов и возвращает элементы MyObject
.
Расширенное использование
Обратите внимание, что MyDistinct
всегда берет первый элемент каждой группы. Что если вам нужно условие, определяющее, какой элемент вам нужен?
Вот как вы можете это сделать:
public static class Extensions
{
public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query,
Func<T, V> f,
Func<IGrouping<V,T>,T> h=null)
{
if (h==null) h=(x => x.First());
return query.GroupBy(f).Select(h);
}
}
Эта модификация также позволяет вам использовать его точно так же, как и раньше, то есть, указав один параметр, например .MyDistinct(d => d.Name)
, но также позволяет указать имеющее условие, такое как x => x.FirstOrDefault(y => y.Name.Contains("1")||y.Name.Contains("2"))
в качестве второго параметра, например:
var myQuery2 = (from x in _myObject select x).MyDistinct(d => d.Name,
x=>x.FirstOrDefault(y=>y.Name.Contains("1")||y.Name.Contains("2"))
);
Если вы выполните этот запрос, результат будет:
Код имени
Test1 T
Test2 Q
ноль
поскольку Test5
не соответствует условию (оно не содержит 1 или 2), в третьем ряду вы получаете значение NULL.
Примечание. Если вы хотите показать только условие, вы можете сделать его еще проще, реализовав его следующим образом:
public static IEnumerable<T> MyDistinct2<T, V>(this IEnumerable<T> query,
Func<T, V> f,
Func<T,bool> h=null
)
{
if (h == null) h = (y => true);
return query.GroupBy(f).Select(x=>x.FirstOrDefault(h));
}
В этом случае запрос будет выглядеть так:
var myQuery3 = (from x in _myObject select x).MyDistinct2(d => d.Name,
y => y.Name.Contains("1") || y.Name.Contains("2")
);
поэтому вам не нужно писать x=>x.FirstOrDefault(... condition...)
.
Попробуйте это в DotNetFiddle
Ответ 3
в С#
Тип параметра, который вы ищете Func
private string MyMethod(int testParameter, Func<MyClass,string> selector){
return selector(_myObject);
}
в VB вы все еще хотите, чтобы синтаксис Func немного отличался.
Function MyMethod(ByVal testParameter As Integer, ByVal selector as Func(Of MyClass,string) as string
return selector(_myObject)
End Function
Ответ 4
class MyClass
{
private MyObject _myObject = new MyObject() { Name = "Test", Code = "T" };
private string MyMethod(int testParameter, Func<MyObject, string> selector)
{
return selector(_myObject );
}
}
Ответ 5
Вы можете сделать это с помощью делегата вашего селектора:
delegate string SampleDelegate(MyObject obj);
private string MyMethod(int testParameter, SampleDelegate selector)
{
return selector(_myObject);
}
Ответ 6
Вероятно, вы ищете класс Delegate ( "Делегировать" в VB, "делегировать" на С#) или один из его подтипов.
На этой странице приведены некоторые примеры, которые вы, вероятно, найдете полезными, особенно в нижней части страницы.
Вот пример VB того, что вы хотели бы сделать:
Public Class MyClass
Private Property _myObject As MyObject = New MyObject With {.Name = "Test", .Code = "T"}
Private Function MyMethod(testParameter As Integer, selector As Func(Of MyObject, String)) As String
Return selector(_myObject).ToString
End Function
End Class