2017-08-30 154 views
7

鉴于以下接口和两个类:简化与LINQ foreach循环(选择在每一次迭代的两个对象)

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

而继使用它们的逻辑给出:

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

测试通过,我在里面看到list正是我想要的 - 每个数字有两个对象实例:

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} 

我的问题是,如何使用LINQ简化foreach循环逻辑?我认为可能有一个优雅的方式与SelectMany操作符相同,但我无法生成相同的输出。

回答

15

SelectMany确实是你想要什么:

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

换句话说,每个号码,创建两个元素的数组,然后使用到SelectMany夷为平地。

new IMyObj[]部分是必需的,而不仅仅是new[],因为类型推断不能告诉你想要的数组类型。如果类型是一样的,你可以这样做:

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

,可以串联使用[Union][1]两个列表:

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

Alternativly一个接一个:

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

var result = l1.Concat(l2); 

或者l1.AddRange(l2)

当然,所有这些方法都不会与您的inout具有相同的顺序,因为首先存储所有关闭MyObj1的实例,然后存储所有实例MyOb2

+4

请注意,与“SelectMany”不同,它不给出与OP原始代码相同的顺序。我还建议使用'Concat'而不是'Union'作为集合行为还没有被指定......最后,你需要使用接口指定类型参数,否则将不能编译。 –