Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 305
lorcan Avatar asked Feb 15 '13 14:02

lorcan


4 Answers

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()
like image 172
Tom Pažourek Avatar answered Nov 19 '22 11:11

Tom Pažourek


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.

like image 39
Timothy Shields Avatar answered Nov 19 '22 11:11

Timothy Shields


Use String.Join like this:

 txtDiscNumber.Text = String.Join(LISTSEPARATOR,
      album.OrderedTracks
                  .Where(a => a.DiscNumber.HasValue)
                  .Select(a => a.DiscNumber.Value.ToString())
                  .Distinct());
like image 6
Cristian Lupascu Avatar answered Nov 19 '22 11:11

Cristian Lupascu


You can use

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

with the initial value it works for empty collections

like image 6
Karpik Avatar answered Nov 19 '22 13:11

Karpik