String.Format ve string birleştirme kullanmak ne zaman daha iyidir?


120

Excel'e bir hücre girişi belirlemek için bir dizin değerini ayrıştıran küçük bir kod parçam var. Beni düşündürüyor ...

Arasındaki fark nedir

xlsSheet.Write("C" + rowIndex.ToString(), null, title);

ve

xlsSheet.Write(string.Format("C{0}", rowIndex), null, title);

Biri diğerinden "daha iyi" mi? Ve neden?



Yanıtlar:


115

C # 6'dan önce

Dürüst olmak gerekirse, ilk versiyonun daha basit olduğunu düşünüyorum - ancak bunu şu şekilde basitleştirebilirim:

xlsSheet.Write("C" + rowIndex, null, title);

Diğer cevaplar şüpheli olabilecek performans isabet bahsetmek, ancak minimal olacak dürüst olmak gerekirse hiç mevcutsa ve bu birleştirme sürümü biçim dizesi ayrıştırmak gerekmez -.

Biçim dizeleri yerelleştirme vb. Amaçlar için harikadır, ancak bu gibi bir durumda birleştirme daha basittir ve aynı şekilde çalışır.

C # 6 ile

Dize enterpolasyonu, birçok şeyi C # 6'da okumayı kolaylaştırır. Bu durumda, ikinci kodunuz şöyle olur:

xlsSheet.Write($"C{rowIndex}", null, title);

bu muhtemelen en iyi seçenek, IMO.



Biliyorum biliyorum.
Jest olarak

Jon. Her zaman Bay Richter hayranı oldum ve boks vb. İle ilgili rehberliği dini olarak takip ettim. Ancak, (eski) makalenizi okuduktan sonra, şimdi bir dönüşüm geçirdim. Teşekkür
stevethethread


4
Artık C # 6 mevcut olduğuna göre, daha kolay okunabilir olduğunu düşündüğüm şey için yeni dize enterpolasyon sözdizimini kullanabilirsiniz:xlsSheet.Write($"C{rowIndex}", null, title);
HotN

158

İlk tercihim (bir C ++ arka planından geliyor) String.Format içindi. Bunu daha sonra aşağıdaki nedenlerden dolayı düşürdüm:

  • Dize birleştirme muhtemelen "daha güvenlidir". Bir parametreyi kaldırmak veya parametre sırasını yanlışlıkla bozmak benim başıma geldi (ve birkaç başka geliştiricinin başına geldiğini gördüm). Derleyici, parametreleri biçim dizesine göre kontrol etmez ve sonunda bir çalışma zamanı hatasıyla karşılaşırsınız (yani, bir hatayı günlüğe kaydetme gibi belirsiz bir yöntemde bulunmayacak kadar şanslıysanız). Birleştirme ile bir parametrenin kaldırılması daha az hataya meyillidir. Hata olasılığının çok düşük olduğunu iddia edebilirsiniz , ancak olabilir .

- Dize birleştirme boş değerlere izin verir, izin String.Formatvermez. " s1 + null + s2" Yazmak bozulmaz, sadece boş değeri String.Empty olarak değerlendirir. Bu, kendi senaryonuza bağlı olabilir - boş bir FirstName'i sessizce görmezden gelmek yerine bir hata olmasını istediğiniz durumlar vardır. Ancak bu durumda bile ben şahsen, String.Format'tan aldığım standart ArgumentNullException yerine boş değerleri kontrol etmeyi ve belirli hatalar atmayı tercih ederim.

  • Dize birleştirme daha iyi performans gösterir. Yukarıdaki yazılardan bazıları zaten bundan bahsediyor (nedenini gerçekten açıklamadan, bu da beni bu yazıyı yazmaya karar verdi :).

Fikir, .NET derleyicisinin bu kod parçasını dönüştürmek için yeterince akıllı olmasıdır:

public static string Test(string s1, int i2, int i3, int i4, 
        string s5, string s6, float f7, float f8)
{
    return s1 + " " + i2 + i3 + i4 + " ddd " + s5 + s6 + f7 + f8;
}

buna:

public static string Test(string s1, int i2, int i3, int i4,
            string s5, string s6, float f7, float f8)
{
    return string.Concat(new object[] { s1, " ", i2, i3, i4, 
                    " ddd ", s5, s6, f7, f8 });
}

