3

I have a foreach loop that I would like to replace with a Linq query, but I've not been able to figure out how to write the query. Please see my example below and TIA.

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 
    { }
}
5
  • 1
    Well, it could. But the moment you need to think more than 5 minutes while making LINQ query, then you know there is something wrong. Commented Mar 16, 2011 at 15:48
  • @Euphoric If you take that approach in programming, it will be very hard to expand your knowledge. Taking the 5 minutes to learn something here and there, will likely pay off in the end. Commented Mar 16, 2011 at 15:51
  • @jsmith: Its not about me. Its about other programmers. I would be confused for at least 5 minutes, if I saw queries people proposed. But original is crystal-clear right from the start. Commented Mar 16, 2011 at 15:53
  • @Euphoric I agree to an extent. But that is just because you are more comfortable with foreach loops. Someone comfortable with LINQ will know what those queries mean immediately. It likely depends on the developers he is working with. I agree that it shouldn't be used as a Golden Hammer however. Commented Mar 16, 2011 at 15:56
  • I am going to have to agree with jsmith here. I learned something yesterday. Yes, it took a good deal more than five minutes, but I'm better for it. Once I understood Ani's answer, it makes as much sense to me as the nested foreach loops. Commented Mar 17, 2011 at 14:50

2 Answers 2

5

You can do:

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

Do note that this will create a List<MemoryBlock> rather than a List<IMemory> as in your sample. To make the list of the interface type, make the final call ToList<IMemory>().

EDIT:

In .NET 3.5, in which the IEnumerable<T> interface is not covariant, you can do:

var list1 = device.MemoryBanks
                  .SelectMany(m => new IMemory[] { m }
                                       .Concat(m.MemoryBlocks.Cast<IMemory>()))
                  .ToList();
Sign up to request clarification or add additional context in comments.

5 Comments

Will Concat() not complain because the implied type of new [] {m} is IEnumerable<MemoryBank> and not IEnumerable<IMemory> ?
@BrokenGlass: In .NET 4, the covariance of the interface means that an IEnumerable<MemoryBank> is also an IEnumerable<MemoryBlock>, which is what the first sample relies on. An IEnumerable<MemoryBlock> is in turn an IEnumerable<IMemory>, which you can rely on to call ToList<IMemory>().
Got it, thanks for the additional explanation - so the type of the enumeration created by Concat() would be the first type in the interface hierarchy (going from the specific type up to IMemory) that both input enumerations share, correct? - covariance gets me every time (and +1)
@BrokenGlass: The type inference rules in C# are complicated (for me anyway)! I wouldn't dare make any sweeping statements. But in general, how you've summarized it for Concat isn't correct; the inferred type-argument should be one of the element-types of the sequence. So new string[0].Concat(new FileInfo[0]) wouldn't work; you would have to explicitly specify the right type-argument: new string[0].Concat<object>(new FileInfo[0]). Also note that covariant conversions don't work when the type-argument of the constructed type is a value-type.
@BrokenGlass: My point essentially is that you must think of both the legality of the conversions and the inference of the type-arguments for the method-call. I too find this stuff complicated. I do think that most people not named Skeet/Lippert probably do too.
1

Something like this should work:

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.