迭代IEnumerable,特殊大小写最后一个元素

本文关键字:大小写 最后一个 元素 IEnumerable 迭代 | 更新日期: 2023-09-27 18:12:57

我正在构建一个基于IEnumerable的字符串,并做这样的事情:

public string BuildString()
{
    var enumerable = GetEnumerableFromSomewhere(); // actually an in parameter,
                                                   // but this way you don't have to care
                                                   // about the type :)
    var interestingParts = enumerable.Select(v => v.TheInterestingStuff).ToArray();
    stringBuilder.Append("This is it: ");
    foreach(var part in interestingParts)
    {
        stringBuilder.AppendPart(part);
        if (part != interestingParts.Last())
        {
            stringBuilder.Append(", ");
        }
    }
}
private static void AppendPart(this StringBuilder stringBuilder, InterestingPart part)
{
    stringBuilder.Append("[");
    stringBuilder.Append(part.Something");
    stringBuilder.Append("]");
    if (someCondition(part)) 
    {
         // this is in reality done in another extension method,
         // similar to the else clause
         stringBuilder.Append(" = @");
         stringBuilder.Append(part.SomethingElse");
    }
    else
    {
         // this is also an extension method, similar to this one
         // it casts the part to an IEnumerable, and iterates over
         // it in much the same way as the outer method.
         stringBuilder.AppendInFilter(part);
    }
}

我对这个习语不是很满意,但是我正在努力表达更简洁的东西。

这当然是更大的字符串构建操作的一部分(其中有几个类似于这个的块,以及介于两者之间的其他东西)-否则我可能会放弃StringBuilder并直接使用string.Join(", ", ...)

我最接近于简化上面的方法是为每个迭代器构建这样的结构:

stringBuilder.Append(string.Join(", ", propertyNames.Select(prop => "[" + prop + "]")));

但是这里我仍然用+连接字符串,这让人感觉StringBuilder并没有真正贡献很多。

我如何简化这段代码,同时保持它的效率?

迭代IEnumerable,特殊大小写最后一个元素

你可以替换它:

string.Join(", ", propertyNames.Select(prop => "[" + prop + "]"))

与c# 6字符串插值:

string.Join(", ", propertyNames.Select(prop => $"[{prop}]"))

在这两种情况下,区别只是语义上的,这并不重要。字符串连接,就像在选择中那样,不是问题。编译器仍然只为它创建1个新字符串(而不是4个,每个段一个,第四个为联合字符串)。

把它们放在一起:

var result = string.Join(", ", enumerable.Select(v => $"[{v.TheInterestingStuff}]"));

因为foreach的主体是更复杂的,适合字符串插值范围,你可以只是删除字符串的最后N个字符一旦计算,如KooKiz建议。

string separator = ", ";
foreach(var part in interestingParts)
{
    stringBuilder.Append("[");
    stringBuilder.Append(part);
    stringBuilder.Append("]");
    if (someCondition(part)) 
    {
        // Append more stuff
    }
    else
    {
        // Append other thingd
    }
    stringBuilder.Append(separator);
}
stringBuilder.Length = stringBuilder.Lenth - separator;

在任何情况下,我认为为了更好地封装,循环范围的内容应该放在一个单独的函数中,该函数将接收partseparator,并将返回输出字符串。它也可以是user734028

建议的StringBuilder的扩展方法。

使用StringBuilderAggregate扩展方法
如果您的集合很大,将比连接字符串更有效

        var builder = new StringBuilder();
        list.Aggregate(builder, (sb, person) =>
        {
            sb.Append(",");
            sb.Append("[");
            sb.Append(person.Name);
            sb.Append("]");
            return sb;
        });
        builder.Remove(0, 1); // Remove first comma

由于纯foreach总是比LINQ更有效,所以只需更改分隔逗号的逻辑

var builder = new StringBuilder();
foreach(var part in enumerable.Select(v => v.TheInterestingStuff))
{
    builder.Append(", ");
    builder.Append("[");
    builder.Append(part);
    builder.Append("]");
}
builder.Remove(0, 2); //Remove first comma and space

解决方案:

var answer = interestingParts.Select(v => "[" + v + "]").Aggregate((a, b) => a + ", " + b);

序列化解决方案:

var temp = JsonConvert.SerializeObject(interestingParts.Select(x => new[] { x }));
var answer = temp.Substring(1, temp.Length - 2).Replace(",", ", ");
代码:
public string BuildString()
{
    var enumerable = GetEnumerableFromSomewhere();
    var interestingParts = enumerable.Select(v => v.TheInterestingStuff).ToArray();
    stringBuilder.Append("This is it: ");
    foreach(var part in interestingParts)
    {
        stringBuilder.AppendPart(part)
    }
    if (stringBuilder.Length>0)
        stringBuilder.Length--;
}
private static void AppendPart(this StringBuilder stringBuilder, InterestingPart part)
{
    if (someCondition(part)) 
    {
        stringBuilder.Append(string.Format("[{0}] = @{0}", part.Something));         
    }
    else
    {
         stringBuilder.Append(string.Format("[{0}]", part.Something));
         stringBuilder.AppendInFilter(part); //
    }
}

现在好多了。

现在讨论一下如何使它非常快。我们可以用parallel, for。但是您会认为(如果您认为)Appends都发生在单个可共享资源上,即StringBuilder,然后您必须将其锁定为Append,这不是那么有效!好吧,如果我们可以说外部函数中for循环的每次迭代都创建一个字符串工件,那么我们就可以有一个字符串数组,在并行for开始之前分配给感兴趣的部分的计数,并且并行for的每个索引将其字符串存储到其各自的索引中。

类似:

string[] iteration_buckets = new string[interestingParts.Length];
System.Threading.Tasks.Parallel.For(0, interestingParts.Length,
    (index) =>
    {
        iteration_buckets[index] = AppendPart(interestingParts[index]);
    });

你的函数AppendPart必须被调整,使它成为一个非扩展,只接受一个字符串并返回一个字符串。循环结束后,您可以执行一个字符串。Join来获取一个字符串,这也是你可以用stringBuilder.ToString()来做的。