String.Concat başlığı altında neler olduğunu tahmin etmek kolaydır (Reflector kullanın). Dizideki nesneler ToString () aracılığıyla dizelerine dönüştürülür. Daha sonra toplam uzunluk hesaplanır ve yalnızca bir dizi ayrılır (toplam uzunlukla birlikte). Son olarak, her bir dizge elde edilen dizgeye wstrcpy aracılığıyla güvenli olmayan bir kod parçasıyla kopyalanır.

Sebepler String.Concatçok daha hızlı mı? Hepimiz ne String.Formatyaptığına bir göz atabiliriz - biçim dizgesini işlemek için gereken kod miktarına şaşıracaksınız. Üstelik (bellek tüketimiyle ilgili yorumlar gördüm) String.Formatdahili olarak bir StringBuilder kullanıyor. Bunu nasıl yapacağınız aşağıda açıklanmıştır:

StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));

Yani geçirilen her argüman için 8 karakter ayırır. Argüman tek basamaklı bir değer ise, o zaman çok kötü, boşa harcanan alanımız var. Argüman, üzerinde uzun bir metin döndüren özel bir nesneyse ToString(), bazı yeniden tahsisler gerekebilir (elbette en kötü durum senaryosu).

Bununla karşılaştırıldığında, birleştirme yalnızca nesne dizisinin alanını boşa harcar (çok fazla değil, bunun bir referans dizisi olduğu hesaba katılırsa). Biçim belirteçleri için ayrıştırma ve aracı StringBuilder yoktur. Kutulama / kutudan çıkarma ek yükü her iki yöntemde de mevcuttur.

String.Format'a gitmemin tek nedeni, yerelleştirmenin dahil olduğu zamandır. Kaynaklara biçim dizeleri koymak, kodla uğraşmadan farklı dilleri desteklemenize olanak tanır (biçimlendirilmiş değerlerin dile bağlı olarak sırasının değiştiği senaryoları düşünün, yani "{0} saat ve {1} dakika sonra" Japonca'da oldukça farklı görünebilir: ).


İlk (ve oldukça uzun) gönderimi özetlemek gerekirse:

  • benim için en iyi yol (performansa karşı sürdürülebilirlik / okunabilirlik açısından) herhangi bir ToString()çağrı yapmadan dize birleştirme kullanmaktır
  • performans peşindeyseniz, ToString()boks yapmaktan kaçınmak için aramaları kendiniz yapın (biraz okunabilirliğe karşı önyargılıyım) - sorunuzdaki ilk seçenekle aynı
  • kullanıcıya yerelleştirilmiş dizeler gösteriyorsanız (buradaki durum değil), String.Format()bir kenara sahiptir.

5
1) string.FormatReSharper kullanılırken "güvenlidir"; yani [yanlış] kullanılabilen diğer kodlar kadar güvenlidir. 2) string.Format yok bir "kasa" için izin null: string.Format("A{0}B", (string)null)"AB" sonuçlanır. 3) Bu performans düzeyini nadiren umursuyorum (ve bu amaçla, çekildiğim nadir bir günStringBuilder ) ...

Kabul ediyorum 2), gönderiyi düzenleyeceğim. Bunun 1.1'de güvenli olup olmadığı doğrulanamıyor, ancak en son çerçeve gerçekten boş güvenli.
Dan C.

String.Concat, işlenenlerden biri bir parametre veya değişken olmaktan ziyade dönüş değeri olan bir yöntem çağrısıysa hala kullanılıyor mu?
Richard Collette

2
@RichardCollette Evet, String.Concat yöntem çağrılarının dönüş değerlerini birleştirseniz bile kullanılır, örneğin Release modunda string s = "This " + MyMethod(arg) + " is a test";bir String.Concat()çağrıya derlenir .
Dan C.

Harika cevap; çok iyi yazılmış ve açıklanmıştır.
Frank V

6

Bence ilk seçenek daha okunabilir ve birincil endişeniz bu olmalı.

xlsSheet.Write("C" + rowIndex.ToString(), null, title);

string.Format, başlık altında bir StringBuilder kullanır ( reflektörle kontrol edin ), böylece önemli miktarda birleştirme yapmadığınız sürece herhangi bir performans avantajı sağlamaz. Senaryonuz için daha yavaş olacaktır, ancak gerçek şu ki, bu mikro performans optimizasyonu kararı çoğu zaman uygun değildir ve bir döngü içinde olmadığınız sürece kodunuzun okunabilirliğine gerçekten odaklanmalısınız.

