Подтвердить что ты не робот

Селектор значений свойства 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!

4b9b3361

Ответ 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