如何使用LINQ在对象列表中的字段中分配与另一个项目对应的项目?

时间:2018-07-02 20:01:56

标签: c# linq

假设我有一个List<MyClass> myObjects,而我有int[] numbers。鉴于:

  1. numbers的长度等于mObjects.Count
  2. 我正在使用的类具有其他一些字段和方法,如下所示:

    class MyClass
    {
        public int Number;
        public int AnotherNumber;
        public void WorkWithNumber() { /* Do some work. */ }
    }
    

是否可以使用linq将numbers的每个项目分配给列表中相应的MyClass.Number?最好使用myObjects.ForEach()

7 个答案:

答案 0 :(得分:1)

理想情况下,您应该使用foreach循环:

foreach (var p in myList.Select((obj, idx) => new { Val = numbers[idx], Obj = obj })) {
    p.Obj.Number = p.Val;
}

您可以在没有foreach的情况下使用相同的技巧,但是解决方案的可读性会大大降低。

答案 1 :(得分:1)

用于在两个列表上同步迭代的LINQ算法为 Enumerable.Zip

但是除非您对表达式的结果求值,否则这是行不通的,因为

  1. 您的要求是非功能性编程,因为修改第二个列表中的现有项目会产生副作用,
  2. LINQ使用延迟执行,除非对表达式求值,否则不会有副作用。

您可以通过应用.ToList()来避免这种陷阱并丢弃结果:

numbers.Zip(myObjects, (i, obj) => obj.Number = i).ToList();

但这不是那么漂亮。

  1. 因为您创建的结果列表只是出于丢弃目的。
  2. 因为在值类型的情况下它会不幸地失败。

另一种选择是添加非功能性重载.Zip 。即:

public static class EnumerableX
{ public static void Zip<T1,T2>(this IEnumerable<T1> list1, IEnumerable<T2> list2, Action<T1,T2> act)
  { using (var en1 = list1.GetEnumerator())
    using (var en2 = list2.GetEnumerator())
      while (en1.MoveNext() & en2.MoveNext())
        act(en1.Current, en2.Current);
  }
}

使用此扩展名以下行将完成工作

numbers.Zip(myObjects, (i, obj) => { obj.Number = i; });

但是值类型的问题仍然存在。为了克服这个问题,Action的参数需要为ref T1类型。但这显然需要更多工作,并且不在这个问题的范围内。

答案 2 :(得分:0)

我将在myObjects上使用for循环,并从Number中的相应索引中分配numbers属性。

void Main()
{
    var myObjects = new List<MyClass>();
    myObjects.Add(new MyClass());
    myObjects.Add(new MyClass());
    myObjects.Add(new MyClass());

    var numbers = new int[] { 1, 2, 3 };

    for(var i = 0; i < myObjects.Count; i++)
    {
        myObjects.ElementAt(i).Number = numbers[i];
        Console.WriteLine($"myObjects.ElementAt(i).Number = {myObjects.ElementAt(i).Number}");
    }

    //  myObjects.ElementAt(i).Number = 1
    //  myObjects.ElementAt(i).Number = 2
    //  myObjects.ElementAt(i).Number = 3
}

class MyClass
{
    public int Number;
    public int AnotherNumber;
    public void WorkWithNumber() { /* Do some work. */ }
}

答案 3 :(得分:0)

如果您真的想使用LINQ,则Select的重载将带有两个参数的lambda:object及其索引:

 public class Program
{
    public static void Main(string[] args)
    {
        var list = new []{1,2,3,4,5};

        var objects = new []{new Obj(),new Obj(),new Obj(),new Obj(),new Obj()};

        objects = objects.Select((x,i)=>{
            x.Int = list[i];

            return x;
        }).ToArray();

        foreach(var o in objects)
        {
            Console.WriteLine(o.Int);
        }

    }
    }

public class Obj 
{
    public int Int {get;set;}
}

您需要调用ToArray()以便对LINQ进行评估,但是不必将其归还给对象变量

答案 4 :(得分:0)

如果您想以简单的方式限制ForEach,可以尝试一下。

List<MyClass> myObjects = new List<MyClass>();
myObjects.Add(new MyClass());
myObjects.Add(new MyClass());
myObjects.Add(new MyClass());

int[] numbers = new int[] { 1, 2, 3 };
int i = 0;
myObjects.ForEach(x =>
{
    x.Number = numbers[i];
    i++;
});

c#在线:http://rextester.com/MSBFH56439

答案 5 :(得分:0)

这是您想要的吗?

//Set things up...
List<MyClass> myObjects = new List<MyClass>
{
    new MyClass{Number = 23},
    new MyClass{Number = 42},
    new MyClass{Number = 66}
};
int [] numbers = new int[myObjects.Count];

//answer your question...
var i = 0;
foreach (var myObj in myObjects)
{
    numbers[i] = myObj.Number;
    ++i;
}

您可以使用“ for”遍历两个集合,但是“ foreach”仅适用于单个集合(除非有我不知道的语言功能)。

答案 6 :(得分:0)

您可以利用overload to Select为您传递索引:

numbers.Select((item, index) => myObjects[index].Number = item)
       .ToList();

此方法的缺点在于,您需要调用ToList/ToArray来强制执行Select的开销。您可以编写自己的扩展方法来迭代可枚举,但不执行任何操作:

public static class MyEnumerableExtensions
{
    public static void ForceExecute<T>(this IEnumerable<T> source)
    {
        foreach (T item in source);
    }
}

...然后:

numbers.Select((item, index) => myObjects[index].Number = item)
       .ForceExecute();

...尽管这样做对您的程序员来说可能不那么直观。