Her iki durumda da, önce okunabilirlik için yazın ve ardından gerçekten performansla ilgili endişeleriniz olduğunu düşünüyorsanız etkin noktalarınızı belirlemek için bir performans profili oluşturucu kullanın.



5

Basit bir tek birleştirme olduğu basit bir durum için, karmaşıklığına değmeyeceğini hissediyorum string.Format(ve test etmedim, ancak bunun gibi basit bir durum için biraz daha yavaş string.Format olabileceğinden şüpheleniyorum , biçim dizesi ayrıştırmasıyla ve tüm). Jon Skeet gibi ben de açık bir şekilde aramamayı tercih ediyorum .ToString(), çünkü bu dolaylı olarak string.Concat(string, object)aşırı yükleme ile yapılacaktır ve bence kod daha temiz görünüyor ve onsuz okunması daha kolay.

Ama birkaç bitiştirmeden daha fazlası için (kaçı özneldir) kesinlikle tercih ederim string.Format. Bir noktada, hem okunabilirliğin hem de performansın birleştirme ile gereksiz yere zarar gördüğünü düşünüyorum.

Biçim dizesi için çok sayıda parametre varsa (yine, "birçok" özneldir), genellikle hangi değerin hangi parametreye gittiğinin izini kaybetmemek için, değiştirilen bağımsız değişkenlere açıklamalı dizinleri eklemeyi tercih ederim. Yapmacık bir örnek:

Console.WriteLine(
    "Dear {0} {1},\n\n" +

    "Our records indicate that your {2}, \"{3}\", is due for {4} {5} shots.\n" +
    "Please call our office at 1-900-382-5633 to make an appointment.\n\n" +

    "Thank you,\n" +
    "Eastern Veterinary",

    /*0*/client.Title,
    /*1*/client.LastName,
    /*2*/client.Pet.Animal,
    /*3*/client.Pet.Name,
    /*4*/client.Pet.Gender == Gender.Male ? "his" : "her",
    /*5*/client.Pet.Schedule[0]
);

Güncelleme

Bana, verdiğim örnek biraz kafa karıştırıcı geliyor, çünkü hem birleştirme hem destring.Format burada kullandım. Ve evet, mantıksal ve sözcüksel olarak, yaptığım şey bu. Ancak birleştirmelerin tümü , hepsi dizgi değişmezleri olduğundan , derleyici 1 tarafından optimize edilecektir . Yani çalışma zamanında tek bir dize olacaktır. Bu yüzden sanırım çalışma zamanında birçok birleştirmeden kaçınmayı tercih ettiğimi söylemeliyim .

Elbette, C # 5 veya daha eski sürümleri kullanmaya devam etmediğiniz sürece bu konunun çoğu güncel değil. Şimdi , okunabilirlik açısından neredeyse tüm durumlarda çok daha üstün olan dizeleri enterpolasyonlu hale getirdik string.Format. Bu günlerde, bir değeri doğrudan bir dizgenin başına veya sonuna birleştirmediğim sürece, neredeyse her zaman dize enterpolasyonu kullanıyorum. Bugün, daha önceki örneğimi şöyle yazacağım:

Console.WriteLine(
    $"Dear {client.Title} {client.LastName},\n\n" +

    $"Our records indicate that your {client.Pet.Animal}, \"{client.Pet.Name}\", " +
    $"is due for {(client.Pet.Gender == Gender.Male ? "his" : "her")} " +
    $"{client.Pet.Schedule[0]} shots.\n" +
    "Please call our office at 1-900-382-5633 to make an appointment.\n\n" +

    "Thank you,\n" +
    "Eastern Veterinary"
);

Bu şekilde derleme zamanı birleştirmeyi kaybedersiniz. Enterpolasyonlu her dize string.Format, derleyici tarafından bir çağrıya dönüştürülür ve sonuçları çalışma zamanında birleştirilir. Bu, okunabilirlik için çalışma zamanı performansından ödün verildiği anlamına gelir. Çoğu zaman, bu değerli bir fedakarlıktır, çünkü çalışma süresi cezası ihmal edilebilir düzeydedir. Bununla birlikte, performans açısından kritik kodda, farklı çözümlerin profilini çıkarmanız gerekebilir.


1 Bunu C # spesifikasyonunda görebilirsiniz :

... aşağıdaki yapılara sabit ifadelerde izin verilir:

