IEnumerable<T> 的 String.Join(string, string[]) 的模拟

Posted

技术标签:

【中文标题】IEnumerable<T> 的 String.Join(string, string[]) 的模拟【英文标题】:An analog of String.Join(string, string[]) for IEnumerable<T> 【发布时间】:2009-06-14 19:19:59 【问题描述】:

String 类包含非常有用的方法 - String.Join(string, string[])

它从一个数组创建一个字符串,用给定的符号分隔数组的每个元素。但一般 - 它不会在最后一个元素之后添加分隔符!我将它用于 ASP.NET 编码,以便与“&lt;br /&gt;”或Environment.NewLine 分隔。

所以我想在asp:Table 的每一行之后添加一个空行。我可以使用IEnumerable&lt;TableRow&gt; 的什么方法来实现相同的功能?

【问题讨论】:

只是一个新的 TableRow 用于插入的值? IEnumerable&lt;TableRow&gt; 如何与接受字符串的连接方法一起工作,换句话说,您实际上是如何调用现有方法的?我不明白你在这里寻找什么。 我目前根本不打电话给String.Join。只是寻找相同的功能 - 在数组元素之间插入分隔符 【参考方案1】:

String.Join 的 Linq 等效项是 Aggregate

例如:

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

如果给定一个 TableRows 的 IE,代码将类似。

【讨论】:

警告:这可能在功能上等同于String.Join,但实现不同。 String.Join 足够聪明,可以使用StringBuilder 来避免分配一大堆瞬态String 实例,而上面的代码没有。 @KentBoogaart 这并不是特别重要,因为最初的问题是关于复制行为的通用方式,string.Join 只是具有“散布”行为的函数的一个示例。跨度> 【参考方案2】:

我写了一个扩展方法:

    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())

【讨论】:

除非我严重误解了这个问题,否则上面的代码毫无意义。它返回最后一项的分隔符就位??什么? 看来先生。 @abatischev 更改了我的代码。嗯。检查第 1 版。Grrr。 我只是重新设计了一些缩进,据我所知 =) 如有打扰,抱歉,如果认为需要,请毫无疑问地回滚 是的,我回滚了,因为您的编辑行为不同。【参考方案3】:

在 .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 来做到这一点无需扩展方法

【讨论】:

【参考方案4】:

没有内置的方法可以做到这一点,你应该自己动手。

【讨论】:

"IEnumerable 的什么方法...?"这是正确的答案,尽管没有那么有用。当然不值得投反对票。【参考方案5】:

如果我找不到适合我需要的方法,我会创建自己的。扩展方法非常好,因为它们可以让你扩展类似的东西。对 asp:table 不太了解,但这里至少有一个扩展方法,您可以调整到任何内容:p

public static class TableRowExtensions

    public string JoinRows(this IEnumerable<TableRow> rows, string separator)
    
        // do what you gotta do
    

【讨论】:

【参考方案6】:

您正在寻找的是Intersperse 函数。有关此类函数的 LINQ 实现,请参阅this question。


顺便说一句,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);

【讨论】:

【参考方案7】:

我发现我经常使用这种扩展方法。

运气好的话,可能是incorporated into 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);

【讨论】:

重新定位问题后发送指向该问题的链接。【参考方案8】:

如果您要经常做这种事情,那么构建自己的扩展方法来做这件事是值得的。下面的实现允许您执行与string.Join(", ", arrayOfStrings) 等效的操作,其中arrayOfStrings 可以是IEnumerable&lt;T&gt;,分隔符可以是任何对象。它允许您执行以下操作:

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

我喜欢这个的两点是:

    它在 LINQ 上下文中非常易读。 它相当高效,因为它使用 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();

【讨论】:

但它对集合进行了两次评估。如果那是一个数据库查询,那么它根本就没有效率。此外,在这里丢失泛型类型并使其仅适用于字符串会更自然。 @Lasse 是的,这是一个公平的观点。我假设只在内存中。现在重构,以便枚举只评估一次。 为什么不使用foreach 声明?现在你有一个可能的资源泄漏。您至少应该使用带有.GetEnumerator()using 语句。 @Matthew 是的,很明显,我错过了 using 语句。我不能使用 foreach ,因为这会导致枚举被评估两次(例如,请参阅此答案的历史!)【参考方案9】:

我发现这篇文章正在寻找同样的东西,但对答案非常不满意(大多数似乎是关于加入字符串 [?],并且接受的答案似乎很冗长)。所以我最终只是想出了一些其他的方法来做到这一点。

我更喜欢这样的方式:

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,创建一个新序列 item, delimiter ,然后使用 SelectMany 将它们连接在一起。 p>

另一种方法,不是单一的,但可能更容易阅读并且非常简单:

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)here。那里也有一个带有Aggregate 的实现,我认为这里不值得写。它可以以类似的方式适应AfterEach

【讨论】:

以上是关于IEnumerable<T> 的 String.Join(string, string[]) 的模拟的主要内容,如果未能解决你的问题,请参考以下文章

无法将类型“IEnumerable<T>”隐式转换为“ActionResult<IEnumerable<T>>”

c# IEnumerable<T>

如何将多个 IEnumerable<IEnumerable<T>> 列表添加到 IEnumerable<List<int>>

IEnumerable<T>是啥意思?

IEnumerable<T> 和 IQueryable<T> 澄清?

返回 IEnumerable<T> 与 IQueryable<T>