用LINQ替换嵌套的foreach;在内部修改和更新属性

时间:2009-08-26 16:59:39

标签: c# linq algorithm linq-to-objects

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

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

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

  

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

以下是嵌套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);
        }
    }
}

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

更新:我在思考/倾向于jQuery / selector类型解决方案的方向。我没有以这种方式具体说出这个问题。我意识到我对这个想法的影响太大(没有副作用)。谢谢大家!如果有这样的方法来执行类似jQuery的选择器,请让我们看看它!

8 个答案:

答案 0 :(得分:19)

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

答案 1 :(得分:13)

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

答案 2 :(得分:12)

LINQ不用于修改对象集。您不希望SELECT sql语句修改所选行的值,不是吗?它有助于记住LINQ代表什么 - L anguage IN tegrated Q uery。修改linq查询中的对象是恕我直言,反模式。

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

答案 3 :(得分:10)

I don't like mixing "query comprehension" syntax and dotted-method-call syntax在同一声明中。

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

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

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

  • 一般情况下,我会避免缩写(Empsempaddr
  • 不一致的名称更令人困惑(addrAddresses):选择一个并坚持下去
  • “数字”这个词是暧昧的。它可以是一个身份(“囚犯号码378请前进。”)或一个计数(“该领域的绵羊数量是12”)。由于我们在代码中经常使用这两个概念,因此明确这一点很有价值。我经常使用“index”作为第一个,“count”作为第二个。
  • type字段设为字符串是代码气味。如果您可以将其设为enum,那么您的代码可能会更好。

答案 4 :(得分: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; });

答案 5 :(得分: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*/);

答案 6 :(得分: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;
           }) 
    )
);

答案 7 :(得分:0)

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

How to update value of LINQ results using FOREACH loop