String output: format veya concat C #?


178

Dize çıkarmak veya dizeleri bitirmek istediğinizi varsayalım. Aşağıdaki stillerden hangisini tercih edersiniz?

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

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

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

Biçimi kullanmayı mı tercih edersiniz yoksa sadece dizeleri mi birleştiriyorsunuz? Favorin ne? Bunlardan biri gözlerinize zarar veriyor mu?

Birini değil diğerini kullanmak için rasyonel argümanlarınız var mı?

İkincisi için giderdim.

Yanıtlar:


88

Bu kodu deneyin.

Kodunuzun biraz değiştirilmiş bir sürümüdür.
1. Konsol.WriteLine'ı muhtemelen ölçmeye çalıştığımdan daha az büyüklükte emir aldığından kaldırdım.
2. Döngüden önce Kronometreyi başlatıyorum ve hemen sonra durduruyorum, bu şekilde işlev örneğin 26.4 keneler çalıştırılırsa hassasiyeti kaybetmiyorum.
3. Sonucu bazı yinelemelere bölme şekliniz yanlıştı. 1000 milisaniye ve 100 milisaniyeniz varsa ne olacağını görün. Her iki durumda da, 1000000'e böldükten sonra 0 ms alırsınız.

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

Bunlar benim sonuçlarım:

1000000 x sonuç = string.Format ("{0} {1}", p.FirstName, p.LastName); aldı: 618ms - 2213706 keneler
1000000 x sonuç = (p.FirstName + "" + p.LastName); aldı: 166ms - 595610 keneler


1
Çok ilginç. Ortalama 224ms vs. 48ms, x4.66 iyileştirme, x3.72'nizden bile daha iyi. Ben string.Formatherhangi bir kompozit biçimlendirme özellikleri (yani sadece basit {0}) kullanmaz IL yeniden yazmak bir post-derleme aracı olup olmadığını ve bunları oldukça hızlı dize birleştirme ile değiştirin merak ediyorum . PostSharp gibi mevcut bir IL yeniden yazarıyla böyle bir başarı elde edilebileceğini merak ediyorum.
Allon Guralnek

31
Dizeler değiştirilemez, yani kodunuzda aynı küçük bellek parçası tekrar tekrar kullanılır. Aynı iki dizeyi bir araya getirmek ve aynı yeni dizeyi tekrar tekrar oluşturmak belleği etkilemez. Net sadece aynı hafıza referansını kullanacak kadar akıllıdır. Bu nedenle, kodunuz iki concat yöntemi arasındaki farkı gerçekten test etmez. Aşağıdaki cevabımdaki koda bakınız.
Ludington

1
Dürüst olmak gerekirse, her zaman bitiştiriyorum çünkü benim için okumak daha kolay ve daha hızlı vay :)
puretppc

Öyleyse hız diğerini seçmenin tek nedeni mi?
niico

158

Pek çok insanın en hızlı çalışan kodu hemen bulmak istediğine şaşırdım. BİR MİLYON yinelemenin işlenmesi bir saniyeden az sürüyorsa, bu son kullanıcı tarafından HİÇBİR ŞEKİLDE olacak mı? Pek olası değil.

Erken optimizasyon = FAIL.

Bu String.Formatseçenekle devam ediyorum , çünkü bu sadece mimari açıdan en mantıklı. Bir sorun haline gelene kadar performansı umursamıyorum (ve eğer olsaydı, kendime soracağım: Bir kerede bir milyon adı birleştirmem gerekiyor mu? Elbette hepsi ekrana sığmayacak ...)

Müşterinizin daha sonra görüntülenip görüntülenmeyeceğini yapılandıracak şekilde değiştirmek isteyip istemediğini düşünün "Firstname Lastname"veya "Lastname, Firstname."Biçim seçeneğiyle bu kolaydır - biçim dizesini değiştirin. Concat ile ekstra koda ihtiyacınız olacak. Tabii ki bu özel örnekte büyük bir şey gibi görünmüyor, ama tahmin ediyorum.


47
"Erken Optimizasyon == BAŞARISIZ" açısından iyi bir nokta, evet. Ancak, yürütme ayak izi için ödeme yapmaya başladığınızda (bir hizmet olarak bulut ve altyapı, herhangi biri?) Ve / veya bir şey için 1 milyon kullanıcıyı desteklemeye başladığınızda, bir istek üzerine tek bir kullanıcıya yanıt vermek soru değildir. Bir kullanıcıya bir talepte
bulunmanın maliyeti