...

  • Önceden tanımlanmış + ... ikili operatör ...

Ayrıca küçük bir kodla da doğrulayabilirsiniz:

const string s =
    "This compiles successfully, " +
    "and you can see that it will " +
    "all be one string (named `s`) " +
    "at run time";

1
fyi, tüm birleştirmeler yerine @ "... multi line string" kullanabilirsiniz.
Aaron Palmer

Evet, ama sonra dizinizi sola yaslamalısınız. @ dizeleri, tırnaklar arasındaki tüm yeni satırları ve sekme karakterlerini içerir.
P Daddy

Bunun eski olduğunu biliyorum, ancak bu, biçim dizesini bir resx dosyasına koy diyebileceğim bir durum.
Andy

2
Vay canına, herkes konunun özü yerine dizgiye odaklandı.
P Baba

heheh - Ben sadece dizininizdeki String birleşimini fark ettim. String.Format()
Kristopher

3

Eğer dizginiz birleştirilen birçok değişkenle daha karmaşık olsaydı, o zaman ben string.Format () 'ı seçerdim. Ancak sizin durumunuzda dizgi boyutu ve birleştirilen değişkenlerin sayısı için, ilk sürümünüzle giderdim, daha sade .


3

String.Format'a (Reflector kullanarak) bir göz attım ve aslında bir StringBuilder oluşturuyor ve ardından AppendFormat'ı çağırıyor. Bu yüzden çoklu karıştırma için concat'tan daha hızlıdır. En hızlı (inanıyorum) bir StringBuilder oluşturmak ve Append'e manuel olarak çağrı yapmak olacaktır. Tabii ki "çok" sayısı tahmin edilebilir. + (Aslında & çünkü çoğunlukla VB programcısıyım) örneğiniz kadar basit bir şey için kullanırım. Daha karmaşık hale geldikçe String.Format kullanıyorum. Eğer LOTS değişken varsa, o zaman bir StringBuilder ve Append'e giderdim, örneğin, kod oluşturan bir kodumuz var, orada üretilen kodun bir satırını çıkarmak için bir satır gerçek kod kullanıyorum.

Bu işlemlerin her biri için kaç dizginin yaratılacağına dair bazı spekülasyonlar var gibi görünüyor, bu yüzden birkaç basit örnek alalım.

"C" + rowIndex.ToString();

