Using Enumerable.Aggregate(...) Method over an empty sequence

13,958

Solution 1

To concatenate a list of strings, use the string.Join method.

The Aggregate function doesn't work with empty collections. It requires a binary accumulate function and it needs an item in the collection to pass to the binary function as a seed value.

However, there is an overload of Aggregate:

public static TResult Aggregate<TSource, TAccumulate, TResult>(
    this IEnumerable<TSource> source,
    TAccumulate seed,
    Func<TAccumulate, TSource, TAccumulate> func,
    Func<TAccumulate, TResult> resultSelector
)

This overload allows you to specify a seed value. If a seed value is specified, it will also be used as the result if the collection is empty.

EDIT: If you'd really want to use Aggregate, you can do it this way:

sequence.Aggregate(string.Empty, (x, y) => x == string.Empty ? y : x + Separator + y)

Or this way by using StringBuilder:

sequence.Aggregate(new StringBuilder(), (sb, x) => (sb.Length == 0 ? sb : sb.Append(Separator)).Append(x)).ToString()

Solution 2

I think you might find the following helper extension method useful.

public static TOut Pipe<TIn, TOut>(this TIn _this, Func<TIn, TOut> func)
{
    return func(_this);
}

It allows you to express your query in the following way.

txtDiscNumber.Text = album.OrderedTracks
    .Where(a => a.DiscNumber.HasValue)
    .Select(a => a.DiscNumber.Value.ToString())
    .Distinct()
    .Pipe(items => string.Join(LISTSEPARATOR, items));

This still reads "top to bottom," which greatly aids readability.

Solution 3

Use String.Join like this:

 txtDiscNumber.Text = String.Join(LISTSEPARATOR,
      album.OrderedTracks
                  .Where(a => a.DiscNumber.HasValue)
                  .Select(a => a.DiscNumber.Value.ToString())
                  .Distinct());

Solution 4

You can use

.Aggregate(string.Empty, (i, j) => i + LISTSEPARATOR + j);

with the initial value it works for empty collections

Share:
13,958
lorcan
Author by

lorcan

Updated on June 16, 2022

Comments

  • lorcan
    lorcan almost 2 years

    I would like to use the Enumerable.Aggregate(...) method to concatenate a list of strings separated by a semicolon. Rather easy, isn't it?

    Considering the following:

    • private const string LISTSEPARATOR = "; ";
    • album.OrderedTracks is List<TrackDetails>
    • TrackDetails has DiscNumber Int16? property

    The following statement will trow an exception if the sequence returned by Distinct() is empty (as the Aggregate() method doesn't apply on empty sequence):

        txtDiscNumber.Text = album.OrderedTracks
            .Where(a => a.DiscNumber.HasValue)
            .Select(a => a.DiscNumber.Value.ToString())
            .Distinct()
            .Aggregate((i, j) => i + LISTSEPARATOR + j);
    

    The workaround I am using:

        List<string> DiscNumbers = 
            album.OrderedTracks
                .Where(a => a.DiscNumber.HasValue)
                .Select(a => a.DiscNumber.Value.ToString())
                .Distinct()
                .ToList();
    
        if (!DiscNumbers.Any())
            txtDiscNumber.Text = null;
        else
            txtDiscNumber.Text = 
                DiscNumbers.Aggregate((i, j) => i + LISTSEPARATOR + j);
    

    Is there any better solution? Is it possible to do this in a single LINQ statement?

    Thanks in advance.

  • Cosmin
    Cosmin over 7 years
    Please note that if you use the seed value, it will appear at the begining of the result string: ;item1;item2
  • Abhishek
    Abhishek about 5 years
    This is the actual answer for how to use Aggregate for this. Yes Join is better in this very specific situation, but not really the question