Использование метода Enumerable.Aggregate(...) над пустой последовательностью
Я хотел бы использовать метод Enumerable.Aggregate(...), чтобы объединить список строк, разделенных точкой с запятой. Скорее легко, не так ли?
Учитывая следующее:
-
private const string LISTSEPARATOR = "; ";
- . OrderedTracks -
List<TrackDetails>
- В TrackDetails есть DiscNumber Int16? Свойство
Следующий оператор вызовет исключение, если последовательность, возвращаемая Distinct(), пуста (поскольку метод Aggregate() не применяется в пустой последовательности):
txtDiscNumber.Text = album.OrderedTracks
.Where(a => a.DiscNumber.HasValue)
.Select(a => a.DiscNumber.Value.ToString())
.Distinct()
.Aggregate((i, j) => i + LISTSEPARATOR + j);
Обходной путь, который я использую:
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);
Есть ли лучшее решение? Возможно ли это сделать в одном заявлении LINQ?
Спасибо заранее.
Ответы
Ответ 1
Чтобы объединить список строк, используйте метод string.Join
.
Функция Aggregate
не работает с пустыми коллекциями. Для этого требуется двоичная функция накопления, и в коллекции нужно, чтобы элемент в коллекции передавался двоичной функции в качестве начального значения.
Однако существует перегрузка Aggregate
:
public static TResult Aggregate<TSource, TAccumulate, TResult>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func,
Func<TAccumulate, TResult> resultSelector
)
Эта перегрузка позволяет указать начальное значение. Если задано начальное значение, оно также будет использоваться в качестве результата, если коллекция пуста.
EDIT: Если вы действительно хотите использовать Aggregate
, вы можете сделать это следующим образом:
sequence.Aggregate(string.Empty, (x, y) => x == string.Empty ? y : x + Separator + y)
Или с помощью StringBuilder
:
sequence.Aggregate(new StringBuilder(), (sb, x) => (sb.Length == 0 ? sb : sb.Append(Separator)).Append(x)).ToString()
Ответ 2
Я думаю, вы могли бы найти следующий вспомогательный метод расширения.
public static TOut Pipe<TIn, TOut>(this TIn _this, Func<TIn, TOut> func)
{
return func(_this);
}
Он позволяет вам выразить свой запрос следующим образом.
txtDiscNumber.Text = album.OrderedTracks
.Where(a => a.DiscNumber.HasValue)
.Select(a => a.DiscNumber.Value.ToString())
.Distinct()
.Pipe(items => string.Join(LISTSEPARATOR, items));
Это все еще читается "сверху вниз", что значительно облегчает читаемость.
Ответ 3
Используйте String.Join
следующим образом:
txtDiscNumber.Text = String.Join(LISTSEPARATOR,
album.OrderedTracks
.Where(a => a.DiscNumber.HasValue)
.Select(a => a.DiscNumber.Value.ToString())
.Distinct());
Ответ 4
Использованные методы, подобные этому, для целей отладки,
придумали два метода расширения:
public static string Concatenate<T, U>(this IEnumerable<T> source, Func<T, U> selector, string separator = ", ")
{
if (source == null)
{
return string.Empty;
}
return source
.Select(selector)
.Concatenate(separator);
}
public static string Concatenate<T>(this IEnumerable<T> source, string separator = ", ")
{
if (source == null)
{
return string.Empty;
}
StringBuilder sb = new StringBuilder();
bool firstPass = true;
foreach (string item in source.Distinct().Select(x => x.ToString()))
{
if (firstPass)
{
firstPass = false;
}
else
{
sb.Append(separator);
}
sb.Append(item);
}
return sb.ToString();
}
Используйте это:
string myLine = myCol.Concatenate(x => x.TheProperty);