2016-10-14 11 views
2

Я строю строку на основе IEnumerable, и делать что-то вроде этого:Перебор 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 не действительно много способствуют.

Как я могу упростить этот код, сохраняя при этом его эффективность?

+0

Вы можете использовать 'prop => $" [{prop}] "или' prop => string.Format ("[{0}]", prop) ' – Anton

+0

Если ваша цель - уменьшить выделение памяти, тогда вы не можете действительно упростить этот код, так работает StringBuilder. Если содержимое цикла foreach намного больше, чем вы должны разделить его на небольшие методы для удобочитаемости, но это все, что вы можете сделать. –

ответ

2

Вы можете заменить это:

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

С # 6 интерполяции строк:

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

В обоих случаях разница семантический только и это на самом деле не имеет значения. Конкатенация строк, как в вашем случае в выборе, не является проблемой. Компилятор все еще создает для него только одну новую строку (а не 4, по одному для каждого сегмента и 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; 

В любом случае я думаю, что для лучшей герметизации содержание объема передачи контура должен сидеть в отдельной функции, которая получит part и separator и возвратит строку вывода. Она также может быть методом расширения для StringBuilder как предложено user734028

+0

Это еще одна строка, созданная там, где может быть нуль. В большинстве случаев это не имеет значения, но требования OP неясны. –

+0

Проверьте с помощью string.IsNullOrEmpty или string.IsNullOrWhitespace, и вы можете рассчитать емкость для уменьшения выделения памяти в StringBuilder – Anton

+1

Я знаю, что мои строки никогда не будут пустыми, так что это не -эмиссионный. Тем не менее, тело обычно слишком сложно заменить на интерполяцию строк. –

0

агрегатного раствора:

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(",", ", "); 
+3

Желаю, чтобы люди перестали использовать 'Агрегат' для конкатенации (черт тебя решайер). Это не более читаемо, чем 'string.Join' и ** way ** менее эффективно –

+0

Мне нравится Aggregate, для целей, где это уместно. Это не один из них :) –

+0

@KooKiz, Resharper? Вы думаете, что невозможно использовать Aggregate без его помощи? –

0

Используйте Aggregate метод расширения с StringBuilder.
Будет более эффективно тогда конкатенации строк, если ваша коллекция большие

 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 
+0

Как это лучше, чем использование цикла for, а не специальный корпус последнего элемента? –

+0

Только потому, что вы не используете специальный корпус для последнего элемента. Одна операция меньше за цикл – Fabio

+0

@Fabio При написании алгоритма для обрезки разделителя более эффективно помещать разделитель в конец. Вначале StringBuilder должен перераспределить кусок.В конце концов, это просто вопрос изменения длины. Фактически, вы можете сделать это непосредственно непосредственно: 'builder.Length - = 2;' –

0

код:

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. Но вы подумали бы (если вы думаете), что Append все происходит с единственным ресурсом, который можно использовать совместно с StringBuilder, а затем вам придется заблокировать его, добавив к нему, не так эффективно! Хорошо, если мы можем сказать, что каждая итерация цикла for во внешней функции создает один артефакт строки, тогда у нас может быть один массив строк, выделенный для подсчета интересных частей перед запуском Parallel, и каждый индекс Parallel for сохранит свою строку в соответствующем индексе.

Что-то вроде:

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

ваша функция AppendPart должны быть скорректированы, чтобы сделать это без расширения взять только строку и возвращает строку. После окончания цикла вы можете сделать string.Join, чтобы получить строку, что вы можете делать с stringBuilder.ToString() тоже.