23
Bu tamamen yanlış. Bir web geliştirme ortamında, dize oluşturma kodunuzun çoğu kez modelinizde, görünümlerinizde ve denetleyicilerinizde derin olacaktır ve sayfa yükleme başına on binlerce kez çağrılabilir. Dize oluşturma kodunu değerlendirmek için harcanan zamanın uzunluğunu% 50 azaltmak büyük bir kazanç olabilir.
Benjamin Sussman

2
Böyle bir soru sadece OP'nin bir örneğinde geçerli değildir. Cevap, insanların "telleri hangi şekilde birleştirmeliyim?" tüm kodlarını yazarken .
Phil Miller

6
@Benjamin: ... bu durumda profilinizi alıp darboğazınızı bulursunuz. Ama bahse girerim, bunu hiçbir yerden çekiyorsunuzdur; geçmişte bir dizi webapps yazıp profilledikten sonra, neredeyse her zaman darboğazları (sunucular tarafında) veritabanı sorguları olarak buldum .
BlueRaja - Danny Pflughoeft

2
Bu kesinlikle erken optimizasyon DEĞİLDİR. Oldukça yanlış. Dize performansı, çok fazla biçimlendirme ve Dize oluşturma yapıyorsanız, özellikle .NET'te kullanıcı arayüzlerini tamamen durdurabilir. ubiquity.acm.org/article.cfm?id=1513451
user99999991

54

Ah sevgili - diğer cevaplardan birini okuduktan sonra işlemleri sırasını tersine çevirmeye çalıştım - bu yüzden önce birleştirme, sonra String.Format gerçekleştirme ...

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

Bu nedenle işlemlerin sırası BÜYÜK bir fark yaratır veya daha doğrusu ilk işlem DAİMA çok daha yavaştır.

İşte operasyonların bir kereden fazla tamamlandığı bir çalışmanın sonuçları. Siparişleri değiştirmeyi denedim, ancak ilk sonuç göz ardı edildikten sonra işler genellikle aynı kuralları takip ediyor:

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

Aynı yöntemin sonraki çalışmalarını görebileceğiniz gibi (kodu 3 yönteme yeniden uyarladım) aşamalı olarak daha hızlıdır. En hızlı olanı Console.WriteLine (String.Concat (...)) yöntemi, ardından normal birleştirme ve sonra biçimlendirilmiş işlemler gibi görünüyor.

Başlangıçtaki ilk gecikme, ilk işlemden önce bir Console.Writeline ("Başlat!") Yerleştirerek Konsol Akışı'nın başlatılmasıdır.


2
Ardından Console.WriteLine'ı testlerinizden tamamen kaldırın. Sonuçları eğriltiyor!
CShark

Bu nedenle performans testlerini yaparken her zaman bir deneme veya "kontrol" senaryosuyla
başlıyorum

36

Dizeler değiştirilemez, yani kodunuzda aynı küçük bellek parçası tekrar tekrar kullanılır. Aynı iki dizeyi bir araya getirmek ve aynı yeni dizeyi tekrar tekrar oluşturmak belleği etkilemez. Net sadece aynı hafıza referansını kullanacak kadar akıllıdır. Bu nedenle, kodunuz iki concat yöntemi arasındaki farkı gerçekten test etmez.

Boyut için bunu deneyin:

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

Örnek Çıktı:

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
Cevaba

string.FormatBuradaki küçük performansın nasıl kullanıldığını görüyorum . Mimari olarak formatı daha kolay değiştirebileceğiniz anlamına gelir. Ama stringbuilder'ı gerçekten anlamıyorum. Buradaki diğer tüm iş parçacıkları, dizeleri birleştirmek yerine Stringbuilder kullanmanız gerektiğini söylüyor. Avantajı nedir? Açıkçası hız değil, bu kriter kanıtladığı gibi.
roryok

22

Yazık fakir çevirmenler

Eğer varsa bilmek saat keneler tasarrufu uygulamanız, daha sonra ince, İngilizce olarak kalacak. Bununla birlikte, birçok kültür, örneğin adlarda genellikle Soyadı Adı görür.

