Question

is there a way in .NET (or some sort of standard extension methods) to ask questions of an enumeration?

For example is the current item the first or last item in the enumeration:

string s = "";

foreach (var person in PeopleListEnumerator) {

  if (PeopleListEnumerator.IsFirstItem) s += "[";

  s += person.ToString();

  if (!PeopleListEnumerator.IsLastItem) s += ",";
  else s += "]";
}
Was it helpful?

Solution

Just for the sake of fun, a solution to the general problem that doesn't require eager evaluation and has a single local variable (except the enumerator):

static class TaggedEnumerableExtensions
{
    public class TaggedItem<T>
    {
        public TaggedItem(T value, bool isFirst, bool isLast)
        {
            IsFirst = isFirst;
            IsLast = isLast;
            Value = value;
        }
        public T Value { get; private set; }
        public bool IsFirst { get; private set; }
        public bool IsLast { get; private set; }
    }
    public static IEnumerable<TaggedItem<T>> ToTaggedEnumerable<T>(this IEnumerable<T> e)
    {
        using (var enumerator = e.GetEnumerator()) {
            if (!enumerator.MoveNext())
                yield break;
            var current = enumerator.Current;
            if (!enumerator.MoveNext()) {
                yield return new TaggedItem<T>(current, true, true);
                yield break;
            } else {
                yield return new TaggedItem<T>(current, true, false);
            }

            for (;;) {
                current = enumerator.Current;
                if (!enumerator.MoveNext()) {
                    yield return new TaggedItem<T>(current, false, true);
                    yield break;
                }
                yield return new TaggedItem<T>(current, false, false);
            }
        }
    }
}

Test:

class Program
{
    static void Main(string[] args)
    {
        foreach (var item in Enumerable.Range(0, 10).ToTaggedEnumerable()) {
            Console.WriteLine("{0} {1} {2}", item.IsFirst, item.IsLast, item.Value);
        }
    }
}

OTHER TIPS

If your collection is a List, you can do:

string s = "[" + String.Join(",", PeopleList.ToArray()) + "]";

Not that I know of. You can try writing such extension methods yourself, though. Or keep track of the first/last item elsewhere.

Eric Lippert had a blog post about that kind of problem recently along with some thoughts about how to modify your problem description to more accurately reflect what you actually want.

You're probably better off using a String.Join with some LINQ in this case:

String.Join(
  ",",
  (from p in PeopleListEnumerator
  select p.ToString()).ToArray()
)

The IEnumerable interface does not define or expect the items that it returns to be in any given order. So the concept of "First" doesn't always apply.

However, for this particular pattern this is what I do

StringBuilder result = new StringBuilder();
result.Append( '[' );

foreach( var person in PeopleListEnumerator )
{
    if( result.Length > 1 )
        result.Append( ',' );
    result.Append( person.ToString() );
}

result.Append( ']' );

Jon Skeet wrote Smart Enumerations to provide this sort of functionality and they are part of the MiscUtils library.

That said your specific example is best solved by the String.Join() approach as many others have pointed out. Writing a general string Join(this IEnumerable<T>,string) extension is not hard and is then usable in any more situations without having to resort to annoying temporary arrays.

Using LINQ, you can do:

string s = 
    string.Format("[{0}]", string.Join(",",PeopleListEnumerator.Select(p => p.ToString()).ToArray()));

I find myself defining and using a "bool first" local variable, for example:

string s = "[";
bool first = true;

foreach (var person in PeopleListEnumerator) {

  if (first)
    first = false;
  else
    s += ",";
  s += person.ToString();
}
s += "]";

This code closely matches what you really want to accomplish:

StringBuilder s = new StringBuilder("[");
string delimiter = "";

foreach (var person in PeopleListEnumerator) {  
  s.Append(delimiter).Append(person.ToString());
  delimiter = ",";
}

return s.Append("]").ToString();

The way I'd skin this cat (yes, there are that many ways) would be:

StringBuilder sb = new StringBuilder();

foreach (var person in PeopleListEnumerator )
{
    sb.AppendFormat(",{0}", person.ToString());
}

string s = "[" + sb.ToString().Substring(1) + "]";

Hope this helps...

In .NET enumerations have no concept of order, except by value. If you want a first and last enumeration I would suggest explicitly defining what these first and last values are. The following enumeration is an example of this:

public enum PlayerType : byte
{
    Starter = byte.MinValue,
    RegularPlayer = 1,
    BenchWarmer = 2,
    Closer = byte.MaxValue
}

You can then express something similar to the following:

List<byte> values = new List<byte>((byte[])Enum.GetValues(typeof(PlayerType)));

PlayerType starter = (PlayerType)values.Min();
PlayerType closer = (PlayerType)values.Max();
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top