【问题标题】:An analog of String.Join(string, string[]) for IEnumerable<T>IEnumerable<T> 的 String.Join(string, string[]) 的模拟
【发布时间】: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。只是寻找相同的功能 - 在数组元素之间插入分隔符

标签: c# linq extension-methods ienumerable


【解决方案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(", ");
        

        我喜欢这个的两点是:

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

        【讨论】:

        • 但它对集合进行了两次评估。如果那是一个数据库查询,那么它根本就没有效率。此外,在这里丢失泛型类型并使其仅适用于字符串会更自然。
        • @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,而不是AfterEachhere。那里也有一个带有Aggregate 的实现,我认为这里不值得写。它可以以类似的方式适应AfterEach

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-09-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-08-04
          • 1970-01-01
          • 2012-01-19
          • 1970-01-01
          相关资源
          最近更新 更多