C#: обновить значение элемента в списке
Я ищу способ обновить элемент в списке, не перечисляя его самостоятельно.
Я получил класс MyProjects, который содержит список именованных проектов.
Я хочу найти MyProjects.Проекты-класс, где свойство члена Class1 (имя) равно значению "накладные расходы".
Что работает:
foreach (Project prj in MyProjects.Projects) {
if (prj.Name == "Overhead")
prj.IsActive = true;
};
Я, однако, попытался сделать то же самое с помощью Linq, но не смог написать его в виде одной строки. Возможно ли это вообще? Причина, по которой я не люблю повторять в пути выше разве что я уже перебираю весь список в этом кодблоке и думаю, что мог бы быть более красивый способ :)
3 ответов:
Вы не должны пытаться свести все к одной строке - настолько короткой, насколько это возможно для чтения. В этом случае можно использовать:
foreach (var project in MyProjects.Projects.Where(p => p.Name == "Overhead")) { project.IsActive = true; }Это использование LINQ для части запроса , которая подходит, поскольку это то, что означает Q LINQ. Я настоятельно призываю вас не мутировать элементы внутри вызовов LINQ так, как это делает ответ Mayank. Он подвержен ошибкам (о чем свидетельствует оригинальный ответ, не работающий) и противоречит духу LINQ.
Это примерно так читабельно, как получится, ИМО. Он делает точно то же самое, что и исходный код, заметьте - вы не можете избежать чего-то, повторяющего каждый элемент в списке, если каждый элемент может быть тем, что вы хотите обновить.
EDIT: просто для смеха, если Вы действительно, действительно хотели сделать это в довольно минимальном коде, вы могли бы использовать:
// DON'T USE THIS! MyProjects.Project.Count(p => p.Name == "Overhead" && (p.IsActive = true));Здесь мы используем тот факт, что
&&является коротким замыканием, чтобы избежать оценки назначения (p.IsActive = true), Если условие не соответствует. Удобно, что мы присваиваем свойству значениеbool, поскольку это означает, что нам не нужно делать ничего другого, чтобы сделать его допустимым вторым операндом для оператора&&. Мы используемCount(), чтобы полностью оценить результат без создания каких - либо дополнительных списков и т. д.-И мы используем версию с предикатом, чтобы избежать даже необходимости вызоваWhere, что делала предыдущая версия. (LastOrDefaultтоже сработает.) Но это все ужасное злоупотребление, и никогда не должно появляться в каком-либо реальном коде.
Я придумал способ свести его к одной строке, не злоупотребляя LINQ, так как я использую его только для части запроса (фильтра) и использую пользовательский метод расширения для выполнения действия настройки свойства. Вы все еще собираетесь перечислять элементы (вы должны), но вы можете скрыть это в методе расширения. Я подозреваю, что вам на самом деле было все равно, перечисляете вы элемент или нет, вам просто не нравилось количество видимого пространства, которое цикл
foreachбудет занимать в вашем основном код.Используйте этот метод расширения:
public static IEnumerable<T> SetProperty<T>(this IEnumerable<T> list, Action<T> action) { foreach (var item in list) { action.Invoke(item); } return list; }Это позволяет свести его к одной читаемой строке.
Projects.Where(p => p.Name == "Overhead").SetProperty(p => p.IsActive = true);Полная тестовая программа:
using System; using System.Collections.Generic; using System.Linq; namespace ConsoleApplication2 { class Program { static void Main(string[] args) { var Projects = new List<Project>() { new Project() { Name="Overhead", IsActive=false }, new Project() { Name="Nadfadfs", IsActive=false }, new Project() { Name="Overhead", IsActive=false }, new Project() { Name="dasfasdf", IsActive=false } }; PrintProjectList(Projects); Console.WriteLine("--Setting property--"); Projects.Where(p => p.Name == "Overhead").SetProperty(p => p.IsActive = true); PrintProjectList(Projects); Console.WriteLine("Press any key to exit."); Console.ReadKey(); } static void PrintProjectList(IEnumerable<Project> projects) { foreach(var p in projects) { Console.WriteLine($"Name: {p.Name} IsActive: {p.IsActive}"); } } } class Project { public string Name { get; set; } public bool IsActive { get; set; } } public static class Extensions { public static IEnumerable<T> SetProperty<T>(this IEnumerable<T> list, Action<T> action) { foreach (var item in list) { action.Invoke(item); } return list; } } }Вывод:
Имя: Накладные Расходы IsActive: False
Имя: Nadfadfs IsActive: False
Имя: Накладные Расходы IsActive: False
Имя: dasfasdf IsActive: False
--Свойство Установки--
Имя: Накладные Расходы IsActive: True
Имя: Накладные Расходы IsActive: False
Имя: Накладные Расходы IsActive: True
Имя: Накладные Расходы IsActive: False
Оказывается, что моя функция
SetPropertyочень похожа на функцию ForEach, которая уже встроена в фреймворк. Главное отличие состоит в том, что шахта может работать на любомIEnumerable<T>. Этот синтаксис любят одни и ненавидят другие по причинам, которые Эрик Липперт указал в своем блоге (спасибо Джону скиту за указание на это). Также видеть это Обсуждение командой Microsoft. Я оставляю вам сделать свой собственный вывод.На боковой ноте, называя его
SetPropertyявляется своего рода неточным, потому что вы могли бы сделать любое действие на предметы в коллекции. Вы можете назвать егоForEach, но это противоречит структуре. Не уверен, как бы я это назвал, но, возможно,PerformAction.
Следующая инструкция Linq должна работать
MyProjects.Projects.Where(p => p.Name == "Overhead") .Select(x => {x.IsActive = true; return x;}) .ToList();Согласно комментариям @JonSkeet, @TimSchmelter и @LeandroSoares, приведенный выше код действительно плохая идея. Вот некоторые причины
- Вызов
Приведенный выше код не очень удобочитаем и его трудно поддерживать. Приведенный выше код злоупотребляет API, поскольку он заставляет API делать вещи, для которых он не предназначен..ToList()вызывает выполнение и загрузку в память всей ленивой коллекции.
Comments