我知道这个问题之前已经被问过很多次,但我尝试了回答,它们似乎不起作用。
我有两个长度相同但类型不同的列表,并且我想同时遍历它们,使list1[i]
与list2[i]
相对应。
例如:
假设我有列表1(作为List<string>
)和列表2(作为List<int>
)
我想做类似这样的事情
foreach( var listitem1, listitem2 in list1, list2)
{
// do stuff
}
这个是否可能?
我知道这个问题之前已经被问过很多次,但我尝试了回答,它们似乎不起作用。
我有两个长度相同但类型不同的列表,并且我想同时遍历它们,使list1[i]
与list2[i]
相对应。
例如:
假设我有列表1(作为List<string>
)和列表2(作为List<int>
)
我想做类似这样的事情
foreach( var listitem1, listitem2 in list1, list2)
{
// do stuff
}
这个是否可能?
Zip()
操作符,因此您可以在早期版本的.NET中使用它。int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
// The following example concatenates corresponding elements of the
// two input sequences.
var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);
foreach (var item in numbersAndWords)
{
Console.WriteLine(item);
}
// OUTPUT:
// 1 one
// 2 two
// 3 three
有用的链接:
编辑 - 在两个集合中定位到同一索引时进行迭代
如果要以“同步”的方式移动两个集合,即使用第一个集合的第一个元素与第二个集合的第一个元素,然后是第二个与第二个,以此类推,而不需要执行任何副作用代码,则可以参见@sll's answer 并使用.Zip()
将在同一索引处投射出一对元素,直到其中一个集合没有元素为止。
更一般地说
您可以从每个集合的IEnumerable
中访问IEnumerator
,使用GetEnumerator()
方法,然后在需要移动到该集合的下一个元素时调用MoveNext()
。 当处理两个或多个有序流而不需要将流实体化时,这种技术很常见。
var stream1Enumerator = stream1.GetEnumerator();
var stream2Enumerator = stream2.GetEnumerator();
var currentGroupId = -1; // Initial value
// i.e. Until stream1Enumerator runs out of
while (stream1Enumerator.MoveNext())
{
// Now you can iterate the collections independently
if (stream1Enumerator.Current.Id != currentGroupId)
{
stream2Enumerator.MoveNext();
currentGroupId = stream2Enumerator.Current.Id;
}
// Do something with stream1Enumerator.Current and stream2Enumerator.Current
}
正如其他人所指出的,如果集合被实例化并支持索引,例如 ICollection
接口,你也可以使用下标 []
操作符,尽管这在现今感觉相当笨拙:
如其他人指出,如果集合被实例化且支持索引,例如 ICollection
接口,则还可以使用下标 []
运算符,尽管现在这种方式感觉相当笨拙:
var smallestUpperBound = Math.Min(collection1.Count, collection2.Count);
for (var index = 0; index < smallestUpperBound; index++)
{
// Do something with collection1[index] and collection2[index]
}
最后,Linq的.Select()
还有一个重载函数,它提供了返回元素的索引序号,这对于某些情况也是很有用的。
例如,以下代码将交替地配对collection1
中的所有元素和collection2
的前两个元素:
var alternatePairs = collection1.Select(
(item1, index1) => new
{
Item1 = item1,
Item2 = collection2[index1 % 2]
});
Dispose()
(或包装在 using
中)枚举器。 - Bip901简短的回答是不行。
更详细的解释是因为foreach
只是语法糖 - 它从集合中获取一个迭代器并调用Next
方法。这在同时使用两个集合时是不可能的。
如果你只想要一个循环,你可以使用for
循环并使用相同的索引值来遍历两个集合。
for(int i = 0; i < collectionsLength; i++)
{
list1[i];
list2[i];
}
另一个选择是使用 LINQ Zip 运算符(.NET 4.0 中的新功能)将两个集合合并成一个,并迭代处理结果。
foreach(var tup in list1.Zip(list2, (i1, i2) => Tuple.Create(i1, i2)))
{
var listItem1 = tup.Item1;
var listItem2 = tup.Item2;
/* The "do stuff" from your question goes here */
}
如果你可以在 lambda 表达式中创建元组,那么“做事情”的大部分工作都可以在其中完成,这会更好。
如果集合可以迭代,那么使用 for()
循环可能仍然更简单。
更新:现在 C# 7.0 中内置了对 ValueTuple
的支持,我们可以使用以下代码:
foreach ((var listitem1, var listitem2) in list1.Zip(list2, (i1, i2) => (i1, i2)))
{
/* The "do stuff" from your question goes here */
}
您可以将两个 IEnumerable<> 封装在辅助类中:
var nums = new []{1, 2, 3};
var strings = new []{"a", "b", "c"};
ForEach(nums, strings).Do((n, s) =>
{
Console.WriteLine(n + " " + s);
});
//-----------------------------
public static TwoForEach<A, B> ForEach<A, B>(IEnumerable<A> a, IEnumerable<B> b)
{
return new TwoForEach<A, B>(a, b);
}
public class TwoForEach<A, B>
{
private IEnumerator<A> a;
private IEnumerator<B> b;
public TwoForEach(IEnumerable<A> a, IEnumerable<B> b)
{
this.a = a.GetEnumerator();
this.b = b.GetEnumerator();
}
public void Do(Action<A, B> action)
{
while (a.MoveNext() && b.MoveNext())
{
action.Invoke(a.Current, b.Current);
}
}
}
为什么不使用for()而不是foreach?例如...
int length = list1.length;
for(int i = 0; i < length; i++)
{
// do stuff with list1[i] and list2[i] here.
}