如何评估IEnumerable<;T>;对C#中的第一个和/或最后一个元素进行特殊处理时?
这是一个更一般的问题形式,如何对列表的第一个和最后一个元素进行特殊处理?更具体的问题很容易回答。我们知道第一个和最后一个元素的索引,所以我们可以直接访问它们,或者根据这些值测试索引变量。例如:如何评估IEnumerable<;T>;对C#中的第一个和/或最后一个元素进行特殊处理时?,c#,linq,C#,Linq,这是一个更一般的问题形式,如何对列表的第一个和最后一个元素进行特殊处理?更具体的问题很容易回答。我们知道第一个和最后一个元素的索引,所以我们可以直接访问它们,或者根据这些值测试索引变量。例如: for (int i = 0; i < values.Count; ++i) { if (i == values.Count - 1) { // do something with last element } else { // do something els
for (int i = 0; i < values.Count; ++i)
{
if (i == values.Count - 1)
{
// do something with last element
}
else
{
// do something else
}
}
public static Bar TransformFoo(Foo value)
{
if (isLast /* how do we know this? */)
{
// do something with the last element
}
else
{
// do something else
}
}
public static IEnumerable<Bar> TransformFooSequence(IEnumerable<Foo> source)
{
return source.Select(TransformFoo);
}
Bar TransformFoo(Foo value, bool isLast)
{
if (isLast)
{
// do something with the last element
}
else
{
// do something else
}
}
IEnumerable<Bar> TransformFooSequence(IEnumerable<Foo> source)
{
return source
.WithPositions()
.Select(entry => TransformFoo(
entry.value,
(entry.flags & PositionFlags.Last) == PositionFlags.Last));
}
for(int i=0;i
但有时我需要用IEnumerable
来做一个变体。例如:
for (int i = 0; i < values.Count; ++i)
{
if (i == values.Count - 1)
{
// do something with last element
}
else
{
// do something else
}
}
public static Bar TransformFoo(Foo value)
{
if (isLast /* how do we know this? */)
{
// do something with the last element
}
else
{
// do something else
}
}
public static IEnumerable<Bar> TransformFooSequence(IEnumerable<Foo> source)
{
return source.Select(TransformFoo);
}
Bar TransformFoo(Foo value, bool isLast)
{
if (isLast)
{
// do something with the last element
}
else
{
// do something else
}
}
IEnumerable<Bar> TransformFooSequence(IEnumerable<Foo> source)
{
return source
.WithPositions()
.Select(entry => TransformFoo(
entry.value,
(entry.flags & PositionFlags.Last) == PositionFlags.Last));
}
公共静态条转换Foo(Foo值)
{
如果(isLast/*我们如何知道这一点?*/)
{
//用最后一个元素做点什么
}
其他的
{
//做点别的
}
}
公共静态IEnumerable转换序列(IEnumerable源)
{
返回来源。选择(TransformFoo);
}
因为这是一种常见的模式,所以我想用一种通用的方法来解决它(而不是像我过去那样为每种情况编写一个自定义for循环)。一个选项是使用ToList()
将序列转换为列表,或者使用count()
对元素进行计数。这两种情况下的问题是,解决方案涉及评估整个序列,这可能会非常昂贵
所以问题是,,如何对
IEnumerable
序列进行求值,同时对其第一个和/或最后一个元素进行特殊处理,同时保持对该序列的惰性求值?解决此问题的一种方法是为IEnumerable
创建一个新的扩展方法,该方法返回源序列的元素以及关于其位置的语义信息。如果源序列的元素具有类型T
,那么扩展方法将返回类型(T,PositionFlags)
的元组。代码如下:
[Flags]
enum PositionFlags
{
None = 0,
First = 1,
Last = 2
}
public static IEnumerable<(T value, PositionFlags flags)> WithPositions<T>(
this IEnumerable<T> source)
{
using (var enumerator = source.GetEnumerator())
{
if (!enumerator.MoveNext())
{
yield break;
}
T value = enumerator.Current;
PositionFlags flags = PositionFlags.First;
while (enumerator.MoveNext())
{
yield return (value, flags);
value = enumerator.Current;
flags = PositionFlags.None;
}
flags |= PositionFlags.Last;
yield return (value, flags);
}
}
[标志]
枚举位置标志
{
无=0,
第一个=1,
最后一个=2
}
公共静态IEnumerable with positions(
这是(不可数的来源)
{
使用(var enumerator=source.GetEnumerator())
{
如果(!enumerator.MoveNext())
{
屈服断裂;
}
T值=枚举数。当前值;
PositionFlags flags=PositionFlags.First;
while(枚举数.MoveNext())
{
收益率回报(值、标志);
值=枚举数。当前值;
标志=位置标志。无;
}
标志|=位置标志。最后;
收益率回报(值、标志);
}
}
然后,我们可以传递位置信息,对序列中的第一个和/或最后一个项目进行特殊处理。例如:
for (int i = 0; i < values.Count; ++i)
{
if (i == values.Count - 1)
{
// do something with last element
}
else
{
// do something else
}
}
public static Bar TransformFoo(Foo value)
{
if (isLast /* how do we know this? */)
{
// do something with the last element
}
else
{
// do something else
}
}
public static IEnumerable<Bar> TransformFooSequence(IEnumerable<Foo> source)
{
return source.Select(TransformFoo);
}
Bar TransformFoo(Foo value, bool isLast)
{
if (isLast)
{
// do something with the last element
}
else
{
// do something else
}
}
IEnumerable<Bar> TransformFooSequence(IEnumerable<Foo> source)
{
return source
.WithPositions()
.Select(entry => TransformFoo(
entry.value,
(entry.flags & PositionFlags.Last) == PositionFlags.Last));
}
Bar TransformFoo(Foo值,bool isLast)
{
如果(isLast)
{
//用最后一个元素做点什么
}
其他的
{
//做点别的
}
}
IEnumerable转换序列(IEnumerable源)
{
返回源
.职位
.选择(条目=>TransformFoo(
输入值,
(entry.flags和PositionFlags.Last)=PositionFlags.Last);
}
有一个可枚举的。Select方法将元素的索引与元素本身一起传递到选择器Func
。您可以将当前索引与总项(或最后一个索引值)一起传递到函数中,以便函数具有处理特殊项所需的信息
var source = {some IEnumerable<Foo>};
var count = source.Count();
source.Select( ( item, i ) => TransformFoo( item, i, count ) );
public static Bar TransformFoo( Foo item, int index, int totalItems )
{
if( 0 == index )
{
// first item handling
}
else if( ( index + 1 ) == totalItems )
{
// last item handling
}
else
{
// default item handling
}
}
IEnumerable
可以是无序集。如果设计得当,您的函数应该需要一个IList
,以指示输入将被视为有序集
如果出于某种原因必须将参数公开为IEnumerable
,在99%的情况下,您只需将其转换为列表
进行处理即可。您的团队应该专注于核心业务,而不是为像这样的小细节编写最花哨的代码
如果您遇到了一种极为罕见的情况,必须使用定义为IEnumerable
的输入参数,并且转换为列表的成本非常高,那么您可以直接使用枚举器获取元素并以任何方式处理它们
此方法将迭代泛型列表,并根据元素的位置对其调用first()
、middle()
、和last()
public static bool DoSomething<T>(IEnumerable<T> source, Action<T> first, Action<T> middle, Action<T> last)
{
T current = default(T);
var enumerator = source.GetEnumerator();
bool ok = enumerator.MoveNext();
if (!ok) return false; //There were no elements
var firstElement = enumerator.Current;
ok = enumerator.MoveNext();
if (!ok) return false; //There was only 1 element
first(firstElement);
while (ok)
{
current = enumerator.Current;
ok = enumerator.MoveNext();
if (ok) middle(current);
}
last(current);
return true;
}
此方法处理任何类型,并允许您为第一个、中间和最后一个元素传递委托。如果没有足够的元素(您需要三个或更多元素),您没有说明如何处理该案例。在本例中,如果集合没有足够的元素以这种方式处理,则不会处理任何元素,并且方法返回false
。否则返回true
在我的工作示例中,您可以看到它与几个测试用例一起运行
我可能会将这种逻辑封装在一个类中。例如,如果您正在编写一段代码来处理具有页眉、详细信息和页脚部分的大型平面文件,那么您可能会编写这样的基类,并从中继承文件处理器:
internal abstract class ReportBase
{
protected readonly IEnumerable<string> _file;
public ReportBase(IEnumerable<string> file)
{
_file = file;
}
public bool Process()
{
return ProcessInternal(_file, ProcessHeader, ProcessDetail, ProcessFooter);
}
protected bool ProcessInternal<T>(IEnumerable<T> source, Action<T> first, Action<T> middle, Action<T> last)
{
T current = default(T);
var enumerator = source.GetEnumerator();
bool ok = enumerator.MoveNext();
if (!ok) return false; //There were no elements
var firstElement = enumerator.Current;
ok = enumerator.MoveNext();
if (!ok) return false; //There was only 1 element
first(firstElement);
while (ok)
{
current = enumerator.Current;
ok = enumerator.MoveNext();
if (ok) middle(current);
}
last(current);
return true; //At l
}
abstract protected void ProcessHeader(string header);
abstract protected void ProcessDetail(string header);
abstract protected void ProcessFooter(string header);
}
内部抽象类ReportBase
{
受保护的只读IEnumerable\u文件;
公共报告库(IEnumerable文件)
{
_文件=文件;
}
公共布尔过程()
{
返回ProcessInternal(_文件、ProcessHeader、ProcessDetail、ProcessFooter);
}
受保护的bool ProcessInternal(IEnumerable源、操作优先、操作中间、操作最后)
{
T电流=默认值(T);
var枚举器=source.GetEnumerator();
bool ok=enumerator.MoveNext();
如果(!ok)返回false;//没有元素
var firstElement=枚举数.Current;
ok=枚举数。MoveNext();
如果(!ok)返回false;//只有1个元素
第一(第一要素);
while(ok)
{
当前=
public static IEnumerable<TResult> Transform<TSource, Tresult>(
this IEnumerable<TSource> source,
Func<TSource, TResult> transformFuncNonLastElement,
Func<TSource, TResult> transformFuncLansElement)
{
// for every element: check if it is the last one,
// if not, yield return transformFuncNonLastElement
// if last: yield return transformFuncLastElement
IEnumerator<TSource> enumerator = source.GetEnumerator();
if (enumerator.MoveNext())
{ // There is at least one element.
TSource current = enumerator.Current;
// while current is not the last one: transformFuncNonLastElement
while (enumerator.MoveNext())
{
// current is not the last one
TResult transformedNonLastValue = transformFuncNonLastElement(current);
yield return transformedNonLastValue;
current = enumerator.Current;
}
// if here: there are no more elements. current is the last one
TResult transformedLastValue = transformFuncLastElement(current);
yield return transformedLastValue;
}
// else: input sequence empty: return empty
}
IEnumerable<Foo> myFoos = ...
IEnumerable<Bar> result = myFoors.Transform(
foo => ToNonLastBar(foo),
foo => ToLastBar(foo));
Bar ToNonLastBar(Foo foo) {...}
Bar ToLastBar(Foo foo) {...}