Ciąg wyjściowy: formatowanie lub konkatowanie w C #?


178

Powiedzmy, że chcesz wyprowadzić lub połączyć ciągi. Który z poniższych stylów preferujesz?

  • var p = new { FirstName = "Bill", LastName = "Gates" };

  • Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

  • Console.WriteLine(p.FirstName + " " + p.LastName);

Czy wolisz używać formatu, czy po prostu łączysz ciągi? Jaki jest Twój ulubiony? Czy któryś z nich rani twoje oczy?

Czy masz jakieś racjonalne argumenty, aby użyć jednego, a nie drugiego?

Ja bym wybrał drugą.

Odpowiedzi:


88

Wypróbuj ten kod.

To nieco zmodyfikowana wersja Twojego kodu.
1. Usunąłem Console.WriteLine, ponieważ prawdopodobnie jest o kilka rzędów wielkości wolniejszy niż to, co próbuję zmierzyć.
2. Uruchamiam Stopwatch przed pętlą i zatrzymuję go zaraz po tym, dzięki czemu nie tracę precyzji, jeśli wykonanie funkcji wymaga np. 26,4 tików.
3. Sposób podzielenia wyniku przez kilka iteracji był nieprawidłowy. Zobacz, co się stanie, jeśli masz 1000 milisekund i 100 milisekund. W obu sytuacjach po podzieleniu przez 1000000 otrzymasz 0 ms.

Stopwatch s = new Stopwatch();

var p = new { FirstName = "Bill", LastName = "Gates" };

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (p.FirstName + " " + p.LastName);
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();
s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", p.FirstName, p.LastName);
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();


