Class String содержит очень полезный метод - String.Join(string, string[]).

Он создает строку из массива, отделяя каждый элемент массива заданным символом. Но в целом - он не добавляет разделитель после последнего элемента! Я использую его для кодирования ASP.NET для разделения с помощью "<br />" или Environment.NewLine.

Поэтому я хочу добавить пустую строку после каждой строки в asp:Table. Какой метод IEnumerable<TableRow> я могу использовать для тех же функций?

9
abatishchev 14 Июн 2009 в 23:19

9 ответов

Лучший ответ

Я написал метод расширения:

    public static IEnumerable<T> 
        Join<T>(this IEnumerable<T> src, Func<T> separatorFactory)
    {
        var srcArr = src.ToArray();
        for (int i = 0; i < srcArr.Length; i++)
        {
            yield return srcArr[i];
            if(i<srcArr.Length-1)
            {
                yield return separatorFactory();
            }
        }
    }

Вы можете использовать его следующим образом:

tableRowList.Join(()=>new TableRow())
8
spender 3 Ноя 2010 в 11:20

Эквивалент String.Join в Linq: Aggregate

Например:

IEnumerable<string> strings;
string joinedString = strings.Aggregate((total,next) => total + ", " + next);

Если указан IE TableRows, код будет аналогичным.

9
Scott Weinstein 18 Апр 2010 в 13:21

В .NET 3.5 вы можете использовать этот метод расширения:

public static string Join<TItem>(this IEnumerable<TItem> enumerable, string separator)
{
   return string.Join(separator, enumerable.Select(x => x.ToString()).ToArray());
}

Или в .NET 4

public static string Join<TItem>(this IEnumerable<TItem> enumerable, string separator)
{
   return string.Join(separator, enumerable);
}

НО вопрос требовал разделителя после каждого элемента, включая последний, для которого это (версия 3.5) будет работать: -

public static string AddDelimiterAfter<TItem>(this IEnumerable<TItem> enumerable, string delimiter)
{
   return string.Join("", enumerable.Select(x => x.ToString() + separator).ToArray());
}

Вы также можете использовать .Aggregate, чтобы сделать это без метода расширения .

8
Ian Mercer 18 Апр 2010 в 06:07

Для этого нет встроенного метода, вы должны использовать свой собственный.

1
driis 14 Июн 2009 в 19:34

Если бы я не мог найти метод, который соответствовал бы моим потребностям, я бы просто создал свой собственный. И методы расширения в этом плане очень хороши, поскольку позволяют расширять подобные вещи. Не знаю много о asp: table, но вот метод расширения, по крайней мере, который вы можете настроить на что угодно: p

public static class TableRowExtensions
{
    public string JoinRows(this IEnumerable<TableRow> rows, string separator)
    {
        // do what you gotta do
    }
}
1
abatishchev 18 Апр 2010 в 07:53

Вам нужна функция Intersperse. Для реализации такой функции в LINQ см. этот вопрос.


Кстати, еще одним возможным аналогом String.Join является функция Intercalate, которую я и искал:

public static IEnumerable<T> Intercalate<T>(this IEnumerable<IEnumerable<T>> source,
                                            IEnumerable<T> separator) {
    if (source == null) throw new ArgumentNullException("source");
    if (separator == null) throw new ArgumentNullException("separator");
    return source.Intersperse(separator)
        .Aggregate(Enumerable.Empty<T>(), Enumerable.Concat);
}
1
Community 23 Май 2017 в 11:53

Я обнаружил, что часто использую этот метод расширения.

Если повезет, он может быть встроен в C #.

public static class Extensions
{
    public static string ToString(this IEnumerable<string> list, string separator)
    {
        string result = string.Join(separator, list);
        return result;
    }
}

Применение:

var people = new[]
{
    new { Firstname = "Bruce", Surname = "Bogtrotter" },
    new { Firstname = "Terry", Surname = "Fields" },
    new { Firstname = "Barry", Surname = "Wordsworth"}
};

var guestList = people
                    .OrderBy(p => p.Firstname)
                    .ThenBy(p => p.Surname)
                    .Select(p => $"{p.Firstname} {p.Surname}")
                    .ToString(Environment.NewLine);
