C# 用 LINQ 替换嵌套的 foreach;修改和更新内部深处的属性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1336177/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 15:17:44  来源:igfitidea点击:

Replacing nested foreach with LINQ; modify and update a property deep within

c#linqalgorithmlinq-to-objects

提问by p.campbell

Consider the requirement to changea data member on one or more properties of an object that is 5 or 6 levels deep.

考虑在5 或 6 层深的对象的一个​​或多个属性上更改数据成员的要求。

There are sub-collections that need to be iterated through to get to the property that needs inspection & modification.

有一些子集合需要迭代才能到达需要检查和修改的属性。

Here we're calling a method that cleans the street address of a Employee. Since we're changing data within the loops, the current implementation needs a forloop to prevent the exception:

这里我们调用了一个方法来清除 Employee 的街道地址。由于我们在循环中更改数据,当前的实现需要一个for循环来防止异常:

Cannot assign to "someVariable" because it is a 'foreach iteration variable'

无法分配给“someVariable”,因为它是“foreach 迭代变量”

Here's the current algorithm (obfuscated) with nested foreachand a for.

这是当前的算法(混淆),带有嵌套foreachfor.

foreach (var emp in company.internalData.Emps)
{
    foreach (var addr in emp.privateData.Addresses)
    {
        int numberAddresses = addr.Items.Length;

        for (int i = 0; i < numberAddresses; i++)
        {
            //transform this street address via a static method
            if (addr.Items[i].Type =="StreetAddress")
               addr.Items[i].Text = CleanStreetAddressLine(addr.Items[i].Text);
        }
    }
}

Question:Can this algorithm be reimplemented using LINQ? The requirement is for the original collection to have its data changed by that static method call.

问题:这个算法可以用 LINQ 重新实现吗?要求是原始集合的数据由该静态方法调用更改。

Update:I was thinking/leaning in the direction of a jQuery/selector type solution. I didn't specifically word this question in that way. I realize that I was over-reaching on that idea (no side-effects). Thanks to everyone! If there is such a way to perform a jQuery-like selector, please let's see it!

更新:我正在考虑/倾向于 jQuery/选择器类型解决方案的方向。我并没有以那种方式专门提出这个问题。我意识到我在这个想法上太过分了(没有副作用)。谢谢大家!如果有这样的方式来执行类似jQuery的选择器,请让我们看看吧!

采纳答案by Josh E

LINQ is not intended to modify sets of objects. You wouldn't expect a SELECT sql statement to modify the values of the rows being selected, would you? It helps to remember what LINQ stands for - Language INtegrated Query. Modifying objects within a linq query is, IMHO, an anti-pattern.

LINQ 不用于修改对象集。您不会期望 SELECT sql 语句修改被选择行的值,对吗?它有助于记住LINQ代表-大号anguage INtegrated Query。恕我直言,在 linq 查询中修改对象是一种反模式。

Stan R.'s answer would be a better solution using a foreachloop, I think.

我认为,Stan R. 的答案是使用foreach循环的更好解决方案。

回答by Mehrdad Afshari

foreach(var item in company.internalData.Emps
                        .SelectMany(emp => emp.privateData.Addresses)
                        .SelectMany(addr => addr.Items)
                        .Where(addr => addr.Type == "StreetAddress"))
     item.Text = CleanStreetAddressLine(item.Text);

回答by Rune FS

LINQ does not provide the option of having side effects. however you could do:

LINQ 不提供有副作用的选项。但是你可以这样做:

company.internalData.Emps.SelectMany(emp => emp.Addresses).SelectMany(addr => Addr.Items).ToList().ForEach(/*either make an anonymous method or refactor your side effect code out to a method on its own*/);

回答by Stan R.

var dirtyAddresses = company.internalData.Emps.SelectMany( x => x.privateData.Addresses )
                                              .SelectMany(y => y.Items)
                                              .Where( z => z.Type == "StreetAddress");

  foreach(var addr in dirtyAddresses)
    addr.Text = CleanStreetAddressLine(addr.Text);

回答by John Fisher

