2017-08-30 84 views
7

otrzymuje następujące interfejsu i dwie klasy:Uproszczenie pętli foreach z LINQ (wybierając dwa obiekty w każdej iteracji)

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

A biorąc pod uwagę następujące logiki, że ich zastosowań:

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

Test przechodzący i widzę dokładnie to, czego chcę - dwie instancje obiektu na numer:

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} 

Moje pytanie brzmi: jak uprościć logikę pętli z LINQ? Myślę, że może być elegancki sposób na to samo z operatorem SelectMany, ale nie byłem w stanie wyprodukować tego samego wyjścia.

Odpowiedz

15

SelectMany jest rzeczywiście to, co chcesz:

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

Innymi słowy, dla każdego numeru, należy utworzyć tablicę dwóch elementów, który jest następnie używany do SelectMany spłaszczony.

Część new IMyObj[] jest wymagana, a nie tylko new[], ponieważ wnioskowanie o typie nie jest w stanie określić typu tablicy. Jeśli typy były takie same, można zrobić coś takiego:

var list = numbers.SelectMany(n => new[] { new MyObj(n), new MyObj(n) }) 
        .ToList(); 
0

można złączyć dwie listy używając [Union][1]:

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

Alternativly jeden po drugim:

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

Oczywiście wszystkie te podejścia nie będą miały tej samej kolejności, co pierwsze, wszystkie instancje od MyObj1 są przechowywane, a następnie wszystkie instancje MyOb2.

+4

Należy zauważyć, że to nie daje tej samej kolejności, co oryginalny kod OP, w przeciwieństwie do 'SelectMany'. Sugerowałbym również użycie 'Concat' zamiast' Union', ponieważ zachowanie ustalone nie zostało określone ... i wreszcie, musisz określić argumenty typu używając interfejsu, ponieważ inaczej nie skompiluje się. –