首页 > 解决方案 > 如何评估 IEnumerable同时在 C# 中对其第一个和/或最后一个元素进行特殊处理?

问题描述

这是一个更一般的问题形式,我怎样才能对列表的第一个和最后一个元素做一些特别的事情?更具体的问题很容易回答。我们知道第一个和最后一个元素的索引,因此我们可以直接访问它们或根据这些值测试索引变量。例如:

for (int i = 0; i < values.Count; ++i)
{
  if (i == values.Count - 1)
  {
    // do something with last element
  }
  else
  {
    // do something else
  }
}

但有时我需要用IEnumerable<T>. 例如:

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);
}

由于这是一种常见模式,我想以一般方式解决它(而不是像过去那样为每种情况编写自定义 for 循环)。一种选择是使用 将序列转换为列表ToList()或使用 计算元素Count()。这两种情况的问题是解决方案涉及评估整个序列,这可能非常昂贵。

所以问题是,我如何评估一个IEnumerable<T>序列,同时对其第一个和/或最后一个元素进行特殊处理,同时以一般方式保持对该序列的惰性评估?

标签: c#linq

解决方案


解决这个问题的一种方法是创建一个新的扩展方法,用于IEnumerable<T>返回源序列的元素以及关于它们位置的语义信息。如果源序列的元素具有类型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);
    }
}

然后我们可以传递位置信息以对序列中的第一个和/或最后一个项目进行特殊处理。例如:

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));
}

推荐阅读