Console.Clear();
Console.WriteLine(n.ToString()+" x result = string.Format(\"{0} {1}\", p.FirstName, p.LastName); took: " + (fElapsedMilliseconds) + "ms - " + (fElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x result = (p.FirstName + \" \" + p.LastName); took: " + (cElapsedMilliseconds) + "ms - " + (cElapsedTicks) + " ticks");
Thread.Sleep(4000);

Oto moje wyniki:

1000000 x wynik = string.Format ("{0} {1}", p.FirstName, p.LastName); zajęło: 618 ms - 2213706 taktów
1000000 x wynik = (p.FirstName + "" + p.LastName); wziął: 166ms - 595610 ticków


1
Bardzo interesujące. Uzyskałem średnio 224 ms w porównaniu z 48 ms, co oznacza poprawę x4,66, nawet lepszą niż twój x3,72. Zastanawiam się, czy istnieje narzędzie po kompilacji, które może przepisać IL, string.Formatktóre nie używa żadnych funkcji formatowania kompozytowego (tj. Jest po prostu proste {0}) i zastępuje je znacznie szybszą konkatenacją ciągów. Zastanawiam się, że taki wyczyn jest możliwy do osiągnięcia przy użyciu istniejącego narzędzia do ponownego pisania IL, takiego jak PostSharp.
Allon Guralnek

31
Ciągi znaków są niezmienne, co oznacza, że ​​ten sam mały fragment pamięci jest używany w kodzie w kółko. Dodawanie tych samych dwóch ciągów do siebie i ciągłe tworzenie tego samego nowego ciągu nie ma wpływu na pamięć. .Net jest wystarczająco inteligentny, aby używać tego samego odniesienia do pamięci. Dlatego Twój kod nie testuje tak naprawdę różnicy między dwiema metodami konkatowania. Zobacz kod w mojej odpowiedzi poniżej.
Ludington

1
Szczerze mówiąc, zawsze konkatenuję, ponieważ jest to dla mnie łatwiejsze do odczytania i wow, jest szybsze :)
puretppc

Czyli prędkość to jedyny powód, dla którego warto wybrać jedną z nich?
niico

158

Jestem zdumiony, że tak wiele osób od razu chce znaleźć kod, który wykonuje się najszybciej. Jeśli przetworzenie JEDNEGO MILIONA iteracji NADAL zajmie mniej niż sekundę, czy będzie to JAKIEKOLWIEK zauważalne dla użytkownika końcowego? Niezbyt prawdopodobne.

Przedwczesna optymalizacja = FAIL.

Poszedłbym z String.Format opcję tylko dlatego, że ma to największy sens z architektonicznego punktu widzenia. Nie obchodzi mnie wydajność, dopóki nie stanie się to problemem (a jeśli tak, zadałbym sobie pytanie: czy muszę łączyć milion nazwisk naraz? Z pewnością nie wszystkie zmieszczą się na ekranie ...)

Zastanów się, czy klient później chce to zmienić, aby mógł skonfigurować, czy ma wyświetlać, "Firstname Lastname"czy "Lastname, Firstname."z opcją Format, jest to łatwe - po prostu zamień ciąg formatu. Do konkatacji potrzebny będzie dodatkowy kod. Jasne, że w tym konkretnym przykładzie nie brzmi to jak wielka sprawa, ale ekstrapoluj.


47
Słuszna uwaga, jeśli chodzi o „Przedwczesną optymalizację == FAIL”, tak. Ale kiedy zaczynasz płacić za ślad wykonawczy (chmura i infrastruktura jako usługa, ktoś?) I / lub zaczynasz wspierać 1 milion użytkowników w czymś, wtedy odpowiedź dla pojedynczego użytkownika na żądanie nie jest kwestią. Koszt obsługi żądania
wysłanego

23
To jest po prostu całkowicie błędne. W środowisku programistycznym często zdarza się, że kod generujący ciąg jest głęboko osadzony zarówno w modelu, widokach, jak i kontrolerach i może zostać wywołany dziesiątki tysięcy razy na ładowanie strony. Skrócenie czasu poświęcanego na ocenę kodu generującego ciągi o 50% może być ogromną korzyścią.
Benjamin Sussman

2
Takie pytanie nie będzie dotyczyło tylko jednej instancji PO. Odpowiedzią jest coś, co ludzie mogą zapamiętać jako "w jaki sposób mam składać struny?" kiedy piszą cały swój kod.
Phil Miller,

6
@Benjamin: ... w takim przypadku profilowałbyś i stwierdził, że jest to Twoje wąskie gardło. Założę się jednak, że wyciągasz to znikąd; po napisaniu i profilowaniu wielu aplikacji internetowych w przeszłości prawie zawsze stwierdzałem, że wąskim gardłem w czasie odpowiedzi (po stronie serwera) są zapytania do bazy danych.
BlueRaja - Danny Pflughoeft

2
Z całą pewnością NIE jest to przedwczesna optymalizacja. Całkiem błędne przekonanie. Wydajność ciągów może całkowicie zablokować interfejs użytkownika, szczególnie w .NET, jeśli wykonujesz dużo formatowania i budowania ciągów. ubiquity.acm.org/article.cfm?id=1513451
user99999991

54

Ojej - po przeczytaniu jednej z pozostałych odpowiedzi próbowałem odwrócić kolejność operacji - więc najpierw wykonałem konkatenację, a potem String.Format ...

Bill Gates
Console.WriteLine(p.FirstName + " " + p.LastName); took: 8ms - 30488 ticks
Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 0ms - 182 ticks

Tak więc kolejność operacji robi OGROMNĄ różnicę, a raczej pierwsza operacja jest ZAWSZE znacznie wolniejsza.

Oto wyniki przebiegu, w którym operacje są wykonywane więcej niż raz. Próbowałem zmienić rozkazy, ale ogólnie rzecz biorąc, obowiązują te same zasady, gdy pierwszy wynik jest ignorowany:

Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 5ms - 20335 ticks
Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 0ms - 156 ticks
Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 0ms - 122 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 181 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 122 ticks
Bill Gates
String.Concat(FirstName, " ", LastName); took: 0ms - 142 ticks
Bill Gates
String.Concat(FirstName, " ", LastName); took: 0ms - 117 ticks

Jak widać, kolejne uruchomienia tej samej metody (refaktoryzowałem kod na 3 metody) są przyrostowo szybsze. Najszybszą wydaje się być metoda Console.WriteLine (String.Concat (...)), po której następuje zwykłe konkatenowanie, a następnie operacje sformatowane.

Początkowe opóźnienie w uruchomieniu jest prawdopodobnie związane z inicjalizacją Console Stream, ponieważ umieszczenie Console.Writeline („Start!”) Przed pierwszą operacją przywraca wszystkie czasy do linii.


2
Następnie całkowicie usuń Console.WriteLine z testów. To wypacza wyniki!
CShark

Zawsze zaczynam od scenariusza wyrzucenia lub „kontroli” podczas przeprowadzania testów wydajności właśnie z tego powodu
drzaus

36

Ciągi znaków są niezmienne, co oznacza, że ​​ten sam mały fragment pamięci jest używany w kodzie w kółko. Dodawanie tych samych dwóch ciągów do siebie i ciągłe tworzenie tego samego nowego ciągu nie ma wpływu na pamięć. .Net jest wystarczająco inteligentny, aby używać tego samego odniesienia do pamięci. Dlatego twój kod tak naprawdę nie testuje różnicy między dwiema metodami konkatowania.

Wypróbuj ten rozmiar:

Stopwatch s = new Stopwatch();

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0, sbElapsedMilliseconds = 0, sbElapsedTicks = 0;

Random random = new Random(DateTime.Now.Millisecond);

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (random.Next().ToString() + " " + random.Next().ToString());
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();

s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", random.Next().ToString(), random.Next().ToString());
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();

StringBuilder sb = new StringBuilder();
s.Start();
for(var i = 0; i < n; i++){
    sb.Clear();
    sb.Append(random.Next().ToString());
    sb.Append(" ");
    sb.Append(random.Next().ToString());
    result = sb.ToString();
}
s.Stop();
sbElapsedMilliseconds = s.ElapsedMilliseconds;
sbElapsedTicks = s.ElapsedTicks;
s.Reset();

Console.WriteLine(n.ToString() + " x result = string.Format(\"{0} {1}\", p.FirstName, p.LastName); took: " + (fElapsedMilliseconds) + "ms - " + (fElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x result = (p.FirstName + \" \" + p.LastName); took: " + (cElapsedMilliseconds) + "ms - " + (cElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(\" \"); sb.Append(random.Next().ToString()); result = sb.ToString(); took: " + (sbElapsedMilliseconds) + "ms - " + (sbElapsedTicks) + " ticks");
Console.WriteLine("****************");
Console.WriteLine("Press Enter to Quit");
Console.ReadLine();

Przykładowe dane wyjściowe:

1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 513ms - 1499816 ticks
1000000 x result = (p.FirstName + " " + p.LastName); took: 393ms - 1150148 ticks
1000000 x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(" "); sb.Append(random.Next().ToString()); result = sb.ToString(); took: 405ms - 1185816 ticks

1
Dodano StringBuilder i przykładowe wyjście do odpowiedzi
mikeschuld

Widzę, że używanie string.Formatjest warte niewielkiej wydajności tutaj. Architektonicznie jest to lepsze, ponieważ oznacza, że ​​możesz łatwiej zmieniać format. Ale budowniczy ciągów, naprawdę nie widzę sensu. Każdy inny wątek tutaj mówi, że powinieneś użyć Stringbuildera zamiast łączenia ciągów. Jaka jest zaleta? Najwyraźniej nie szybkości, jak pokazuje ten wzorzec.
roryok 27.07.15

22

Szkoda biednych tłumaczy

Jeśli ty wiesz że Twoja aplikacja pozostanie w języku angielskim, to dobrze, zachowaj tyknięcia zegara. Jednak w wielu kulturach nazwisko nazwisko imię jest zwykle widoczne na przykład w adresach.

Więc używaj string.Format(), zwłaszcza jeśli kiedykolwiek będziesz mieć swoją aplikację wszędzie, gdzie angielski nie jest pierwszym językiem.


2
Jak string.Format()zachowywałby się inaczej w różnych kulturach? Czy nadal nie wypisuje imienia, a następnie nazwiska? Wygląda na to, że w obu sytuacjach należałoby wziąć pod uwagę odmienną kulturę. Czuję, że czegoś mi tu brakuje.
Broots Waymb

2
Zgadzam się z @DangerZone .. skąd string.Format()możesz wiedzieć, że używasz nazwy dla adresu? Gdyby string.Format()zamienić {0} {1}na podstawie kultury, uznałbym to za zepsute.
Alex McMillan

2
Uważam, że Jeremy próbował wskazać, że w opisanym scenariuszu dotyczącym obsługi różnych krajów może być właściwe wyodrębnienie samego ciągu formatu do zasobu językowego. W większości krajów ten ciąg to „{0} {1}”, ale w przypadku krajów, w których pierwszeństwo ma nazwisko (np. Węgry, Hongkong, Kambodża, Chiny, Japonia, Korea, Madagaskar, Tajwan, Wietnam i części Indii), ten ciąg będzie miał postać „{1} {0}”.
Richard J Foster

W rzeczy samej. Lub, bardziej subtelnie, dodaj ciąg formatu jako atrybut osoby. Ja na przykład lubię mieć swoje nazwisko po imieniu, ale mój kolega Beng nie.
Jeremy McGee

14

Oto moje wyniki po 100 000 iteracji:

Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took (avg): 0ms - 689 ticks
Console.WriteLine(p.FirstName + " " + p.LastName); took (avg): 0ms - 683 ticks

A oto kod stanowiska:

Stopwatch s = new Stopwatch();

var p = new { FirstName = "Bill", LastName = "Gates" };

//First print to remove the initial cost
Console.WriteLine(p.FirstName + " " + p.LastName);
Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

int n = 100000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

for (var i = 0; i < n; i++)
{
    s.Start();
    Console.WriteLine(p.FirstName + " " + p.LastName);
    s.Stop();
    cElapsedMilliseconds += s.ElapsedMilliseconds;
    cElapsedTicks += s.ElapsedTicks;
    s.Reset();
    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    fElapsedMilliseconds += s.ElapsedMilliseconds;
    fElapsedTicks += s.ElapsedTicks;
    s.Reset();
}

Console.Clear();

Console.WriteLine("Console.WriteLine(\"{0} {1}\", p.FirstName, p.LastName); took (avg): " + (fElapsedMilliseconds / n) + "ms - " + (fElapsedTicks / n) + " ticks");
Console.WriteLine("Console.WriteLine(p.FirstName + \" \" + p.LastName); took (avg): " + (cElapsedMilliseconds / n) + "ms - " + (cElapsedTicks / n) + " ticks");

Nie wiem więc, czyją odpowiedź oznaczyć jako odpowiedź :)


Dlaczego tło tej odpowiedzi jest niebieskie?
user88637

@yossi jest niebieskie, ponieważ odpowiadający jest taki sam jak
pytający

9

Łączenie ciągów jest w porządku w takim prostym scenariuszu - jest to bardziej skomplikowane z czymś bardziej skomplikowanym, nawet z LastName, FirstName. Dzięki formatowi możesz na pierwszy rzut oka zobaczyć, jaka będzie ostateczna struktura łańcucha podczas czytania kodu, przy konkatenacji prawie niemożliwe jest natychmiastowe rozpoznanie końcowego wyniku (z wyjątkiem bardzo prostego przykładu, takiego jak ten).

Na dłuższą metę oznacza to, że kiedy wrócisz, aby zmienić format ciągu, będziesz mieć możliwość wpadnięcia i wprowadzenia kilku poprawek w ciągu formatu, albo zmarszczysz brwi i zaczniesz się poruszać rodzaje metod dostępu do właściwości zmieszane z tekstem, co z większym prawdopodobieństwem spowoduje problemy.

Jeśli używasz .NET 3.5, możesz użyć metody rozszerzenia, takiej jak ta i uzyskać łatwy przepływ, poza składnią mankietu, w następujący sposób:

string str = "{0} {1} is my friend. {3}, {2} is my boss.".FormatWith(prop1,prop2,prop3,prop4);

Wreszcie, gdy Twoja aplikacja staje się coraz bardziej złożona, możesz zdecydować, że aby rozsądnie zarządzać ciągami znaków w aplikacji, chcesz przenieść je do pliku zasobów w celu lokalizacji lub po prostu do statycznego pomocnika. Będzie to DUŻO łatwiejsze do osiągnięcia, jeśli konsekwentnie używasz formatów, a twój kod może być po prostu refaktoryzowany, aby użyć czegoś takiego jak

string name = String.Format(ApplicationStrings.General.InformalUserNameFormat,this.FirstName,this.LastName);

7

Do bardzo prostej manipulacji użyłbym konkatenacji, ale gdy przekroczysz 2 lub 3 elementy Format staje się bardziej odpowiedni IMO.

Innym powodem, dla którego warto preferować String.Format, jest to, że ciągi .NET są niezmienne i robiąc to w ten sposób, tworzy mniej kopii tymczasowych / pośrednich.


6

Chociaż całkowicie rozumiem preferencje stylu i wybrałem konkatenację dla mojej pierwszej odpowiedzi, częściowo w oparciu o moje własne preferencje, część mojej decyzji była oparta na myśli, że konkatenacja będzie szybsza. Tak więc z ciekawości przetestowałem go i wyniki były oszałamiające, szczególnie dla tak małego sznurka.

Używając poniższego kodu:

    System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch();

    var p = new { FirstName = "Bill", LastName = "Gates" };

    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    Console.WriteLine("Console.WriteLine(\"{0} {1}\", p.FirstName, p.LastName); took: " + s.ElapsedMilliseconds + "ms - " + s.ElapsedTicks + " ticks");

    s.Reset();
    s.Start();
    Console.WriteLine(p.FirstName + " " + p.LastName);
    s.Stop();

    Console.WriteLine("Console.WriteLine(p.FirstName + \" \" + p.LastName); took: " + s.ElapsedMilliseconds + "ms - " + s.ElapsedTicks + " ticks");

Otrzymałem następujące wyniki:

Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 2ms - 7280 ticks
Bill Gates
Console.WriteLine(p.FirstName + " " + p.LastName); took: 0ms - 67 ticks

Korzystanie z metody formatowania jest ponad 100 razy wolniejsze! Konkatenacja nie została nawet zarejestrowana jako 1ms, dlatego też wyprowadzam tyknięcia timera.


2
Ale oczywiście powinieneś wykonać tę operację więcej niż raz, aby uzyskać pomiary.
erikkallen

2
I stracić wywołanie Console.Writeline (), ponieważ wykracza to poza zakres pytania?
Aidanapword

czy testowałeś z kulturystą? ;)
niico

6

Począwszy od C # 6,0 interpolowanych ciągów, można to zrobić, co jeszcze bardziej upraszcza format.

var name = "Bill";
var surname = "Gates";
MessageBox.Show($"Welcome to the show, {name} {surname}!");

Interpolowane wyrażenie tekstowe wygląda jak ciąg szablonu zawierający wyrażenia. Interpolowane wyrażenie tekstowe tworzy ciąg, zastępując zawarte wyrażenia reprezentacjami ToString wyników wyrażeń.

Ciągi interpolowane mają podobną wydajność do String.Format, ale poprawioną czytelność i krótszą składnię ze względu na fakt, że wartości i wyrażenia są wstawiane w linii.

Zapoznaj się również z tym artykułem dotnetperls dotyczącym interpolacji ciągów.

Jeśli szukasz domyślnego sposobu formatowania ciągów, ma to sens pod względem czytelności i wydajności (z wyjątkiem sytuacji, gdy mikrosekundy będą miały wpływ na twój konkretny przypadek użycia).


5

Do podstawowej konkatenacji ciągów używam zazwyczaj drugiego stylu - łatwiejszego do odczytania i prostszego. Jeśli jednak wykonuję bardziej skomplikowaną kombinację ciągów, zwykle wybieram String.Format.

String.Format oszczędza wiele cytatów i plusów ...

Console.WriteLine("User {0} accessed {1} on {2}.", user.Name, fileName, timestamp);
vs
Console.WriteLine("User " + user.Name + " accessed " + fileName + " on " + timestamp + ".");

Zapisano tylko kilka postaci, ale myślę, że w tym przykładzie format sprawia, że ​​jest znacznie czystszy.


5

Lepszym testem byłoby obserwowanie pamięci za pomocą Perfmon i liczników pamięci CLR. Rozumiem, że cały powód, dla którego chcesz używać String.Format zamiast po prostu łączyć ciągi, jest taki, że ponieważ ciągi są niezmienne, niepotrzebnie obciążasz moduł odśmiecania tymczasowymi ciągami, które muszą zostać odzyskane w następnym przebiegu.

StringBuilder i String.Format, chociaż potencjalnie wolniejsze, są bardziej wydajne w pamięci.

Co jest takiego złego w konkatenacji ciągów?


Zgadzam się; każda operacja na łańcuchu tworzy nową kopię łańcucha. Cała ta pamięć zostanie wcześniej czy później odzyskana przez garbage collectora. Tak więc przydzielenie wielu ciągów może wrócić i ugryźć cię później.
Marnix van Valen

5

Generalnie wolę ten pierwszy, ponieważ szczególnie przy długich strunach może być dużo łatwiejszy do odczytania.

Inną korzyścią jest, moim zdaniem, jedną z wydajności, ponieważ ta ostatnia faktycznie wykonuje 2 instrukcje tworzenia ciągu przed przekazaniem końcowego ciągu do metody Console.Write. Uważam, że String.Format używa StringBuilder pod okładkami, więc unika się wielokrotnych konkatenacji.

Należy jednak zauważyć, że jeśli parametry, które przekazujesz do String.Format (i inne takie metody, takie jak Console.Write) są typami wartości, zostaną one opakowane przed przekazaniem, co może zapewnić własne wyniki wydajności. Post na blogu na ten temat tutaj .


1
Ten post na blogu jest teraz pod adresem: jeffbarnes.net/blog/post/2006/08/08/… . Nie mam wystarczającej liczby powtórzeń do edycji.
Richard Slater

5

Za tydzień od teraz 19 sierpnia 2015 to pytanie będzie miało dokładnie siedem (7) lat. Jest teraz lepszy sposób na zrobienie tego. Lepsze pod względem łatwości utrzymania, ponieważ nie wykonałem żadnego testu wydajności w porównaniu do zwykłego łączenia ciągów (ale czy ma to znaczenie w dzisiejszych czasach? Różnica kilku milisekund?). Nowy sposób na zrobienie tego w C # 6.0 :

var p = new { FirstName = "Bill", LastName = "Gates" };
var fullname = $"{p.FirstName} {p.LastName}";

Ta nowa funkcja jest lepsza , IMO, a właściwie lepsza w naszym przypadku, ponieważ mamy kody, w których budujemy kwerendy, których wartości zależą od pewnych czynników. Wyobraź sobie jedno pytanie, w którym mamy 6 argumentów. Więc zamiast robić na przykład:

var qs = string.Format("q1={0}&q2={1}&q3={2}&q4={3}&q5={4}&q6={5}", 
    someVar, anotherVarWithLongName, var3, var4, var5, var6)

in można napisać w ten sposób i jest to łatwiejsze do odczytania:

var qs=$"q1={someVar}&q2={anotherVarWithLongName}&q3={var3}&q4={var4}&q5={var5}&q6={var6}";

Rzeczywiście, nowy sposób C # 6.0 jest lepszy niż poprzednie alternatywy - przynajmniej z punktu widzenia czytelności.
Philippe

Zgadza się. Jest to również bezpieczniejsze, ponieważ nie musisz się martwić, który obiekt trafi do którego indeksu (symbolu zastępczego), ponieważ umieścisz obiekty bezpośrednio tam, gdzie chcesz.
von v.

BTW, faktycznie wywołuje Format (przynajmniej z Roslyn).
Philippe

Przy okazji, to, do czego odnosi się ten plakat, nazywa się „interpolacją ciągów” i jest omówione w innym miejscu tego wątku.
CShark

4
  1. Formatowanie to metoda „.NET”. Niektóre narzędzia do refaktoryzacji (Refactor! Na przykład) proponują nawet refaktoryzację kodu typu concat, aby używał stylu formatowania.
  2. Formatowanie jest łatwiejsze do optymalizacji pod kątem kompilatora (chociaż drugi prawdopodobnie zostanie zrefaktoryzowany, aby użyć metody „Concat”, która jest szybka).
  3. Formatowanie jest zwykle bardziej czytelne (zwłaszcza przy formatowaniu „fantazyjnym”).
  4. Formatowanie oznacza niejawne wywołania „.ToString” dla wszystkich zmiennych, co jest dobre dla czytelności.
  5. Zgodnie z „Efektywnym C #”, implementacje „WriteLine” i „Format” .NET są pomieszane, automatycznie blokują wszystkie typy wartości (co jest złe). „Efektywny C #” zaleca jawne wykonywanie wywołań „.ToString”, które IMHO jest fałszywe (zobacz post Jeffa )
  6. W tej chwili wskazówki dotyczące typu formatowania nie są sprawdzane przez kompilator, co powoduje błędy w czasie wykonywania. Jednak może to zostać zmienione w przyszłych wersjach.

4

Wybieram na podstawie czytelności. Wolę opcję formatowania, gdy wokół zmiennych jest jakiś tekst. W tym przykładzie:

Console.WriteLine("User {0} accessed {1} on {2}.", 
                   user.Name, fileName, timestamp);

rozumiesz znaczenie nawet bez nazw zmiennych, podczas gdy concat jest zaśmiecony cudzysłowami i znakami + i dezorientuje moje oczy:

Console.WriteLine("User " + user.Name + " accessed " + fileName + 
                  " on " + timestamp + ".");

(Pożyczyłem przykład Mike'a, ponieważ mi się podoba)

Jeśli ciąg formatu niewiele znaczy bez nazw zmiennych, muszę użyć concat:

   Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

Opcja formatu sprawia, że ​​czytam nazwy zmiennych i odwzorowuję je na odpowiadające im liczby. Opcja concat tego nie wymaga. Nadal jestem zdezorientowany cytatami i znakami +, ale alternatywa jest gorsza. Rubin?

   Console.WriteLine(p.FirstName + " " + p.LastName);

Jeśli chodzi o wydajność, oczekuję, że opcja formatowania będzie wolniejsza niż konkatacja, ponieważ format wymaga przeanalizowania ciągu . Nie pamiętam, żebym musiał optymalizować tego rodzaju instrukcje, ale gdybym to zrobił, spojrzałbym na stringmetody takie jak Concat()i Join().

Inną zaletą formatu jest to, że ciąg formatu można umieścić w pliku konfiguracyjnym. Bardzo przydatne z komunikatami o błędach i tekstem interfejsu użytkownika.


4

Użyłbym String.Format, ale chciałbym również mieć ciąg formatu w plikach zasobów, aby można go było zlokalizować dla innych języków. Używanie prostego konkatowania ciągów nie pozwala na to. Oczywiście, jeśli nigdy nie będziesz musiał lokalizować tego ciągu, nie jest to powód do myślenia. To naprawdę zależy od tego, do czego służy sznurek.

Jeśli miałoby to zostać pokazane użytkownikowi, użyłbym String.Format, aby móc zlokalizować, jeśli zajdzie taka potrzeba - a FxCop na wszelki wypadek sprawdzi to za mnie.

Jeśli zawiera liczby lub inne elementy niebędące ciągami (np. Daty), użyłbym String.Format, ponieważ zapewnia mi większą kontrolę nad formatowaniem .

Jeśli służy do tworzenia zapytania takiego jak SQL, użyłbym Linq .

Jeśli do łączenia ciągów w pętli użyłbym StringBuilder aby uniknąć problemów z wydajnością.

Jeśli chodzi o jakieś wyjście, którego użytkownik nie zobaczy i nie będzie miało wpływu na wydajność, użyłbym String.Format, ponieważ i tak mam zwyczaj go używać i po prostu jestem do tego przyzwyczajony :)


3

Jeśli masz do czynienia z czymś, co musi być łatwe do odczytania (a to jest większość kodu), trzymałbym się wersji przeciążenia operatora, chyba że:

  • Kod musi zostać wykonany miliony razy
  • Robisz mnóstwo konkatów (więcej niż 4 to tona)
  • Kod jest skierowany do Compact Framework

W co najmniej dwóch z tych okoliczności użyłbym zamiast tego StringBuilder.


3

Jeśli zamierzasz zlokalizować wynik, to String.Format jest niezbędny, ponieważ różne języki naturalne mogą nawet nie mieć danych w tej samej kolejności.


2

Myślę, że zależy to w dużej mierze od tego, jak skomplikowany jest wynik. Wybieram ten scenariusz, który w danym momencie działa najlepiej.

Wybierz odpowiednie narzędzie w zależności od zadania: D Cokolwiek wygląda na najczystsze!



2

Niezłe!

Właśnie dodane

        s.Start();
        for (var i = 0; i < n; i++)
            result = string.Concat(p.FirstName, " ", p.LastName);
        s.Stop();
        ceElapsedMilliseconds = s.ElapsedMilliseconds;
        ceElapsedTicks = s.ElapsedTicks;
        s.Reset();

I jest jeszcze szybszy (chyba w obu przykładach wywoływany jest string.Concat, ale pierwszy wymaga jakiegoś tłumaczenia).

1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 249ms - 3571621 ticks
1000000 x result = (p.FirstName + " " + p.LastName); took: 65ms - 944948 ticks
1000000 x result = string.Concat(p.FirstName, " ", p.LastName); took: 54ms - 780524 ticks

2
Zajmuje to dokładnie tyle samo czasu, ponieważ konkatenacje ciągów oparte na operatorach są tłumaczone przez kompilator na wywołania string.Concat(...). Jest to wykonywane podczas kompilacji, więc nie ma wpływu na wydajność w czasie wykonywania. Jeśli uruchomisz testy wiele razy lub uruchomisz je na większych próbkach testowych, zobaczysz, że są identyczne.
Allon Guralnek

2

Ponieważ nie sądzę, aby odpowiedzi tutaj obejmowały wszystko, chciałbym dodać tutaj mały dodatek.

Console.WriteLine(string format, params object[] pars) wezwania string.Format . Znak „+” oznacza konkatenację ciągów. Nie sądzę, że zawsze ma to związek ze stylem; Zwykle mieszam te dwa style w zależności od kontekstu, w którym się znajduję.

Krótka odpowiedź

Decyzja, przed którą stoisz, ma związek z alokacją ciągów. Postaram się to uprościć.

Powiedz, że masz

string s = a + "foo" + b;

Jeśli to wykonasz, oceni to w następujący sposób:

string tmp1 = a;
string tmp2 = "foo" 
string tmp3 = concat(tmp1, tmp2);
string tmp4 = b;
string s = concat(tmp3, tmp4);

tmptutaj nie jest tak naprawdę zmienną lokalną, ale jest ona tymczasowa dla JIT (jest umieszczana na stosie IL). Jeśli ldstrumieścisz łańcuch na stosie (na przykład w IL dla literałów), umieszczasz odwołanie do wskaźnika ciągu na stosie.

Moment wywołania concattego odwołania staje się problemem, ponieważ nie ma żadnego dostępnego odwołania do łańcucha, które zawiera oba ciągi. Oznacza to, że .NET musi przydzielić nowy blok pamięci, a następnie wypełnić go dwoma ciągami. Powodem tego jest problem, ponieważ alokacja jest stosunkowo droga.

Co zmienia pytanie na: Jak zmniejszyć liczbę concatoperacji?

Tak więc przybliżona odpowiedź brzmi: string.Formatdla> 1 konkatacji „+” będzie działać dobrze na 1 konkat. A jeśli nie zależy ci na wykonywaniu mikrooptymalizacji wydajności, string.Formatw ogólnym przypadku będzie działać dobrze.

Uwaga o kulturze

A potem jest coś, co nazywa się kulturą ...

string.Format umożliwia korzystanie CultureInfo w formatowaniu. Prosty operator „+” używa bieżącej kultury.

Jest to szczególnie ważna uwaga, jeśli piszesz formaty plików i np. doublewartości, które „dodajesz” do ciągu. Na różnych komputerach możesz skończyć z różnymi łańcuchami, jeśli nie używaszstring.Format z jawnym CultureInfo.

F.ex. zastanów się, co się stanie, jeśli zmienisz „.” zamiast „,” podczas pisania pliku z wartościami oddzielonymi przecinkami ... w języku holenderskim separatorem dziesiętnym jest przecinek, więc użytkownik może po prostu otrzymać „zabawną” niespodziankę.

Bardziej szczegółowa odpowiedź

Jeśli nie znasz wcześniej dokładnego rozmiaru łańcucha, najlepiej użyć takiej zasady, aby nadać przydział buforów, których używasz. Wolna przestrzeń jest najpierw wypełniana, po czym dane są kopiowane.

Powiększanie oznacza przydzielanie nowego bloku pamięci i kopiowanie starych danych do nowego bufora. Następnie można zwolnić stary blok pamięci. W tym momencie dochodzisz do wniosku: uprawa to kosztowna operacja.

Najbardziej praktycznym sposobem na to jest użycie zasad nadmiernej alokacji. Najbardziej powszechną polityką jest nadawanie buforów potęgom 2. Oczywiście musisz to zrobić trochę mądrzej (ponieważ nie ma sensu rosnąć z 1,2,4,8 jeśli już wiesz, że potrzebujesz 128 znaków ), ale masz obraz. Polityka zapewnia, że ​​nie potrzebujesz zbyt wielu kosztownych operacji, które opisałem powyżej.

StringBuilderjest klasą, która w zasadzie z nadmierną alokacją bazowego bufora potęgami dwóch. string.Formatużywa StringBuilderpod maską.

To sprawia, że ​​Twoja decyzja jest podstawowym kompromisem między nadmierną alokacją i dołączaniem (-multiple) (w / wo culture) lub po prostu przydziel i dołącz.


1

Osobiście, drugi, ponieważ wszystko, czego używasz, jest w bezpośredniej kolejności, w jakiej zostanie wyprowadzony. Podczas gdy w przypadku pierwszego musisz dopasować {0} i {1} z odpowiednią zmienną, co łatwo zepsuć.

Przynajmniej nie jest tak źle, jak sprintf w C ++, gdzie jeśli źle ustawisz typ zmiennej, wszystko wybuchnie.

Ponadto, ponieważ druga jest w całości wbudowana i nie musi wyszukiwać ani zastępować wszystkich {0} rzeczy, ta ostatnia powinna być szybsza… chociaż nie wiem na pewno.


1

Właściwie podoba mi się ten pierwszy, ponieważ gdy jest wiele zmiennych przeplatanych z tekstem, wydaje mi się łatwiejszy do odczytania. Poza tym łatwiej jest radzić sobie z cudzysłowami, gdy używa się string.Format (), uh, format. Oto przyzwoita analiza konkatenacji ciągów.


1

Zawsze korzystałem z trasy string.Format (). Możliwość przechowywania formatów w zmiennych, takich jak przykład Nathana, jest wielką zaletą. W niektórych przypadkach mogę dołączyć zmienną, ale raz więcej niż 1 zmienna jest łączona i refaktoryzuję, aby użyć formatowania.


1

Aha, i tylko dla kompletności, poniższe jest kilka taktów szybciej niż zwykłe konkatenacje:

Console.WriteLine(String.Concat(p.FirstName," ",p.LastName));

1

Pierwszy (format) wygląda mi lepiej. Jest bardziej czytelny i nie tworzysz dodatkowych tymczasowych obiektów typu string.


1

Byłem ciekawy, jak wygląda StringBuilder z tymi testami. Wyniki poniżej ...

class Program {
   static void Main(string[] args) {

      var p = new { FirstName = "Bill", LastName = "Gates" };

      var tests = new[] {
         new { Name = "Concat", Action = new Action(delegate() { string x = p.FirstName + " " + p.LastName; }) },
         new { Name = "Format", Action = new Action(delegate() { string x = string.Format("{0} {1}", p.FirstName, p.LastName); }) },
         new { Name = "StringBuilder", Action = new Action(delegate() {
            StringBuilder sb = new StringBuilder();
            sb.Append(p.FirstName);
            sb.Append(" ");
            sb.Append(p.LastName);
            string x = sb.ToString();
         }) }
      };

      var Watch = new Stopwatch();
      foreach (var t in tests) {
         for (int i = 0; i < 5; i++) {
            Watch.Reset();
            long Elapsed = ElapsedTicks(t.Action, Watch, 10000);
            Console.WriteLine(string.Format("{0}: {1} ticks", t.Name, Elapsed.ToString()));
         }
      }
   }

   public static long ElapsedTicks(Action ActionDelg, Stopwatch Watch, int Iterations) {
      Watch.Start();
      for (int i = 0; i < Iterations; i++) {
         ActionDelg();
      }
      Watch.Stop();
      return Watch.ElapsedTicks / Iterations;
   }
}

Wyniki:

Concat: 406 ticków
Concat: 356 ticków
Concat: 411 tyknięć
Concat: 299 ticków
Concat: 266 ticków
Format: 5269 ticków
Format: 954 znaczniki
Format: 1004 tyknięcia
Format: 984 taktów
Format: 974 taktów
StringBuilder: 629 taktów
StringBuilder: 484 takty
StringBuilder: 482 takty
StringBuilder: 508 taktów
StringBuilder: 504 takty

1

Zgodnie z materiałem przygotowawczym MCSD, Microsoft sugeruje użycie operatora + w przypadku bardzo małej liczby konkatenacji (prawdopodobnie 2 do 4). Nadal nie jestem pewien dlaczego, ale warto to rozważyć.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.