这个foreach循环能否用Linq替代?

3

我有一个foreach循环,想用Linq查询替换它,但我一直无法找到如何编写查询的方法。请看下面的示例并谢谢。

using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication
{
    class ExampleProgram
    {
        static void Main( string[] args )
        {
            Device device = new Device();

            // The goal is to populate this list.
            var list1 = new List<IMemory>();

            // I would like to replace this loop with a Linq query
            foreach( MemoryBank memoryBank in device.MemoryBanks )
            {               
                list1.Add( memoryBank ); // add the memory bank

                foreach( MemoryBlock memoryBlock in memoryBank.MemoryBlocks )
                    list1.Add( memoryBlock ); // add the memory block
            }

            //var list2 = from memoryBank in device.MemoryBanks
            //            from memoryBlock in memoryBank.MemoryBlocks
            //            select ???;
        }
    }

    interface IMemory 
    {}

    public class Device
    { 
        public IList<MemoryBank> MemoryBanks { get; set; } 
    }

    public class MemoryBank : MemoryBlock, IMemory
    {
        public IList<MemoryBlock> MemoryBlocks { get; set; } 
    }

    public class MemoryBlock : IMemory 
    { }
}

1
好的,它可能会。但是当你在制作LINQ查询时需要思考超过5分钟的时候,你就知道出了什么问题。 - Euphoric
@Euphoric 如果你在编程中采取这种方法,将很难扩展你的知识。花费5分钟时间学习一些东西,最终可能会得到回报。 - jsmith
@jsmith:这不是关于我,而是其他程序员。如果我看到人们提出的查询,我至少会感到困惑5分钟。但原始版本从一开始就非常清晰明了。 - Euphoric
@Euphoric 我同意到一定程度。但这只是因为你更习惯于使用foreach循环。熟悉LINQ的人会立即知道这些查询的含义。这可能取决于他正在与哪些开发人员合作。我同意不应该将其用作黄金锤子。 - jsmith
我得同意jsmith的观点。昨天我学到了一些东西。虽然花费的时间超过了五分钟,但我因此变得更好了。一旦我理解了Ani的答案,它对我来说就像嵌套的foreach循环一样有意义。 - Michael J
2个回答

5
你可以做到:
var list1 = device.MemoryBanks
                  .SelectMany(m => new[] { m }.Concat(m.MemoryBlocks))
                  .ToList();

请注意,这将创建一个List<MemoryBlock>而不是您示例中的List<IMemory>。要使接口类型的列表,请进行最后一次调用ToList<IMemory>()
编辑:
在不协变的.NET 3.5中,您可以执行以下操作:IEnumerable<T>接口:

var list1 = device.MemoryBanks
                  .SelectMany(m => new IMemory[] { m }
                                       .Concat(m.MemoryBlocks.Cast<IMemory>()))
                  .ToList();

Concat() 不会抱怨,因为 new [] {m} 的隐含类型是 IEnumerable<MemoryBank> 而不是 IEnumerable<IMemory> - BrokenGlass
@BrokenGlass:在.NET 4中,接口的协变性意味着IEnumerable<MemoryBank>也是IEnumerable<MemoryBlock>,这是第一个示例所依赖的。而IEnumerable<MemoryBlock>又是IEnumerable<IMemory>,你可以依靠它来调用ToList<IMemory>() - Ani
明白了,谢谢你的额外解释 - 所以通过Concat()创建的枚举类型会是接口层次结构中的第一个类型(从特定类型到IMemory)两个输入枚举都共享的类型,对吗?- 协变性总是让我感到困惑(加一)。 - BrokenGlass
@BrokenGlass:对我来说,C#中的类型推理规则很复杂!我不敢妄下定论。但总体而言,你对Concat进行的总结是不正确的;推断出的类型参数应该是序列元素类型之一。所以new string[0].Concat(new FileInfo[0])是行不通的;您必须显式指定正确的类型参数:new string[0].Concat<object>(new FileInfo[0])。还要注意,当构造类型的类型参数是值类型时,协变转换无法工作。 - Ani
@BrokenGlass:我的观点基本上是你必须考虑转换的合法性以及方法调用的类型参数推断。我也觉得这些东西很复杂。我认为除了Skeet/Lippert之外的大多数人可能也觉得很复杂。 - Ani

1

这样应该可以工作:

list1 = device.MemoryBanks
              .Select( x=> x.MemoryBlocks.AsEnumerable<IMemory>().Concat(x))
              .SelectMany( x=>x)
              .ToList();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接