给定一个Enumerable,我想Take()所有元素,包括一个终结符(如果找不到终结符则抛出异常)。类似的东西:
list.TakeWhile(v => !condition(v)).Concat(list.Single(condition))
..除了不蹩脚。只想走一次。
对于.NET 4和Rx中的当前运算符,这是否可以简化,或者我是否需要编写新的运算符?
编写运算符会比写这个问题花费更少的时间(尽管我认为有一半的时间会弄清楚这个函数的名称),但我只是不想复制已存在的东西。
更新
好的,这是运营商。非常令人兴奋,我知道。无论如何,可以从内置运算符构建它吗?
public static IEnumerable<T> TakeThroughTerminator<T>([NotNull] this IEnumerable<T> @this, Func<T, bool> isTerminatorTester)
{
foreach (var item in @this)
{
yield return item;
if (isTerminatorTester(item))
{
yield break;
}
}
throw new InvalidOperationException("Terminator not found in list");
}
答案 0 :(得分:2)
如果您不想编写自己的运算符,这里有一些硬核:
var input = Enumerable.Range(1, 10);
var condition = new Func<int, bool>(i => i < 5);
bool terminatorPassed = false;
var condition2 = new Func<int, bool>(i =>
{
try { return !terminatorPassed; }
finally { terminatorPassed = !condition(i); }
});
var result = input.TakeWhile(condition2).ToArray();
if (!terminatorPassed) throw new FutureException("John Connor survived");
答案 1 :(得分:2)
没有内置功能可以有效地进行这样的操作。人们通常不需要获得满足条件的物品,而不需要更多的物品。你必须自己写。
但是你可以使用现有方法构建它,它只是效率不高,因为你需要保持状态某种程度只会使你的代码复杂化。我不会宽恕这种查询,因为它违背了LINQ的哲学并且会自己编写。但既然你问:
var list = Enumerable.Range(0, 10);
Func<int, bool> condition = i => i != 5;
int needed = 1;
var query = list.Where(item => condition(item)
? needed > 0
: needed-- > 0)
.ToList(); // this might cause problems
if (needed != 0)
throw new InvalidOperationException("Sequence is not properly terminated");
然而,这有其自身的问题,无法很好地解决。处理这个的正确方法是手动编写这个(没有LINQ)。这将给你完全相同的行为。
public static IEnumerable<TSource> TakeWhileSingleTerminated<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
var hasTerminator = false;
var terminator = default(TSource);
foreach (var item in source)
{
if (!hasFailed)
{
if (predicate(item))
yield return item;
else
{
hasTerminator = true;
terminator = item;
}
}
else if (!predicate(item))
throw new InvalidOperationException("Sequence contains more than one terminator");
}
if (!hasTerminator)
throw new InvalidOperationException("Sequence is not terminated");
yield return terminator;
}
经过深思熟虑之后,我会说很难获得最有效的原始查询实现,因为它有相互冲突的要求。您将TakeWhile()
与Single()
提前终止,而{{1}}则不能。有可能复制最终结果(正如我们在这里所尝试的那样),但行为不能在不对代码进行重大更改的情况下进行。如果目标只是采取第一个失败的项目,那么这将是完全可能和可复制的,但是因为它不是,所以你只需要处理这个查询所遇到的问题。
p.s。,我认为很明显,仅通过我对此答案进行了多少次编辑就可以做到这一点非常重要。希望这是我的最后一次编辑。
答案 2 :(得分:0)
int? j = null;
var result = list.TakeWhile((o, i) =>
{
if (j == null && cond(o)) { j = i + 1; }
return (j ?? -1) != i;
});
if (j == null) { throw new InvalidOperationException(); }
我会选择运营商,但是如何才能确定真的没有内置方式? ; - )
UPDATE1:好的,我的代码没用。我敢打赌它会抛出异常,如果在检查异常之前没有发生Enumerable的执行......