Bu nedenle string.Format(), özellikle uygulamanızın İngilizce'nin ilk dil olmadığı bir yere gitmesini istiyorsanız kullanın .


2
string.Format()Farklı kültürlerde nasıl farklı davranırdı? Hala adı ve soyadı yazdırmaz mı? Her iki durumda da farklı kültürü hesaba katmanız gerekecek gibi görünüyor. Burada bir şey eksikmiş gibi hissediyorum.
Broots Waymb

2
@DangerZone ile aynı fikirdeyim .. string.Format()adres için bir isim kullandığınızı nasıl anlarsınız? Eğer string.Format()takas {0} {1}kültüre dayalı, ben kırık ele alacak.
Alex McMillan

2
Jeremy'nin öne sürmeye çalıştığı nokta, farklı ülkeleri desteklemek için açıklanan senaryoda, biçim dizesinin kendisini bir dil kaynağına çıkarmak için uygun olabileceğine inanıyorum. Çoğu ülke için bu karakter dizisi "{0} {1}", ancak soyadı ilkinin tipik işlem olduğu ülkeler için (örneğin Macaristan, Hong Kong, Kamboçya, Çin, Japonya, Kore, Madagaskar, Tayvan, Vietnam ve Hindistan'ın bazı bölümlerinde) bu dize "{1} {0}" olur.
Richard J Foster

Aslında. Ya da daha ustaca, biçim dizesini kişinin niteliği olarak ekleyin. Örneğin, soyadımı ilk adımdan sonra almayı seviyorum, ancak meslektaşım Beng yok.
Jeremy McGee

14

İşte 100.000 yinelemeden elde ettiğim sonuçlar:

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

Ve işte tezgah kodu:

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

Kimin cevabını cevap olarak işaretleyeceğini bilmiyorum :)


Bu cevap için arka plan neden mavi?
user88637

@yossi mavi çünkü cevaplayıcı
askerle

9

Dizeleri birleştirmek böyle basit bir senaryoda iyidir - LastName, FirstName bile bundan daha karmaşık bir şeyle daha karmaşıktır. Biçimle, bir bakışta, kodu okurken dizenin son yapısının ne olacağını görebilirsiniz, birleştirme ile nihai sonucu hemen ayırt etmek neredeyse imkansız hale gelir (bunun gibi çok basit bir örnek hariç).

Uzun vadede bunun anlamı, dize biçiminizde bir değişiklik yapmak için geri döndüğünüzde, ya içeri girebilir ve biçim dizesinde birkaç ayar yapabilir ya da kaşınızı kırıp her şeyi hareket ettirmeye başlayabileceğinizdir. sorun getirme olasılığı daha yüksek olan metinle karıştırılmış mülk erişimcilerinin çeşitleri.

.NET 3.5 kullanıyorsanız, bunun gibi bir uzantı yöntemi kullanabilir ve aşağıdaki gibi manşet sözdiziminden kolay bir akış elde edebilirsiniz:

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

Son olarak, uygulamanız karmaşıklık arttıkça, dizelerinizi uygulamanızda düzgün bir şekilde korumak için, bunları yerelleştirmek için bir kaynak dosyasına veya yalnızca statik bir yardımcıya taşımak istediğinize karar verebilirsiniz. Sürekli olarak kullanılan biçimler varsa bunu başarmak çok daha kolay olacaktır ve kodunuz gibi bir şeyi kullanmak için oldukça basit bir şekilde yeniden düzenlenebilir

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

7

Çok basit manipülasyon için birleştirme kullanırım, ancak 2 veya 3 öğenin ötesine geçtiğinizde Format daha uygun IMO olur.

String.Format'ı tercih etmenin bir başka nedeni, .NET dizelerinin değişmez olması ve bu şekilde yapılması daha az geçici / ara kopya oluşturmasıdır.


6

Kısmen kendi tercihime dayanarak ilk cevabım için stil tercihini ve birleşmeyi tamamen anlasam da, kararımın bir kısmı birleşimin daha hızlı olacağı düşüncesine dayanıyordu. Yani, meraktan, test ettim ve sonuçlar özellikle küçük bir ip için şaşırtıcıydı.

Aşağıdaki kodu kullanarak:

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

Aşağıdaki sonuçları aldım:

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