1
Fidel 18 Апр 2019 в 00:57

Если вы собираетесь делать такие вещи часто, то стоит создать свой собственный метод расширения для этого. Приведенная ниже реализация позволяет вам делать эквивалент string.Join(", ", arrayOfStrings), где arrayOfStrings может быть IEnumerable<T>, а разделителем может быть любой объект. Это позволяет делать что-то вроде этого:

var names = new [] { "Fred", "Barney", "Wilma", "Betty" };
var list = names
    .Where(n => n.Contains("e"))
    .Join(", ");

В этом мне нравятся две вещи:

  1. Это очень удобно для чтения в контексте LINQ.
  2. Это довольно эффективно, поскольку использует StringBuilder и позволяет избежать повторной оценки перечисления.
public static string Join<TItem,TSep>(
    this IEnumerable<TItem> enuml,
    TSep                    separator)
{
    if (null == enuml) return string.Empty;

    var sb = new StringBuilder();

    using (var enumr = enuml.GetEnumerator())
    {
        if (null != enumr && enumr.MoveNext())
        {
            sb.Append(enumr.Current);
            while (enumr.MoveNext())
            {
                sb.Append(separator).Append(enumr.Current);
            }
        }
    }

    return sb.ToString();
}
0
Damian Powell 18 Апр 2010 в 13:53

Я обнаружил, что этот пост ищет то же самое, но был очень недоволен ответами (большинство похоже на объединение строк [?], И принятый ответ кажется многословным). В итоге я просто придумал другие способы сделать это.

Я предпочитаю такой способ:

public static IEnumerable<T> AfterEach <T> (this IEnumerable<T> source, T delimiter) =>
    source.SelectMany((item) => Enumerable.Empty<T>().Append(item).Append(delimiter));

Или, альтернативно:

public static IEnumerable<T> AfterEach <T> (this IEnumerable<T> source, T delimiter) =>
    source.SelectMany((item) => new T[] { item, delimiter });

Они оба эквивалентны: для каждого элемента создайте новую последовательность {item, delimiter} и используйте SelectMany, чтобы объединить их все вместе.

Другой подход, который не является однострочным, но может быть более легким для чтения и очень прост:

public static IEnumerable<T> AfterEach <T> (this IEnumerable<T> source, T delimiter) {
    foreach (T item in source) {
        yield return item;
        yield return delimiter;
    }
}

Если вам не нужен дополнительный разделитель в конце, подход аналогичен, за исключением того, что вы объединяете последовательности {delimiter, item} , а затем Skip дополнительный разделитель в начале (это более эффективно, чем пропуск одного в конце).

public static IEnumerable<T> Delimit <T> (this IEnumerable<T> source, T delimiter) =>
    source.SelectMany((item) => Enumerable.Empty<T>().Append(delimiter).Append(item)).Skip(1);

public static IEnumerable<T> Delimit <T> (this IEnumerable<T> source, T delimiter) =>
    source.SelectMany((item) => new T[] { delimiter, item }).Skip(1);

Подход доходности будет таким (опять же, два похожих варианта, просто зависит от того, как вы себя чувствуете):

public static IEnumerable<T> Delimit <T> (this IEnumerable<T> source, T delimiter) {
    foreach (T item in source.Take(1)) // protects agains empty source
        yield return item;
    foreach (T item in source.Skip(1)) {
        yield return delimiter;
        yield return item;
    }
}

public static IEnumerable<T> Delimit <T> (this IEnumerable<T> source, T delimiter) {
    static IEnumerable<U> Helper<U> (IEnumerable<U> source, U delimiter) {
        foreach (U item in source) {
            yield return delimiter;
            yield return item;
        }
    }
    return Helper(source, delimiter).Skip(1);
}

Здесь есть несколько исполняемых примеров и тестовый код (для Delimit, а не AfterEach) . Там тоже есть реализация с Aggregate, о которой я не думал, что стоит здесь писать. Аналогичным образом он может быть адаптирован к AfterEach.

0
Jason C 11 Июн 2021 в 21:04