"C" zaten bir dizedir.
rowIndex.ToString () başka bir dize oluşturur. (@manohard - rowIndex'in kutulaması yapılmaz)
Sonra son dizeyi elde ederiz.
Örnek alırsak

String.Format("C(0)",rowIndex);

daha sonra
rowIndex işleve iletilmek üzere kutulanırken "C {0}" var. Yeni bir dize
oluşturucu oluşturulur
Dize oluşturucuda AppendFormat çağrılır - AppendFormat işlevlerinin ayrıntılarını bilmiyorum ama öyle olduğunu varsayalım son derece verimli olsa da, kutulu rowIndex'i bir dizeye dönüştürmek zorunda kalacaktır.
Ardından dizgi oluşturucuyu yeni bir dizeye dönüştürün.
StringBuilders'ın anlamsız bellek kopyalarının gerçekleşmesini önlemeye çalıştığını biliyorum, ancak String.Format düz birleştirme ile karşılaştırıldığında hala fazladan ek yük ile sonuçlanıyor.

Şimdi birkaç dizeyle bir örnek alırsak

"a" + rowIndex.ToString() + "b" + colIndex.ToString() + "c" + zIndex.ToString();

Başlamak için 6 dizimiz var ve bu tüm durumlar için aynı olacak.
Birleştirmeyi kullanarak ayrıca 4 ara diziye ve nihai sonuca sahibiz. String, Format (veya StringBuilder) kullanılarak elenen ara sonuçlardır.
Her bir ara dizeyi oluşturmak için öncekinin yeni bir bellek konumuna kopyalanması gerektiğini unutmayın, potansiyel olarak yavaş olan sadece bellek ayırma değildir.


4
Kusur aramak. "A" + ... + "b" + ... + "c" + ... içinde, aslında 4 ara dizeye sahip olmayacaksınız. Derleyici, String.Concat (params string [] değerleri) statik yöntemine bir çağrı oluşturur ve hepsi bir kerede birleştirilir. Yine de okunabilirlik uğruna string.Format'ı tercih ederim.
P Daddy

2

String.Format'ı seviyorum çünkü biçimlendirilmiş metninizi satır içi birleştirmeden çok daha kolay takip edebilir ve okuyabilir, ayrıca parametrelerinizi biçimlendirmenize izin veren çok daha esnektir, ancak sizinki gibi kısa kullanımlar için birleştirme konusunda bir sorun görmüyorum.

Döngülerin veya büyük dizelerin içindeki birleştirmeler için her zaman StringBuilder sınıfını kullanmayı denemelisiniz.


2

Bu örnek muhtemelen bir farkı fark etmek için çok önemsizdir. Aslında, çoğu durumda derleyicinin herhangi bir farkı optimize edebileceğini düşünüyorum.

Ancak, tahmin etmem gerekirse string.Format()daha karmaşık senaryolar için bir avantaj sağlardım. Ancak bu, herhangi bir gerçek veriye dayalı değil, birden fazla değişmez dizge üretmek yerine bir tampon kullanarak daha iyi bir iş çıkarması muhtemel olan daha içgüdüsel bir duygu.


1

Yukarıdaki birçok noktaya katılıyorum, belirtilmesi gerektiğine inandığım bir başka nokta da kod sürdürülebilirliği. string.Format, kodun daha kolay değiştirilmesini sağlar.

Yani bir mesajım var "The user is not authorized for location " + locationveya "The User is not authorized for location {0}"

mesajı değiştirmek istersem: location + " does not allow this User Access"veya "{0} does not allow this User Access"

ile string.Format tek yapmam gereken dizeyi değiştirmek. birleştirme için bu mesajı değiştirmem gerekiyor

birden fazla yerde kullanılırsa zaman tasarrufu sağlayabilir.


1

String.format'ın daha hızlı olduğu izlenimine kapıldım, bu testte 3 kat daha yavaş görünüyor

string concat = "";
        System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch    ();
        sw1.Start();
        for (int i = 0; i < 10000000; i++)
        {
            concat = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}","1", "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" , "10" , i);
        }
        sw1.Stop();
        Response.Write("format: "  + sw1.ElapsedMilliseconds.ToString());
        System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch();
        sw2.Start();
        for (int i = 0; i < 10000000; i++)
        {
            concat = "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10" + i;
        }
        sw2.Stop();

string.format 4,6 saniye sürdü ve '+' kullanıldığında 1,6 saniye sürdü.


7
Derleyici "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10"tek bir dizge olarak tanır , böylece satır etkili bir şekilde bir önceki satırdan "12345678910" + idaha hızlı hale gelirstring.Format(...)
wertzui

0

string.Format, biçim şablonu ("C {0}") bir yapılandırma dosyasında (Web.config / App.config gibi) depolandığında muhtemelen daha iyi bir seçimdir


0

String.Format, StringBuilder ve string birleştirme dahil olmak üzere çeşitli dize yöntemlerinin biraz profilini çıkardım. Dize birleştirme, dizeleri oluşturmanın diğer yöntemlerinden neredeyse her zaman daha iyi performans gösterdi. Öyleyse, performans anahtarsa, o zaman daha iyidir. Bununla birlikte, performans kritik değilse kişisel olarak string.Format'ı kodda takip etmesi daha kolay buluyorum. (Ancak bu öznel bir neden) StringBuilder, ancak bellek kullanımı açısından muhtemelen en verimli olanıdır.


0

Performans açısından String.Format'ı tercih ediyorum


-1

Dize birleştirme, String.Format ile karşılaştırıldığında daha fazla bellek alır. Dolayısıyla, dizeleri birleştirmenin en iyi yolu String.Format veya System.Text.StringBuilder Object kullanmaktır.

İlk durumu ele alalım: "C" + rowIndex.ToString () RowIndex'in bir değer türü olduğunu varsayalım, bu nedenle ToString () yönteminin değeri String'e dönüştürmek için Box'a ihtiyacı vardır ve sonra CLR, her iki değeri de içeren yeni dize için bellek oluşturur.

String.Format'ın nesne parametresini beklediği ve rowIndex'i bir nesne olarak aldığı ve onu dahili olarak dizgeye dönüştürdüğü durumlarda Boxing olacaktır, ancak bu içseldir ve aynı zamanda ilk durumda olduğu kadar fazla bellek kullanmayacaktır.

Kısa dizeler için sanırım o kadar önemli olmayacak ...

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.