Biçimlendirme yöntemini kullanmak 100 kat daha yavaştır !! Birleştirme bile 1 ms olarak kaydedilmedi, bu yüzden zamanlayıcı kenelerini de çıkardım.


2
Ancak elbette ölçüm almak için işlemi bir kereden fazla yapmalısınız.
erikkallen

2
Ve sorunun kapsamı dışında olduğu için Console.Writeline () çağrısını mı kaybettiniz?
Aidanapword

bir stringbuilder ile test yaptın mı? ;)
niico

6

C # 6.0'dan itibaren enterpolasyonlu dizeler bunu yapmak için kullanılabilir, bu da formatı daha da basitleştirir.

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

Enterpolasyonlu dize ifadesi, ifadeler içeren bir şablon dizesine benzer. Enterpolasyonlu dize ifadesi, içerilen ifadeleri ifadelerin sonuçlarının ToString temsilleriyle değiştirerek bir dize oluşturur.

Enterpolasyonlu dizeler String.Format ile benzer performansa sahiptir, ancak değerlerin ve ifadelerin satır içine eklenmesi nedeniyle gelişmiş okunabilirlik ve daha kısa sözdizimi.

Lütfen dize enterpolasyonu hakkındaki bu dotnetperls makalesine de bakın .

Dizelerinizi biçimlendirmek için varsayılan bir yol arıyorsanız, bu, okunabilirlik ve performans açısından anlamlıdır (mikrosaniyelerin özel kullanım durumunuzda bir fark yaratacağı durumlar hariç).


5

Temel dize birleştirme için genellikle ikinci stili kullanıyorum - okunması daha kolay ve daha basit. Ancak, daha karmaşık bir dize kombinasyonu yapıyorsam genellikle String.Format tercih ediyorum.

String.Format birçok tırnak ve artı üzerinde tasarruf sağlar ...

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

Sadece birkaç karakter kurtardı, ancak bence bu örnekte biçim onu ​​çok daha temiz hale getiriyor.


5

Perfmon ve CLR bellek sayaçlarını kullanarak belleğinizi izlemek daha iyi bir test olacaktır. Anladığım kadarıyla, stringleri birleştirmek yerine String.Format'ı kullanmak istediğiniz tüm neden, dizeler değişmez olduğundan, gereksiz yere çöp toplayıcıyı bir sonraki geçişte geri alınması gereken geçici dizelerle yüklediğinizdir.

StringBuilder ve String.Format, potansiyel olarak daha yavaş olmasına rağmen, bellekte daha verimlidir.

Dize birleştirmede bu kadar kötü olan nedir?


Katılıyorum; her dize işlemi dizenin yeni bir kopyasını oluşturur. Tüm bu bellek er ya da geç çöp toplayıcı tarafından geri kazanılacaktır. Bu nedenle, çok sayıda dizeyi ayırmak sizi daha sonra ısırmaya geri dönebilir.
Marnix van Valen

5

Genelde öncekini tercih ederim, özellikle dizeler uzadığında okunması çok daha kolay olabilir.

Diğer bir avantajı, sonuncusu Console.Write yöntemine geçmeden önce 2 dize oluşturma ifadeleri gerçekleştirdiği için performanstan birine inanıyorum. String.Format, inanıyorum kapakları altında bir StringBuilder kullanır, bu nedenle birden çok birleşim önlenir.

Bununla birlikte, String.Format'a (ve Console.Write gibi diğer yöntemler) ilettiğiniz parametreler değer türleri ise, kendi performans isabetlerini sağlayabilecek şekilde iletilmeden önce kutulara alınacaklarına dikkat edilmelidir. Bu konuda blog yazısı burada .


1
Bu blog yazısı şu anda: jeffbarnes.net/blog/post/2006/08/08/… . Düzenlemek için yetersiz temsilciden muzdaripim.
Richard Slater

5

19 Ağustos 2015'ten bir hafta sonra, bu soru tam olarak yedi (7) yaşında olacak. Şimdi bunu yapmanın daha iyi bir yolu var. Sadece dizeleri birleştirmeye kıyasla herhangi bir performans testi yapmadığım için sürdürülebilirlik açısından daha iyi (ancak bu günlerde önemli mi? Fark birkaç milisaniye?). C # 6.0 ile yapmanın yeni yolu :

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

