Streamlining the foreach loop with LINQ (selecting two objects at each iteration)

Given the following interface and two classes:

public interface IMyObj { int Id { get; set; } } public class MyObj1 : IMyObj { public MyObj1(int id) { Id = id; } public int Id { get; set; } public override string ToString() => $"{GetType().Name} : {Id}"; } public class MyObj2 : IMyObj { public MyObj2(int id) { Id = id; } public int Id { get; set; } public override string ToString() => $"{GetType().Name} : {Id}"; } 

And given the following logic that uses them:

 var numbers = new[] { 1, 5, 11, 17 }; var list = new List<IMyObj>(); foreach (var n in numbers) { // I'd like to simplify this part with LINQ... list.Add(new MyObj1(n)); list.Add(new MyObj2(n)); } Assert.AreEqual(8, list.Count); 

The test passes, and I see exactly what I want inside the list - two instances of the object per number:

 Count = 8 [0]: {MyObj1 : 1} [1]: {MyObj2 : 1} [2]: {MyObj1 : 5} [3]: {MyObj2 : 5} [4]: {MyObj1 : 11} [5]: {MyObj2 : 11} [6]: {MyObj1 : 17} [7]: {MyObj2 : 17} 

My question is: how to simplify the logic of a foreach with LINQ? I think there may be an elegant way to do the same with SelectMany , perhaps, but I could not produce the same result.

+7
c # linq
source share
2 answers

SelectMany really what you want:

 var list = numbers.SelectMany(n => new IMyObj[] { new MyObj1(n), new MyObj2(n) }) .ToList(); 

In other words, for each number, create a two-element array, which is then smoothed using SelectMany .

Part of new IMyObj[] required, not just new[] , because type inference cannot determine the type of array you want. If the types were the same, you could do something like:

 var list = numbers.SelectMany(n => new[] { new MyObj(n), new MyObj(n) }) .ToList(); 
+15
source share

You can combine two lists using [Union][1] :

 var result = numbers.Select(x => (IMyObj) new MyObj1(x)) .Union(numbers.Select(x => x => new MyObj2(x))); 

Alternatively one by one:

 var l1 = numbers.Select(x => new MyObj1(x)).Cast<IMyObject>().ToList(); var l2 = numbers.Select(x => new MyObj(x)); var result = l1.Concat(l2); 

Or l1.AddRange(l2) .

Of course, all these approaches will not have the same order as your inout, since all instances with MyObj1 are saved first, and then all instances of MyOb2 .

0
source share

All Articles