有没有一些我还没有遇到的罕见语言结构(就像我最近在 Stack Overflow 上学到的一些)可以在 C# 中获得表示当前 foreach 循环迭代的值?
例如,根据情况,我目前会做类似于以下的操作:
int i = 0;
foreach (Object o in collection)
{
// ...
i++;
}
foreach (var item in ForEachHelper.WithIndex(collection))
{
Console.Write("Index=" + item.Index);
Console.Write(";Value= " + item.Value);
Console.Write(";IsLast=" + item.IsLast);
Console.WriteLine();
}
以下是 ForEachHelper
类的代码。
public static class ForEachHelper
{
public sealed class Item<T>
{
public int Index { get; set; }
public T Value { get; set; }
public bool IsLast { get; set; }
}
public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable)
{
Item<T> item = null;
foreach (T value in enumerable)
{
Item<T> next = new Item<T>();
next.Index = 0;
next.Value = value;
next.IsLast = false;
if (item != null)
{
next.Index = item.Index + 1;
yield return item;
}
item = next;
}
if (item != null)
{
item.IsLast = true;
yield return item;
}
}
}
为什么要用foreach?!
如果你使用的是List,最简单的方法是用for代替foreach:
for (int i = 0 ; i < myList.Count ; i++)
{
// Do something...
}
或者如果你想使用foreach:
foreach (string m in myList)
{
// Do something...
}
你可以使用这个来知道每次循环的索引:
myList.indexOf(m)
C# 7终于给我们提供了一种优雅的方法来实现这一点:
static class Extensions
{
public static IEnumerable<(int, T)> Enumerate<T>(
this IEnumerable<T> input,
int start = 0
)
{
int i = start;
foreach (var t in input)
{
yield return (i++, t);
}
}
}
class Program
{
static void Main(string[] args)
{
var s = new string[]
{
"Alpha",
"Bravo",
"Charlie",
"Delta"
};
foreach (var (i, t) in s.Enumerate())
{
Console.WriteLine($"{i}: {t}");
}
}
}
int index=0;
foreach (var item in enumerable)
{
blah(item, index); // some code that depends on the index
index++;
}
更新的代码
enumerable.ForEach((item, index) => blah(item, index));
扩展方法:
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T, int> action)
{
var unit = new Unit(); // unit is a new type from the reactive framework (http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx) to represent a void, since in C# you can't return a void
enumerable.Select((item, i) =>
{
action(item, i);
return unit;
}).ToList();
return pSource;
}
这个回答建议向C#语言团队进行游说以获得直接的语言支持。
主要回答认为:
显然,索引的概念与枚举的概念不同,因此无法实现。
虽然在当前的C#语言版本(2020)中是正确的,但这并不是CLR /语言概念上的限制,它是可以做到的。
微软的C#语言开发团队可以创建一个新的C#语言功能,通过添加对新接口IIndexedEnumerable的支持来实现。
foreach (var item in collection with var index)
{
Console.WriteLine("Iteration {0} has value {1}", index, item);
}
//or, building on @user1414213562's answer
foreach (var (item, index) in collection)
{
Console.WriteLine("Iteration {0} has value {1}", index, item);
}
如果使用foreach()
并且存在with var index
,则编译器期望项目集合声明IIndexedEnumerable
接口。如果接口不存在,则编译器可以填充源代码并将其包装为IndexedEnumerable
对象,以添加跟踪索引的代码。
interface IIndexedEnumerable<T> : IEnumerable<T>
{
//Not index, because sometimes source IEnumerables are transient
public long IterationNumber { get; }
}
稍后,CLR可以更新以具有内部索引跟踪,仅在指定with
关键字且源未直接实现IIndexedEnumerable
时使用。
为什么:
虽然这里的大多数人不是微软员工,但这是一个正确的答案,您可以游说微软添加此功能。 您已经可以使用扩展函数和元组构建自己的迭代器,但是Microsoft可以增加语法糖来避免使用扩展函数。
我将您名单中的第${i}个人名 ${name.toUpperCase()} 大写了。
)
}) - oxwilder仅适用于List,而非任何IEnumerable,但在LINQ中有这个:
IList<Object> collection = new List<Object> {
new Object(),
new Object(),
new Object(),
};
foreach (Object o in collection)
{
Console.WriteLine(collection.IndexOf(o));
}
Console.ReadLine();
@Jonathan 我并没有说这是一个好答案,我只是说它展示了可以做到他所要求的 :)
@Graphain 我不指望它会很快 - 我不太确定它是如何工作的,它可能需要每次重新迭代整个列表才能找到匹配的对象,这将进行大量比较。
话虽如此,List可能会保存每个对象的索引以及计数。
如果Jonathan能详细说明一下他的更好的想法就好了。
然而,在foreach中只需保持一个计数器来跟踪进度会更好,更简单,也更具适应性。
obj.Value
会变得相当老套。foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
...
}
// using foreach loop how to get index number:
foreach (var result in results.Select((value, index) => new { index, value }))
{
// do something
}
var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}
public struct ValueWithIndex<T>
{
public readonly T Value;
public readonly int Index;
public ValueWithIndex(T value, int index)
{
this.Value = value;
this.Index = index;
}
public static ValueWithIndex<T> Create(T value, int index)
{
return new ValueWithIndex<T>(value, index);
}
}
public static class ExtensionMethods
{
public static IEnumerable<ValueWithIndex<T>> GetEnumeratorWithIndex<T>(this IEnumerable<T> enumerable)
{
return enumerable.Select(ValueWithIndex<T>.Create);
}
}
int index;
foreach (Object o in collection)
{
index = collection.indexOf(o);
}
IList
的集合。IndexOf
的时间复杂度为 O(n)
,所以这是 O(n^2)
的。2)如果列表中有重复的项,则会失败。 - CodesInChaos
foreach
的主要目的是为所有集合提供一个通用的迭代机制,无论它们是否可索引(如List
)或不可索引(如Dictionary
)。 - Brian GideonDictionary
不能被索引,但Dictionary
的迭代会按特定顺序遍历它(即一个枚举器是可索引的,因为它按顺序产生元素)。在这个意义上,我们可以说我们不是在寻找集合内的索引,而是当前枚举元素在枚举中的索引(即我们是否处于第一个、第五个或最后一个枚举元素)。 - Matt Mitchell