2016-10-14 3 views
2

ich ein String Aufbau basiert auf einer IEnumerable, und so etwas wie dies zu tun:Iterieren über IEnumerable, spezielles Gehäuse letztes Element

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

ich mit diesem Idiom nicht ganz zufrieden bin, aber ich bin zu kämpfen etwas prägnanter formulieren.

Dies ist natürlich ein Teil eines größeren String Gebäudebetrieb (wo es mehrere Blöcke ähnlich wie diese, sowie andere Sachen dazwischen) - sonst würde ich wahrscheinlich die StringBuilder fallen und string.Join(", ", ...) direkt verwenden.

Meine engsten Versuch, die oben zu vereinfachen, obwohl, wie dies Konstrukte für jede Iterator:

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

aber hier bin ich noch Saiten mit + verketten, die es der StringBuilder nicht tut das Gefühl, macht wirklich viel beitragen.

Wie kann ich diesen Code vereinfachen und gleichzeitig effizient halten?

+0

Sie können 'prop => $" [{prop}] "' oder 'prop => string.Format (" [{0}] ", prop)' – Anton

+0

Wenn Ihr Ziel ist, Speicherzuweisungen zu reduzieren, dann Sie können diesen Code nicht wirklich vereinfachen, so funktioniert der StringBuilder. Wenn der Inhalt der 'foreach'-Schleife viel größer ist, als Sie ihn in kleine Methoden für die Lesbarkeit aufteilen sollten, aber das ist alles, was Sie tun können –

Antwort

2

Sie können diese ersetzen:

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

Mit C# 6 String-Interpolation:

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

In beiden Fällen ist der Unterschied semantische nur und es ist nicht wirklich wichtig. String Verkettung wie in Ihrem Fall in der Auswahl ist kein Problem. Der Compiler erstellt immer noch nur eine neue Zeichenfolge (und nicht 4, eine für jedes Segment und eine vierte für die gemeinsame Zeichenfolge).

es Putting alles zusammen:

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

Weil Körper foreach komplexer ist, dass Sie in einem String-Interpolation Rahmen passen können nur die letzten N Zeichen der Zeichenfolge entfernen, sobald berechnet, wie KooKiz empfohlen.

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; 

Auf jeden Fall denke ich, für eine bessere Kapselung, dass der Inhalt des Anwendungsbereichs der Schleife in einer separaten Funktion sitzen sollte, die eine part und die separator erhalten und wird die Ausgabe-String zurück. Es kann auch eine Erweiterungsmethode für StringBuilder sein, wie durch user734028

+0

Das ist immer noch eine Zeichenfolge erstellt, wo es könnte Null sein. In den meisten Fällen ist es egal, aber die Anforderungen von OP sind unklar –

+0

Überprüfen Sie mit string.IsNullOrEmpty oder string.IsNullOrWhitespace, und Sie können Kapazität berechnen, um Speicherzuweisungen in StringBuilder – Anton

+1

zu reduzieren Ich weiß, dass meine Zeichenfolgen nie Null sein wird, also das ist ein non -Problem. Der Körper ist jedoch normalerweise zu komplex, um ihn durch eine String-Interpolation zu ersetzen. –

0

Aggregate Lösung vorgeschlagen:

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

Serialisierung Lösung:

var temp = JsonConvert.SerializeObject(interestingParts.Select(x => new[] { x })); 
var answer = temp.Substring(1, temp.Length - 2).Replace(",", ", "); 
+3

Ich wünschte, die Leute stoppten mit 'Aggregate' für die Verkettung (verdammt Sie Resharper). Es ist nicht lesbarer als "string.Join" und ist ** Weg ** weniger effizient –

+0

Ich liebe Aggregate, für Zwecke, wo es angebracht ist. Das ist keiner von ihnen :) –

+0

@KooKiz, Resharper? Sie denken, dass es unmöglich ist, Aggregat ohne seine Hilfe zu verwenden? –

0

Verwenden Aggregate Erweiterungsmethode mit StringBuilder.
Wird mehr effizient dann Strings verketten, wenn Sie Ihre Sammlung

 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 

Als reine foreach immer effizienter groß sind, ist dann LINQ dann nur Logik ändern delimeter Komma

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

Wie ist das besser als mit einer for-Schleife und nicht Spezial-Gehäuse das letzte Element? –

+0

Nur weil Sie kein spezielles Gehäuse für das letzte Element verwenden. Eine Operation weniger pro Schleife – Fabio

+0

@Fabio Beim Schreiben eines Algorithmus zum Trimmen eines Separators ist es effizienter, das Trennzeichen am Ende zu platzieren. Zu Beginn muss der StringBuilder den Chunk neu zuweisen.Am Ende ist es nur eine Frage der Längenänderung. Tatsächlich können Sie es direkt selbst tun: 'builder.Length - = 2;' –

0

der Code:

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

viel besser jetzt IMO.

Jetzt ein wenig Diskussion darüber, es sehr schnell zu machen. Wir können Parallel.For verwenden. Aber du würdest denken (wenn du denkst), dass die Anhänge alle mit einer einzigen gemeinsam nutzbaren Ressource, dem StringBuilder, passieren, und dann musst du sie anhängen, um sie anzuhängen, nicht so effizient! Nun, wenn wir sagen können, dass jede Iteration der for-Schleife in der äußeren Funktion ein einzelnes String-Artefakt erzeugt, dann können wir ein einzelnes Array von Strings haben, das der Anzahl interessanter parts vor dem Parallelen für Starts und jedem Index des Parallel for würde seinen String in seinem jeweiligen Index speichern.

Etwas wie:

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

Ihre Funktion AppendPart müssen eingestellt werden, es sich um eine nicht-Erweiterung machen nur einen String zu nehmen und einen String zurück. Nachdem die Schleife beendet ist, können Sie eine Zeichenkette machen. Verbinden Sie sich, um eine Zeichenkette zu erhalten, was Sie möglicherweise auch mit stringBuilder.ToString() machen.