Bu yeni özellik daha iyi , IMO ve aslında bizim durumumuzda daha iyi, çünkü değerleri bazı faktörlere bağlı olan sorgu dizeleri oluşturduğumuz kodlarımız var. 6 argümanın olduğu bir sorgu dizesi düşünün. Yani a yerine, örneğin:

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

gibi yazılabilir ve okumak daha kolaydır:

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

Gerçekten de, C # 6.0'ın yeni yolu en azından okunabilirlik açısından önceki alternatiflerden daha iyidir.
Philippe

Doğru. Ayrıca, hangi nesnenin hangi dizine (yer tutucu) gittiği konusunda endişelenmenize gerek olmadığından, nesneleri doğrudan istediğiniz yere koyacağınızdan da daha güvenlidir.
von v.

BTW, aslında Format'ı çağırır (en azından Roslyn ile).
Philippe

Bu posterin bahsettiği BTW'ye "string interpolation" denir ve bu dizinin başka bir yerinde ele alınır.
CShark

4
  1. Biçimlendirme bunu yapmanın “.NET” yoludur. Bazı yeniden düzenleme araçları (biri için Refactor!), Biçimlendirme stilini kullanmak için concat tarzı kodu yeniden düzenlemeyi önerecektir.
  2. Biçimlendirmenin derleyici için optimize edilmesi daha kolaydır (ikincisi muhtemelen hızlı olan 'Concat' yöntemini kullanmak için yeniden düzenlenecektir).
  3. Biçimlendirmenin okunması genellikle daha açıktır (özellikle “süslü” biçimlendirmeyle).
  4. Biçimlendirme, tüm değişkenler üzerinde '.ToString'e yapılan örtülü çağrılar anlamına gelir, bu da okunabilirlik açısından iyidir.
  5. “Etkili C #” a göre, .NET 'WriteLine' ve 'Format' uygulamaları bozulur, tüm değer türlerini (kötü olan) otomatik olarak kutulandırırlar. “Etkili C #”, IMHO'nun sahte olduğu '.ToString' çağrılarını açıkça gerçekleştirmenizi önerir ( Jeff'in gönderisine bakın )
  6. Şu anda, biçimlendirme türü ipuçları derleyici tarafından denetlenmez ve çalışma zamanı hatalarına neden olur. Ancak bu, gelecekteki sürümlerde değiştirilebilir.

4

Okunabilirliğe göre seçiyorum. Değişkenlerin etrafında metin olduğunda format seçeneğini tercih ederim. Bu örnekte:

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

değişken isimleri olmadan bile anlamı anlarsınız, oysa concat tırnak işaretleri ve + işaretleri ile karıştırılır ve gözlerimi karıştırır:

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

(Mike'ın örneğini ödünç aldım çünkü beğendim)

Biçim dizesi değişken adları olmadan çok şey ifade etmiyorsa, concat kullanmalıyım:

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

Biçim seçeneği, değişken adlarını okumamı ve bunları karşılık gelen sayılarla eşlememi sağlıyor. Concat seçeneği bunu gerektirmez. Hala tırnak işaretleri ve + işaretleri ile kafam karıştı, ama alternatif daha kötü. Yakut?

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

Performans açısından, biçim dize ayrıştırılması gerektirir, çünkü biçim seçeneği concat sonra yavaş olmasını bekliyoruz . Bu tür bir talimatı optimize etmek zorunda olduğumu hatırlamıyorum, ama yapsaydım ve stringgibi yöntemlere bakardım .Concat()Join()

Biçimle ilgili diğer avantaj, biçim dizesinin bir yapılandırma dosyasına yerleştirilebilmesidir. Hata mesajları ve kullanıcı arayüzü metni ile çok kullanışlı.


4

String.Format'ı kullanırdım, ancak kaynak dosyalardaki biçim dizesine sahip olurdum, böylece diğer diller için yerelleştirilebilir. Basit bir dize concat kullanmak bunu yapmanıza izin vermez. Açıkçası bu dizeyi yerelleştirmeniz gerekmiyorsa, bu düşünmek için bir neden değildir. Gerçekten ipin ne olduğuna bağlıdır.

Kullanıcıya gösterilecekse, String.Format'ı kullanırım, böylece ihtiyacım olursa yerelleştirebilirim - ve FxCop her ihtimale karşı benim için kontrol edecek :)

Sayılar veya başka dize olmayan şeyler (örneğin tarihler) içeriyorsa , biçimlendirme üzerinde daha fazla denetim sağladığından String.Format kullanırım .

SQL gibi bir sorgu oluşturmak için, Linq kullanırdım .

Dizeleri bir döngü içinde birleştirmek için, performans sorunlarını önlemek için StringBuilder kullanırdım.

Bazı çıktı için kullanıcı görmeyecek ve performansı etkilemezse ben yine de kullanma alışkanlığı ve ben sadece alıyorum çünkü String.Format kullanacağım :)