You can do this, but you don't really want to. Several bloggers have talked about the functional nature of Linq, and if you look at all the MS supplied Linq methods, you will find that they don't produce side effects. They produce return values, but they don't change anything else. Search for the arguments over a Linq ForEach method, and you'll get a good explanation of this concept.

你可以这样做,但你真的不想这样做。有几位博主谈到了Linq 的功能性,如果你查看所有MS 提供的Linq 方法,你会发现它们不会产生副作用。它们产生返回值,但不会改变其他任何东西。搜索有关 Linq ForEach 方法的参数,您将得到对这个概念的很好的解释。

With that in mind, what you probaly want is something like this:

考虑到这一点,您可能想要的是这样的:

var addressItems = company.internalData.Emps.SelectMany(
    emp => emp.privateData.Addresses.SelectMany(
           addr => addr.Items
    )
);
foreach (var item in addressItems)
{
   ...
}

However, if you do want to do exactly what you asked, then this is the direction you'll need to go:

但是,如果您确实想完全按照您的要求做,那么这就是您需要走的方向:

var addressItems = company.internalData.Emps.SelectMany(
    emp => emp.privateData.Addresses.SelectMany(
           addr => addr.Items.Select(item =>
           { 
              // Do the stuff
              return item;
           }) 
    )
);

回答by Jay Bazuzi

I don't like mixing "query comprehension" syntax and dotted-method-call syntaxin the same statement.

我不喜欢在同一个语句中混合“查询理解”语法和点分方法调用语法

I do like the idea of separating the queryfrom the action. These are semantically distinct, so separating them in code often makes sense.

我确实喜欢将查询操作分开的想法。它们在语义上是不同的,因此在代码中将它们分开通常是有意义的。

var addrItemQuery = from emp in company.internalData.Emps
                    from addr in emp.privateData.Addresses
                    from addrItem in addr.Items
                    where addrItem.Type == "StreetAddress"
                    select addrItem;

foreach (var addrItem in addrItemQuery)
{
    addrItem.Text = CleanStreetAddressLine(addrItem.Text);
}

A few style notes about your code; these are personal, so I you may not agree:

关于您的代码的一些样式说明;这些是个人的,所以我你可能不同意:

  • In general, I avoid abbreviations (Emps, emp, addr)
  • Inconsistent names are more confusing (addrvs. Addresses): pick one and stick with it
  • The word "number" is ambigious. It can either be an identity ("Prisoner number 378 please step forward.") or a count ("the number of sheep in that field is 12."). Since we use both concepts in code a lot, it is valuable to get this clear. I use often use "index" for the first one and "count" for the second.
  • Having the typefield be a string is a code smell. If you can make it an enumyour code will probably be better off.
  • 通常,我避免使用缩写 ( Emps, emp, addr)
  • 不一致的名称更容易混淆(addrvs. Addresses):选择一个并坚持使用
  • “数”字含糊不清。它可以是一个身份(“囚犯编号 378 请上前。”)或计数(“该字段中的羊数是 12。”)。由于我们在代码中经常使用这两个概念,因此弄清楚这一点很有价值。我经常对第一个使用“索引”,对第二个使用“计数”。
  • 将该type字段设为字符串是一种代码异味。如果你能把它变成一个enum你的代码可能会更好。

回答by Martin Joná?

Dirty one-liner.

肮脏的单线。

company.internalData.Emps.SelectMany(x => x.privateData.Addresses)
    .SelectMany(x => x.Items)
    .Where(x => x.Type == "StreetAddress")
    .Select(x => { x.Text = CleanStreetAddressLine(x.Text); return x; });

回答by JeeShen Lee

To update the LINQ result using FOREACH loop, I first create local ‘list' variableand then perform the update using FOREACH Loop. The value are updated this way. Read more here:

要使用 FOREACH 循环更新 LINQ 结果,我首先创建本地“列表”变量,然后使用 FOREACH 循环执行更新。值以这种方式更新。在此处阅读更多信息:

How to update value of LINQ results using FOREACH loop

如何使用 FOREACH 循环更新 LINQ 结果的值