我有一个可枚举的函数,但第一项的函数应该有点不同,例如:
void start() {
List<string> a = ...
a.ForEach(DoWork);
}
bool isFirst = true;
private void DoWork(string s) {
// do something
if(isFirst)
isFirst = false;
else
print("first stuff");
// do something
}
你如何重构这一点以避免那个丑陋的旗帜?
答案 0 :(得分:9)
如果您真的想要对第一项做某事,可以解释Jimmy Hoffa的回答。
DoFirstWork(a[0])
a.Skip(1).ForEach(DoWork)
如果重点是逻辑与列表的其余部分是分开的,那么你应该使用一个单独的函数。
答案 1 :(得分:2)
这可能有点沉重,但我暂时从另一个SO问题中解决了这个问题。
public static void IterateWithSpecialFirst<T>(this IEnumerable<T> source,
Action<T> firstAction,
Action<T> subsequentActions)
{
using (IEnumerator<T> iterator = source.GetEnumerator())
{
if (iterator.MoveNext())
{
firstAction(iterator.Current);
}
while (iterator.MoveNext())
{
subsequentActions(iterator.Current);
}
}
}
答案 2 :(得分:1)
查看Jon Skeet的smart enumerations。
的一部分答案 3 :(得分:0)
using System.Linq; // reference to System.Core.dll
List<string> list = ..
list.Skip(1).ForEach(DoWork) // if you use List<T>.ForEeach()
但我建议你写一个:
public static void ForEach(this IEnumerable<T> collection, Action<T> action)
{
foreach(T item in collection)
action(item);
}
所以你可以做下一步:
list.Skip(1).ForEach(DoWork)
答案 4 :(得分:0)
很难说以不同方式处理第一个元素的“最佳”方式是不知道为什么需要以不同方式处理它。
如果你将序列的元素输入到框架的ForEach方法中,你就不能优雅地为Action委托提供它所需的信息来确定元素参数在源序列中的位置,所以我想一个额外的步骤有必要的。如果在循环之后不需要对序列执行任何操作,则可以始终使用队列(或堆栈),将第一个元素传递给您通过Dequeue()(或Pop()使用的任何处理程序)方法调用,然后你有剩余的“同质”序列。
答案 5 :(得分:0)
对于所有可用的Linq材料来说,它看起来似乎很简陋,但总是存在旧时尚的循环。
var yourList = new List<int>{1,1,2,3,5,8,13,21};
for(int i = 0; i < yourList.Count; i++)
{
if(i == 0)
DoFirstElementStuff(yourList[i]);
else
DoNonFirstElementStuff(yourList[i]);
}
如果你不想在循环中改变你的List,那就没问题了。否则,您可能需要显式使用迭代器。那时,你不得不想知道是否真的值得它去除IsFirst标志。
答案 6 :(得分:0)
编辑:添加了用法示例,添加了ForFirst方法,重新排序了我的段落。
以下是完整的解决方案。
用法是以下任一项:
list.ForFirst(DoWorkForFirst).ForRemainder(DoWork);
// or
list.ForNext(1, DoWorkForFirst).ForRemainder(DoWork);
关键是ForNext
方法,它对集合中指定的下一组项执行操作并返回其余项。我还实现了一个ForFirst
方法,简单地使用count:1来调用ForNext。
class Program
{
static void Main(string[] args)
{
List<string> list = new List<string>();
// ...
list.ForNext(1, DoWorkForFirst).ForRemainder(DoWork);
}
static void DoWorkForFirst(string s)
{
// do work for first item
}
static void DoWork(string s)
{
// do work for remaining items
}
}
public static class EnumerableExtensions
{
public static IEnumerable<T> ForFirst<T>(this IEnumerable<T> enumerable, Action<T> action)
{
return enumerable.ForNext(1, action);
}
public static IEnumerable<T> ForNext<T>(this IEnumerable<T> enumerable, int count, Action<T> action)
{
if (enumerable == null)
throw new ArgumentNullException("enumerable");
using (var enumerator = enumerable.GetEnumerator())
{
// perform the action for the first <count> items of the collection
while (count > 0)
{
if (!enumerator.MoveNext())
throw new ArgumentOutOfRangeException(string.Format(System.Globalization.CultureInfo.InvariantCulture, "Unexpected end of collection reached. Expected {0} more items in the collection.", count));
action(enumerator.Current);
count--;
}
// return the remainder of the collection via an iterator
while (enumerator.MoveNext())
{
yield return enumerator.Current;
}
}
}
public static void ForRemainder<T>(this IEnumerable<T> enumerable, Action<T> action)
{
if (enumerable == null)
throw new ArgumentNullException("enumerable");
foreach (var item in enumerable)
{
action(item);
}
}
}
我觉得制作ForRemainder
方法有点荒谬;我可以发誓我正在重新实现一个内置函数,但它并没有浮现在脑海中,在看了一下之后我找不到相应的东西。更新:在阅读其他答案之后,我发现Linq中显然没有相应的内容。我现在感觉不太好。
答案 7 :(得分:0)
取决于你如何“以不同方式处理”。如果你需要做一些完全不同的事情,那么我建议处理循环外的第一个元素。如果除了常规元素处理之外还需要执行某些操作,请考虑检查其他处理的结果。在代码中理解起来可能更容易,所以这里有一些:
string randomState = null; // My alma mater!
foreach(var ele in someEnumerable) {
if(randomState == null) randomState = setState(ele);
// handle additional processing here.
}
这样,你的“旗帜”实际上是你(大概)需要的外部变量,所以你不是在创建专用变量。如果您不想像处理其余枚举那样处理第一个元素,也可以将其包装在if/else
中。