3

Okunması kolay bir şeyle uğraşıyorsanız (ve bu çoğu kod), operatörün aşırı yük sürümü UNLESS ile bağlı kalacağım:

  • Kodun milyonlarca kez yürütülmesi gerekiyor
  • Tonlarca concats yapıyorsunuz (4'ten fazla bir ton)
  • Kod, Kompakt Çerçeveye yöneliktir

Bu koşullardan en az ikisinde bunun yerine StringBuilder kullanırım.


3

Sonucu yerelleştirmek istiyorsanız, farklı doğal dillerdeki veriler aynı sırada bile bulunmayabileceğinden String.Format önemlidir.


2

Bence bu, çıktının ne kadar karmaşık olduğuna bağlı. Hangi senaryo o zaman en iyi çalışırsa onu seçme eğilimindeyim.

İşe göre doğru aleti seçin: D Hangisi en temiz görünüyor!


2

Ben ikincisini de tercih ediyorum ama şu anda bu pozisyonu destekleyecek rasyonel argümanlarım yok.


2

Güzel bir!

Sadece ekledim

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

Ve daha da hızlı (sanırım string.Concat her iki örnekte de çağrılır, ancak birincisi bir çeşit çeviri gerektirir).

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
Operatör tabanlı dize birleştirme işlemleri derleyici tarafından çağrılara çevrildiğinden tam olarak aynı zaman alır string.Concat(...). Derleme sırasında yapılır, böylece çalışma zamanı performansı üzerinde hiçbir etkisi yoktur. Testlerinizi birden çok kez yaparsanız veya daha büyük bir test numunesinde çalıştırırsanız, bunların aynı olduğunu görürsünüz.
Allon Guralnek

2

Buradaki cevapların her şeyi kapsadığını düşünmediğim için, burada küçük bir ekleme yapmak istiyorum.

Console.WriteLine(string format, params object[] pars)çağrıları string.Format. '+', Dize birleştirmeyi ifade eder. Bunun her zaman stille ilgili olduğunu düşünmüyorum; İçinde bulunduğum bağlama bağlı olarak iki stili karıştırma eğilimindeyim.

Kısa cevap

Karşılaştığınız karar dize tahsisi ile ilgilidir. Basitleştirmeye çalışacağım.

Söyle var

string s = a + "foo" + b;

Bunu yürütürseniz, aşağıdaki gibi değerlendirilecektir:

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

tmpburada gerçekten yerel bir değişken değil, ama JIT için geçici (IL yığınına itildi). Yığına bir dize basarsanız ( ldstrdeğişmez değerler için IL'de olduğu gibi ), yığındaki dize işaretçisine bir başvuru koyarsınız.

Her concatiki dizeyi de içeren herhangi bir dize başvurusu olmadığından, bu başvuruyu çağırdığınız an sorun olur. Bu, .NET'in yeni bir bellek bloğu tahsis etmesi ve ardından iki dizeyle doldurması gerektiği anlamına gelir. Bunun bir problem olmasının nedeni tahsinin nispeten pahalı olmasıdır.

Soru şu şekilde değişir: concatİşlem sayısını nasıl azaltabilirsiniz ?

Yani, kaba cevap: string.Format> 1 kat için, '+' 1 kat için iyi çalışır. Ve mikro performans optimizasyonları yapmayı umursamıyorsanız string.Format, genel durumda iyi çalışır.

Kültür hakkında bir not

Ve sonra kültür denen bir şey var ...

string.FormatCultureInfobiçimlendirmenizde kullanabilmenizi sağlar . Basit bir operatör '+' mevcut kültürü kullanır.

Bu özellikle dosya formatları ve f.ex yazıyorsanız önemli bir açıklamadır. doublebir dizeye 'eklediğiniz' değerler. Farklı makinelerde, string.Formataçık bir şekilde kullanmazsanız farklı dizelerle karşılaşabilirsiniz CultureInfo.

F.ex. bir '.' değiştirirseniz ne olacağını düşünün. ',' için virgülle ayrılmış değerler dosyanızı yazarken ... Flemenkçe'de ondalık ayırıcı virgüldür, dolayısıyla kullanıcı 'komik' bir sürpriz yaşayabilir.

Daha ayrıntılı cevap

Önceden dizenin tam boyutunu bilmiyorsanız, kullandığınız arabellekleri genel olarak konumlandırmak için böyle bir ilke kullanmak en iyisidir. Gevşek boşluk önce doldurulur, daha sonra veriler kopyalanır.

Büyüme, yeni bir bellek bloğunun tahsis edilmesi ve eski verilerin yeni tampona kopyalanması anlamına gelir. Daha sonra eski bellek bloğu serbest bırakılabilir. Bu noktada en alt çizgiyi elde edersiniz: büyüme pahalı bir işlemdir.

Bunu yapmanın en pratik yolu, bir genel yerleştirme politikası kullanmaktır. En yaygın politika, arabellekleri 2'nin güçlerinde aşırı konumlandırmaktır. Elbette, bundan biraz daha akıllıca yapmanız gerekir (çünkü 128 karaktere ihtiyacınız olduğunu biliyorsanız 1,2,4,8'den büyümek mantıklı değildir. ) Ama fotoğrafı sen aldın. Politika, yukarıda tarif ettiğim pahalı işlemlerin çoğuna ihtiyaç duymamanızı sağlar.

StringBuilder"Temel", temelde iki tamponun altında bulunan tamponu aşırı yükleyen bir sınıftır. başlık altında string.Formatkullanır StringBuilder.

Bu, kararınızı genel-konum-ve-ekleme (-multiple) (w / wo kültürü) arasında ya da sadece tahsis-ve-ekleme arasında temel bir değiş tokuş yapar.


1

Şahsen, kullandığınız her şey olarak ikincisi çıktı alınacağı sıradadır. İlk olarak, {0} ve {1} ile karıştırılması kolay olan uygun var ile eşleşmeniz gerekir.

En azından C ++ sprintf kadar kötü değil, burada değişken tipi yanlış alırsanız her şey patlayacak.

Ayrıca, ikincisi satır içi olduğundan ve tüm {0} şeyleri için herhangi bir arama ve değiştirme yapmak zorunda olmadığından, ikincisi daha hızlı olmalıdır ... ama emin değilim.


1

Aslında birincisini seviyorum çünkü metinle iç içe çok fazla değişken olduğunda bana okumak daha kolay görünüyor. Ayrıca, string.Format (), uh, formatı kullanırken tırnaklarla uğraşmak daha kolaydır. Dize birleştirme işleminin iyi analizi .


1

Her zaman string.Format () yoluna gittim. Nathan'ın örneği gibi değişkenlerde format depolayabilmek büyük bir avantajdır. Bazı durumlarda bir değişken ekleyebilirim ancak 1'den fazla değişken birleştirildiğinde biçimlendirmeyi kullanmak için yeniden düzenleme yapıyorum.


1

Oh, ve sadece bütünlük için, aşağıdakiler normal birleştirme işleminden daha hızlı bir kaç kenedir:

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

1

İlki (format) bana daha iyi görünüyor. Daha okunabilir ve ekstra geçici dize nesneleri oluşturmuyorsunuz.


1

StringBuilder'in bu testlerle nerede durduğunu merak ettim. Aşağıdaki sonuçlar ...

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

Sonuçlar:

Concat: 406 keneler
Concat: 356 keneler
Concat: 411 keneler
Concat: 299 keneler
Concat: 266 keneler
Biçim: 5269 keneler
Biçim: 954 keneler
Biçim: 1004 keneler
Biçim: 984 keneler
Biçim: 974 keneler
StringBuilder: 629 keneler
StringBuilder: 484 keneler
StringBuilder: 482 keneler
StringBuilder: 508 keneler
StringBuilder: 504 keneler

1

MCSD hazırlık malzemesine göre Microsoft, çok az sayıda birleştirme (muhtemelen 2 ila 4) ile uğraşırken + işlecini kullanmanızı önerir. Hala neden olduğundan emin değilim, ama dikkate alınması gereken bir şey.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.