Slice - Collection Slicing Extension Method

I really like Python collection slicing. It is easy to use, flexible and forgiving. Using C# 3.0 extension methods I have recreate collection slicing in .NET.

List<int> initial = new List<int> { 1, 2, 3, 4, 5 };
 
var sliced = initial.Slice(1, 4);
// 2, 3, 4
 
sliced = initial.Slice(0, 5, 2);
// 1, 3, 5
 
sliced = initial.Slice(null, null, -2);
// 5, 3, 1
 
sliced = initial.Slice(null, -1);
// 1, 2, 3, 4
 
sliced = initial.Slice(-2, -1);
// 4

Just specify the start index, stop index and a step and the method will create a new collection with all the valid values from the initial collection. Slice will never throw an IndexOutOfRange exception. If there are no values within the specified indexes then an empty collection is returned.

Start, stop and step can all be negative. A negative start or stop will begin from the end of the collection (e.g. -1 becomes source.Count - 1) and a negative step will reverse the new collection.

public static IEnumerable<T> Slice<T>(this IEnumerable<T> source, int? start)
{
  return Slice<T>(source, start, null, null);
}
 
public static IEnumerable<T> Slice<T>(this IEnumerable<T> source, int? start, int? stop)
{
  return Slice<T>(source, start, stop, null);
}
 
public static IEnumerable<T> Slice<T>(this IEnumerable<T> source, int? start, int? stop, int? step)
{
  if (source == null) throw new ArgumentNullException("source");
 
  if (step == 0) throw new ArgumentException("Step cannot be zero.", "step");
 
  IList<T> sourceCollection = source as IList<T>;
  if (sourceCollection == null) source = new List<T>(source);
 
  // nothing to slice
  if (sourceCollection.Count == 0) yield break;
 
  // set defaults for null arguments
  int stepCount = step ?? 1;
  int startIndex = start ?? ((stepCount > 0) ? 0 : sourceCollection.Count - 1);
  int stopIndex = stop ?? ((stepCount > 0) ? sourceCollection.Count : -1);
 
  // start from the end of the list if start is negitive
  if (start < 0) startIndex = sourceCollection.Count + startIndex;
 
  // end from the start of the list if stop is negitive
  if (stop < 0) stopIndex = sourceCollection.Count + stopIndex;
 
  // ensure indexes keep within collection bounds
  startIndex = Math.Max(startIndex, (stepCount > 0) ? 0 : int.MinValue);
  startIndex = Math.Min(startIndex, (stepCount > 0) ? sourceCollection.Count : sourceCollection.Count - 1);
  stopIndex = Math.Max(stopIndex, -1);
  stopIndex = Math.Min(stopIndex, sourceCollection.Count);
 
  for (int i = startIndex; (stepCount > 0) ? i < stopIndex : i > stopIndex; i += stepCount)
  {
    yield return sourceCollection[i];
  }
 
  yield break;
}

kick it on DotNetKicks.com