Аналог String.Join (string, string []) для IEnumerable ‹T›

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

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

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


person abatishchev    schedule 14.06.2009    source источник
comment
Просто новый TableRow для вставленных значений?   -  person spender    schedule 14.06.2009
comment
Как IEnumerable<TableRow> работает с методом соединения, который принимает строки, другими словами, как вы на самом деле вызываете существующий метод? Я не совсем понимаю, что вы здесь ищете.   -  person Lasse V. Karlsen    schedule 18.04.2010
comment
В настоящее время я вообще не звоню String.Join. Просто ищу ту же функциональность - вставка разделителя между элементами массива   -  person abatishchev    schedule 18.04.2010


Ответы (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())
person spender    schedule 14.06.2009
comment
Если я не правильно понял вопрос, приведенный выше код не имеет смысла. Он возвращает разделитель вместо последнего элемента ?? Какие? - person Michael Kropat; 02.11.2010
comment
Кажется, что MR. @abatischev изменил мой код. Хм. Проверьте ред. 1. Grrr. - person spender; 03.11.2010
comment
Я просто немного переделал отступы, насколько я помню =) извините, если потревожили, откатывайтесь без сомнений, если считаете, что это нужно - person abatishchev; 03.11.2010
comment
Да, я откатился, потому что ваши правки изменились. - person spender; 03.11.2010

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

Например:

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

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

person Scott Weinstein    schedule 18.04.2010
comment
Предупреждение: функционально это может быть эквивалентно String.Join, но реализация отличается. String.Join достаточно умен, чтобы использовать StringBuilder, чтобы не выделять целую кучу временных String экземпляров, тогда как в приведенном выше коде этого не происходит. - person Kent Boogaart; 15.03.2013
comment
@KentBoogaart Это не особенно важно, поскольку исходный вопрос был об общем способе репликации поведения, string.Join был просто примером функции, которая имеет это «перемежающееся» поведение. - person Pharap; 22.02.2017

В .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 для этого без метода расширения.

person Ian Mercer    schedule 18.04.2010

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

person driis    schedule 14.06.2009
comment
Какой метод IEnumerable ‹TableRow› ...? Это правильный ответ, хотя и не очень полезный. Конечно, не стоит отрицать. - person spender; 14.06.2009

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

public static class TableRowExtensions
{
    public string JoinRows(this IEnumerable<TableRow> rows, string separator)
    {
        // do what you gotta do
    }
}
person Svish    schedule 14.06.2009

Вы ищете 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);
}
person Michael Kropat    schedule 02.11.2010

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

Если повезет, он может быть встроен в 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);
person Fidel    schedule 17.04.2019
comment
Отправьте ссылку на проблему, как только переместите ее. - person abatishchev; 17.04.2019

Если вы собираетесь делать подобные вещи часто, то для этого стоит создать свой собственный метод расширения. Реализация ниже позволяет вам делать эквивалент 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();
}
person Damian Powell    schedule 17.04.2010
comment
Но он оценивает коллекцию дважды. Если это запрос к базе данных, то он вообще неэффективен. Кроме того, было бы более естественным потерять здесь общие типы и заставить его работать только со строками. - person Lasse V. Karlsen; 18.04.2010
comment
@Lasse Да, это было справедливо. Я предполагал только в памяти. Теперь переработан так, что перечисление оценивается только один раз. - person Damian Powell; 18.04.2010
comment
Почему бы вам не использовать инструкцию foreach? Прямо сейчас у вас есть возможная утечка ресурсов. По крайней мере, вы должны использовать оператор using с .GetEnumerator(). - person Matthew Whited; 18.04.2010
comment
@Matthew Да, замечательно, я пропустил оператор using. Однако я не могу использовать foreach, потому что это приводит к тому, что перечисление оценивается дважды (см. Историю этого ответа для примера!) - person Damian Powell; 18.04.2010

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

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

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.

person Jason C    schedule 11.06.2021