17

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

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

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

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

foreach这是带有嵌套和 a的当前算法(混淆)for

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);
        }
    }
}

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

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

4

9 回答 9

21
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);
于 2009-08-26T17:04:43.123 回答
14
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);
于 2009-08-26T17:07:41.433 回答
12

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

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

于 2009-08-26T17:09:30.993 回答
10

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

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

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);
}

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

  • 一般来说,我避免使用缩写(Emps, emp, addr
  • 不一致的名称更容易混淆(addrvs. Addresses):选择一个并坚持下去
  • “数字”这个词是模棱两可的。它可以是一个身份(“第 378 号囚犯,请向前走。”)或计数(“该字段中的绵羊数量为 12。”)。由于我们在代码中经常使用这两个概念,因此弄清楚这一点很有价值。我经常使用“索引”作为第一个,“计数”作为第二个。
  • type字段设为字符串是一种代码味道。如果你能做到这一点,enum你的代码可能会更好。
于 2009-08-26T17:34:48.150 回答
2

肮脏的单线。

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; });
于 2009-08-26T17:49:22.737 回答
1

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*/);
于 2009-08-26T17:04:49.927 回答
1

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

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

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

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

var addressItems = company.internalData.Emps.SelectMany(
    emp => emp.privateData.Addresses.SelectMany(
           addr => addr.Items.Select(item =>
           { 
              // Do the stuff
              return item;
           }) 
    )
);
于 2009-08-26T17:10:23.957 回答
0

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

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

于 2012-04-19T14:44:46.557 回答
0

我克隆了列表并使用 NET 4.7.2

List<TrendWords> ListCopy = new List<TrendWords>(sorted);

        foreach (var words in stopWords)
        {
            foreach (var item in ListCopy.Where(w => w.word == words))
            {
                item.disabled = true;
            }

        }
于 2022-02-01T16:06:54.730 回答