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 编码,以便与“<br />
”或Environment.NewLine
分隔。
所以我想在asp:Table
的每一行之后添加一个空行。我可以使用IEnumerable<TableRow>
的什么方法来实现相同的功能?
【问题讨论】:
只是一个新的 TableRow 用于插入的值?IEnumerable<TableRow>
如何与接受字符串的连接方法一起工作,换句话说,您实际上是如何调用现有方法的?我不明白你在这里寻找什么。
我目前根本不打电话给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如果我找不到适合我需要的方法,我会创建自己的。扩展方法非常好,因为它们可以让你扩展类似的东西。对 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<T>
,分隔符可以是任何对象。它允许您执行以下操作:
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>>”
如何将多个 IEnumerable<IEnumerable<T>> 列表添加到 IEnumerable<List<int>>