Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/linq/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何评估IEnumerable<;T>;对C#中的第一个和/或最后一个元素进行特殊处理时?_C#_Linq - Fatal编程技术网

如何评估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) {...}