Давайте разберем ваш пример кода:
filenames.SelectMany(f =>
Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true)
.Cast<PluginClassAttribute>()
.Select(a => a.PluginType)
).ToList();
Итак, мы начинаем с string[]
, называемого filenames
. Мы вызываем метод расширения SelectMany
для массива, а затем вызываем ToList
для результата:
filenames.SelectMany(
...
).ToList();
SelectMany
принимает делегата в качестве параметра, в этом случае делегат должен принять в качестве входного параметра один параметр типа string
и вернуть IEnumerable<T>
(где выводится тип T
). Вот где лямбды выходят на сцену:
filenames.SelectMany(f =>
Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true)
).ToList()
Здесь произойдет следующее: для каждого элемента в массиве filenames
будет вызван делегат. f
является входным параметром, а все, что находится справа от =>
, является телом метода, на которое ссылается делегат. В этом случае Assembly.LoadFrom
будет вызываться для имени файла в массиве, передавая его имя файла в метод LoadFrom
, используя аргумент f
. На AssemblyInstance
, который возвращается, будет вызван GetCustomAttributes(typeof(PluginClassAttribute), true)
, который возвращает массив Attribute
экземпляров. Таким образом, компилятор не может сделать вывод, что тип T
, упомянутый ранее, является Assembly
.
На IEnumerable<Attribute>
, который возвращается, будет вызываться Cast<PluginClassAttribute>()
, возвращая IEnumerable<PluginClassAttribute>
.
Итак, теперь у нас есть IEnumerable<PluginClassAttribute>
, и мы вызываем Select
. Метод Select
похож на SelectMany
, но возвращает один экземпляр типа T
(который выводится компилятором) вместо IEnumerable<T>
. Настройка идентична; для каждого элемента в IEnumerable<PluginClassAttribute>
он вызовет определенный делегат, передав в него текущее значение элемента:
.Select(a => a.PluginType)
Опять же, a
- это входной параметр, a.PluginType
- это тело метода. Таким образом, для каждого экземпляра PluginClassAttribute
в списке он будет возвращать значение свойства PluginType
(я предполагаю, что это свойство имеет тип Type
).
Резюме
Если мы склеим эти кусочки вместе:
// process all strings in the filenames array
filenames.SelectMany(f =>
// get all Attributes of the type PluginClassAttribute from the assembly
// with the given file name
Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true)
// cast the returned instances to PluginClassAttribute
.Cast<PluginClassAttribute>()
// return the PluginType property from each PluginClassAttribute instance
.Select(a => a.PluginType)
).ToList();
Лямбда против делегатов
Давайте закончим это, сравнив лямбды с делегатами. Возьмите следующий список:
List<string> strings = new List<string> { "one", "two", "three" };
Скажем, мы хотим отфильтровать те, которые начинаются с буквы "t":
var result = strings.Where(s => s.StartsWith("t"));
Это самый распространенный подход; установить его с помощью лямбда-выражения. Но есть альтернативы:
Func<string,bool> func = delegate(string s) { return s.StartsWith("t");};
result = strings.Where(func);
Это по сути то же самое: сначала мы создаем делегат типа Func<string, bool>
(это означает, что он принимает string
в качестве входного параметра и возвращает bool
). Затем мы передаем этот делегат в качестве параметра методу Where
. Это то, что компилятор сделал для нас за кулисами в первом примере (strings.Where(s => s.StartsWith("t"));
).
Третий вариант - просто передать делегат неанонимному методу:
private bool StringsStartingWithT(string s)
{
return s.StartsWith("t");
}
// somewhere else in the code:
result = strings.Where(StringsStartingWithT);
Итак, в нашем случае лямбда-выражение представляет собой довольно компактный способ определения делегата, обычно ссылающийся на анонимный метод.
И если у вас была энергия, прочитанная полностью здесь, хорошо, спасибо за ваше время:)