I had a need to do a look-ahead and look-behind when enumerating though and IEnumerable
. Here is a classic situation:
foreach(var element in anEnumerable)
{
//I can get at the current element while iterating (obviously)
//But sometimes I need to "lookahead" to see what the next element is going to be
///or "lookbehind" to see what the last element was.
}
So I thought I'd see if I could encapsulate the logic to do something like this pseudocode:
foreach(var element in anEnumerable.AsSomethingThatLetsMeSeeAheadAndBehind())
{
element.Current; //the T of whatever anEnumerable<T> was
element.Lag; //The preceeding T in the enumerable as we enumerate
element.Lead; //The next T in the enumerable as we enumerate
element.Lead(2); //not in this discussion, but it would be neat to look beyond the adjacent element
}
The AsSomethingThatLetsMeSeeAheadAndBehind()
extension method is an attempt to make it simple to generate the wrapper around any enumerable of type T
, so I built the below implementations of an IEnumerable<T>
and IEnumerator<T>
from scratch, but there are some code smells:
- Needing two separate extension methods to deal with Value Types and Reference Types. I just could not get it to work any other way (even
ValueType
s must returnnull
for lead and lag outside the enumerable) - The
List<object> _history
in the Enumerator and theMoveNext()
code just feels wrong
This code is a direct copy from Linqpad where It was built. If you copy it and run it, you will see it works as expected. The .Dump()
in Linqpad is calling a foreach
under the hood.
Suggestions on improvements?
void Main()
{
var l = new List<int> { 1, 2, 3, 4, 5, 6, 7 };
var p = new List<IEnumerable<int>>() {l.Take(1), l.Take(2), l.Take(3)};
l.AsLLEStruct().Dump();
p.AstLLERef().Dump();
}
public static class extns
{
public static LagLeadEnumerable<Nullable<T>> AsLLEStruct<T>(this IEnumerable<T> enumerable) where T : struct
{
return new LagLeadEnumerable<Nullable<T>>(enumerable.Cast<T?>());
}
public static LagLeadEnumerable<T> AstLLERef<T>(this IEnumerable<T> enumerable) where T : class
{
return new LagLeadEnumerable<T>(enumerable);
}
}
public class LagLeadEnumerable<T> : IEnumerable<ILagLeader<T>>
{
IEnumerable<T> _enumer;
public LagLeadEnumerable(IEnumerable<T> enumerable)
{
_enumer = enumerable;
}
public IEnumerator<ILagLeader<T>> GetEnumerator()
{
return new LagLeadEnumerator<T>(_enumer.GetEnumerator());
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class LagLeadEnumerator<T> : IEnumerator<LagLeadEnumerator<T>>, ILagLeader<T>
{
IEnumerator<T> _enumerator;
List<object> _history = new List<object>();
bool _lastMove = false;
bool _atEnd = false;
public LagLeadEnumerator(IEnumerator<T> enumerator)
{
_enumerator = enumerator;
Init();
}
object IEnumerator.Current
{
get
{
return this;
}
}
LagLeadEnumerator<T> IEnumerator<LagLeadEnumerator<T>>.Current
{
get
{
return this;
}
}
public void Dispose()
{
_enumerator.Dispose();
}
public bool MoveNext()
{
_history.Add(_enumerator.Current);
_lastMove = _atEnd;
if (!_lastMove)
{
_atEnd = !_enumerator.MoveNext();
}
return !(_atEnd & _lastMove);
}
public void Reset()
{
throw new NotImplementedException();
}
void Init()
{
_history.Clear();
_enumerator.MoveNext();
_history.Add(null);
_history.Add(null);
}
public T Lead
{
get
{
if (!_atEnd)
{
return _enumerator.Current;
}
return default(T);
}
}
public T Lag
{
get
{
return (T)_history[_history.Count - 2];
}
}
public T Current
{
get
{
return (T)_history[_history.Count - 1];
}
}
T ILagLeader<T>.Lead => throw new NotImplementedException();
T ILagLeader<T>.Lag => throw new NotImplementedException();
}
public interface ILagLeader<T>
{
T Lead { get; }
T Current { get; }
T Lag { get; }
}
ILL
;-| is this any know algorithm? Maybe you could add a link? \$\endgroup\$Lag
,Current
andLead
. So unlike a usual enumerator,Current
doesn't refer to the head of the enumerator but the one before it. I'm still not sure exactly what happens in the edge cases (beginning and end of list)... Would be nice to see the output from thoseDump
s. \$\endgroup\$Dump
s but I'm not feeling like debugging it ;-) Let's hope OP'll provide more info. \$\endgroup\$T?
wouldn't be available. This would also allow you to make the history aList<Maybe<T>>
instead ofList<object>
(whereMaybe
is your own option type, I believe there are some implementations on NuGet). I don't have time right now to provide example code for these, so take these as mere suggestions. \$\endgroup\$