Kapan lebih baik menggunakan String.Format vs string concatenation?

120

Saya punya sepotong kecil kode yang mengurai nilai indeks untuk menentukan input sel ke dalam Excel. Itu membuatku berpikir ...

Apa perbedaannya

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

dan

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

Apakah yang satu "lebih baik" dari yang lain? Dan mengapa?

Gavin Miller
sumber
4
Ini mirip dengan stackoverflow.com/questions/16432/…
Jonathan S.Nov
kemungkinan duplikat dari Why use String.Format?
Druid

Jawaban:

115

Sebelum C # 6

Sejujurnya, menurut saya versi pertama lebih sederhana - meskipun saya menyederhanakannya menjadi:

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

Saya menduga jawaban lain mungkin berbicara tentang kinerja hit, tetapi jujur ​​saja itu akan minimal jika ada - dan versi penggabungan ini tidak perlu mengurai string format.

String format sangat bagus untuk tujuan pelokalan dll, tetapi dalam kasus seperti penggabungan ini lebih sederhana dan berfungsi dengan baik.

Dengan C # 6

Interpolasi string membuat banyak hal lebih mudah dibaca di C # 6. Dalam hal ini, kode kedua Anda menjadi:

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

yang mungkin merupakan pilihan terbaik, IMO.

Jon Skeet
sumber
Saya tahu saya tahu. Itu dibuat bercanda (telah membaca link btw sebelumnya, yang merupakan bacaan yang bagus)
nawfal
Jon. Saya selalu menjadi penggemar Mr. Richter, dan telah mengikuti panduan tentang tinju, dll. Dengan religius. Namun, setelah membaca artikel (lama) Anda, saya sekarang menjadi mualaf. Terima kasih
stevethread
3
@ mbomb007: Sekarang di codeblog.jonskeet.uk/2008/10/08/…
Jon Skeet
4
Sekarang C # 6 tersedia, Anda dapat menggunakan sintaks interpolasi string baru untuk apa yang menurut saya lebih mudah dibaca:xlsSheet.Write($"C{rowIndex}", null, title);
HotN
158

Preferensi awal saya (berasal dari latar belakang C ++) adalah untuk String.Format. Saya menjatuhkan ini nanti karena alasan berikut:

  • Rangkaian string bisa dibilang "lebih aman". Itu terjadi pada saya (dan saya telah melihat itu terjadi pada beberapa pengembang lain) untuk menghapus parameter, atau mengacaukan urutan parameter karena kesalahan. Kompiler tidak akan memeriksa parameter terhadap format string dan Anda akan mendapatkan error runtime (yaitu, jika Anda cukup beruntung untuk tidak memilikinya dalam metode yang tidak jelas, seperti mencatat error). Dengan penggabungan, menghapus parameter tidak terlalu rentan terhadap kesalahan. Anda bisa membantah bahwa kemungkinan kesalahan sangat kecil, tetapi itu mungkin saja terjadi.

- Penggabungan string memungkinkan untuk nilai null, String.Formattidak. Menulis " s1 + null + s2" tidak merusak, itu hanya memperlakukan nilai null sebagai String.Empty. Nah, ini mungkin tergantung pada skenario spesifik Anda - ada kasus di mana Anda menginginkan kesalahan alih-alih mengabaikan Nama Depan null secara diam-diam. Namun bahkan dalam situasi ini saya pribadi lebih suka memeriksa nulls sendiri dan melemparkan kesalahan tertentu daripada ArgumentNullException standar yang saya dapatkan dari String.Format.

  • Rangkaian string bekerja lebih baik. Beberapa postingan di atas sudah menyebutkan ini (tanpa benar-benar menjelaskan mengapa, yang menentukan saya menulis postingan ini :).

Idenya adalah kompiler .NET cukup pintar untuk mengonversi potongan kode ini:

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

untuk ini:

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

Apa yang terjadi di bawah tenda String.Concat mudah ditebak (gunakan Reflector). Objek dalam array diubah menjadi string mereka melalui ToString (). Kemudian total panjang dihitung dan hanya satu string yang dialokasikan (dengan total panjang). Akhirnya, setiap string disalin ke string yang dihasilkan melalui wstrcpy di beberapa bagian kode yang tidak aman.

Alasannya String.Concatjauh lebih cepat? Nah, kita semua dapat melihat apa yang String.Formatsedang dilakukan - Anda akan terkejut dengan jumlah kode yang diperlukan untuk memproses format string. Di atas ini (saya telah melihat komentar tentang konsumsi memori), String.Formatmenggunakan StringBuilder secara internal. Begini caranya:

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

Jadi untuk setiap argumen yang lolos, itu cadangan 8 karakter. Jika argumennya adalah nilai satu digit, maka sayang sekali, kita memiliki beberapa ruang yang terbuang. Jika argumennya adalah objek khusus yang mengembalikan beberapa teks panjangToString() , maka mungkin ada beberapa realokasi yang diperlukan (skenario terburuk, tentu saja).

Dibandingkan dengan ini, penggabungan hanya membuang-buang ruang dari larik objek (tidak terlalu banyak, mengingat itu adalah larik referensi). Tidak ada parsing untuk penentu format dan tidak ada StringBuilder perantara. Overhead tinju / unboxing ada di kedua metode.

Satu-satunya alasan saya menggunakan String.Format adalah ketika pelokalan dilibatkan. Menempatkan string format dalam sumber daya memungkinkan Anda untuk mendukung bahasa yang berbeda tanpa mengotak-atik kode (pikirkan tentang skenario di mana nilai yang diformat berubah urutan bergantung pada bahasa, yaitu "setelah {0} jam dan {1} menit" mungkin terlihat sangat berbeda dalam bahasa Jepang: ).


Untuk meringkas posting pertama saya (dan cukup panjang):

  • cara terbaik (dalam hal kinerja vs. pemeliharaan / keterbacaan) bagi saya adalah menggunakan penggabungan string, tanpa ToString()panggilan apa pun
  • jika Anda mengejar kinerja, lakukan ToString()panggilan sendiri untuk menghindari tinju (saya agak bias terhadap keterbacaan) - sama seperti opsi pertama dalam pertanyaan Anda
  • jika Anda menampilkan string yang dilokalkan kepada pengguna (bukan kasusnya di sini), String.Format()memiliki keunggulan.
Dan C.
sumber
5
1) string.Format"aman" saat menggunakan ReSharper; artinya, ini seaman kode lain yang dapat digunakan [secara tidak benar]. 2) string.Format tidak memungkinkan untuk "aman" null: string.Format("A{0}B", (string)null)hasil dalam "AB". 3) Saya jarang peduli dengan tingkat kinerja ini (dan untuk itu, ini adalah hari yang langka ketika saya mundur StringBuilder) ...
Setuju pada 2), saya akan mengedit posting. Tidak dapat memverifikasi apakah ini aman di 1.1, tetapi kerangka kerja terbaru memang tidak aman.
Dan C.
Apakah string.Concat masih digunakan jika salah satu operannya adalah pemanggilan metode dengan nilai kembalian, bukan sebagai parameter atau variabel?
Richard Collette
2
@RichardCollette Ya, String.Concat digunakan meskipun Anda menggabungkan nilai kembalian dari panggilan metode, misalnya string s = "This " + MyMethod(arg) + " is a test";dikompilasi ke String.Concat()panggilan dalam mode Rilis.
Dan C.22
Jawaban yang fantastis; ditulis dan dijelaskan dengan sangat baik.
Frank V
6

Saya pikir opsi pertama lebih mudah dibaca dan itu harus menjadi perhatian utama Anda.

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

string.Format menggunakan StringBuilder di bawah kapnya (periksa dengan reflektor ) sehingga tidak akan memiliki manfaat kinerja apa pun kecuali Anda melakukan penggabungan dalam jumlah yang signifikan. Ini akan lebih lambat untuk skenario Anda tetapi kenyataannya adalah keputusan pengoptimalan kinerja mikro ini sebagian besar waktu tidak sesuai dan Anda harus benar-benar fokus pada keterbacaan kode Anda kecuali Anda berada dalam lingkaran.

Apa pun caranya, tulis agar terbaca terlebih dahulu, lalu gunakan profiler kinerja untuk mengidentifikasi hotspot Anda jika Anda merasa benar-benar memiliki masalah kinerja.

Martin Hollingsworth
sumber
5

Untuk kasus sederhana di mana itu adalah rangkaian tunggal sederhana, saya merasa itu tidak sebanding dengan kerumitannya string.Format(dan saya belum menguji, tetapi saya curiga untuk kasus sederhana seperti ini, string.Format mungkin sedikit lebih lambat, apa dengan format string parsing dan semua). Seperti Jon Skeet, saya lebih suka untuk tidak menelepon secara eksplisit .ToString(), karena itu akan dilakukan secara implisit oleh string.Concat(string, object)kelebihan beban, dan saya pikir kodenya terlihat lebih bersih dan lebih mudah dibaca tanpanya.

Tetapi untuk lebih dari beberapa rangkaian (berapa banyak yang subjektif), saya pasti lebih suka string.Format. Pada titik tertentu saya berpikir bahwa keterbacaan dan kinerja menderita yang tidak perlu dengan penggabungan.

Jika ada banyak parameter untuk format string (sekali lagi, "banyak" adalah subjektif), saya biasanya lebih suka menyertakan indeks yang diberi komentar pada argumen pengganti, jangan sampai saya kehilangan jejak nilai mana yang menuju ke parameter mana. Contoh yang dibuat-buat:

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

Memperbarui

Itu terjadi kepada saya bahwa contoh saya berikan adalah sedikit membingungkan, karena tampaknya bahwa saya telah digunakan baik Rangkaian dan string.Formatdi sini. Dan ya, secara logis dan leksikal, itulah yang telah saya lakukan. Tapi penggabungan semua akan dioptimalkan oleh kompiler 1 , karena semuanya adalah string literal. Jadi pada saat run-time, akan ada satu string. Jadi saya rasa saya harus mengatakan bahwa saya lebih suka menghindari banyak penggabungan pada waktu berjalan .

Tentu saja, sebagian besar topik ini sudah kedaluwarsa, kecuali Anda masih terjebak menggunakan C # 5 atau yang lebih lama. Sekarang kami memiliki string yang diinterpolasi , yang agar terbaca, jauh lebih unggul string.Format, di hampir semua kasus. Hari-hari ini, kecuali saya hanya menggabungkan nilai langsung ke awal atau akhir string literal, saya hampir selalu menggunakan interpolasi string. Hari ini, saya akan menulis contoh saya sebelumnya seperti ini:

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

Anda kehilangan penggabungan waktu kompilasi dengan cara ini. Setiap string yang diinterpolasi diubah menjadi panggilan string.Formatoleh kompiler, dan hasilnya digabungkan pada waktu proses. Itu berarti ini adalah pengorbanan kinerja run-time untuk keterbacaan. Seringkali, ini adalah pengorbanan yang berharga, karena penalti run-time dapat diabaikan. Namun, dalam kode kinerja kritis, Anda mungkin perlu membuat profil solusi yang berbeda.


1 Anda dapat melihat ini di spesifikasi C # :

... konstruksi berikut diizinkan dalam ekspresi konstan:

...

  • Operator biner + ... standar ...

Anda juga dapat memverifikasinya dengan sedikit kode:

const string s =
    "This compiles successfully, " +
    "and you can see that it will " +
    "all be one string (named `s`) " +
    "at run time";
P Ayah
sumber
1
Sebagai gantinya, Anda dapat menggunakan @ "... string multi baris" sebagai ganti semua rangkaian.
Aaron Palmer
Ya, tapi kemudian Anda harus membenarkan string Anda. @ string menyertakan semua baris baru dan karakter tab di antara tanda kutip.
P Daddy
Saya tahu ini sudah lama, tetapi ini adalah kasus di mana saya akan mengatakan meletakkan string format dalam file resx.
Andy
2
Wow, semua orang fokus pada string literal, bukan inti masalahnya.
P Daddy
heheh - Saya baru saja memperhatikan rangkaian String di dalam AndaString.Format()
Kristopher
3

Jika string Anda lebih kompleks dengan banyak variabel yang digabungkan, maka saya akan memilih string.Format (). Tetapi untuk ukuran string dan jumlah variabel yang digabungkan dalam kasus Anda, saya akan menggunakan versi pertama Anda, ini lebih sederhana .

Aaron Palmer
sumber
3

Saya telah melihat String.Format (menggunakan Reflector) dan itu benar-benar membuat StringBuilder kemudian memanggil AppendFormat di atasnya. Jadi lebih cepat daripada concat untuk beberapa pengadukan. Paling cepat (saya percaya) akan membuat StringBuilder dan melakukan panggilan ke Tambahkan secara manual. Tentu saja jumlah "banyak" untuk menebak. Saya akan menggunakan + (sebenarnya & karena saya kebanyakan programmer VB) untuk sesuatu yang sederhana seperti contoh Anda. Karena semakin kompleks, saya menggunakan String.Format. Jika ada BANYAK variabel maka saya akan menggunakan StringBuilder dan Tambahkan, misalnya, kami memiliki kode yang membangun kode, di sana saya menggunakan satu baris kode aktual untuk menghasilkan satu baris kode yang dihasilkan.

Tampaknya ada beberapa spekulasi tentang berapa banyak string yang dibuat untuk masing-masing operasi ini, jadi mari kita ambil beberapa contoh sederhana.

"C" + rowIndex.ToString();

"C" sudah menjadi string.
rowIndex.ToString () membuat string lain. (@manohard - tidak ada tinju dari rowIndex yang akan terjadi)
Kemudian kita mendapatkan string terakhir.
Jika kita ambil contoh

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

maka kita memiliki "C {0}" sebagai string
rowIndex mendapat kotak untuk diteruskan ke fungsi
Sebuah
pembuat string baru dibuat AppendFormat dipanggil pada pembuat string - Saya tidak tahu detail bagaimana fungsi AppendFormat tapi mari kita asumsikan itu sangat efisien, masih harus mengubah rowIndex dalam kotak menjadi string.
Kemudian ubah pembuat string menjadi string baru.
Saya tahu bahwa StringBuilders berusaha mencegah salinan memori yang tidak berguna terjadi tetapi String.Format masih berakhir dengan overhead tambahan dibandingkan dengan penggabungan biasa.

Jika sekarang kita ambil contoh dengan beberapa string lagi

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

kami memiliki 6 string untuk memulai, yang akan sama untuk semua kasus.
Menggunakan penggabungan kami juga memiliki 4 string perantara ditambah hasil akhir. Ini adalah hasil antara yang dihilangkan dengan menggunakan String, Format (atau StringBuilder).
Ingatlah bahwa untuk membuat setiap string perantara, string sebelumnya harus disalin ke lokasi memori baru, bukan hanya alokasi memori yang berpotensi lambat.

pipTheGeek
sumber
4
Nitpick. Dalam "a" + ... + "b" + ... + "c" + ..., Anda sebenarnya tidak memiliki 4 string perantara. Kompilator akan menghasilkan panggilan ke metode statis String.Concat (params string [] values), dan semuanya akan digabungkan sekaligus. Saya masih lebih suka string. Namun, format demi keterbacaan.
P Daddy
2

Saya suka String.Format karena dapat membuat teks Anda yang diformat lebih mudah diikuti dan dibaca daripada penggabungan sebaris, juga jauh lebih fleksibel memungkinkan Anda untuk memformat parameter Anda, namun untuk penggunaan singkat seperti milik Anda, saya tidak melihat masalah tentang penggabungan.

Untuk penggabungan di dalam loop atau dalam string besar, Anda harus selalu mencoba menggunakan kelas StringBuilder.

CMS
sumber
2

Contoh itu mungkin terlalu sepele untuk melihat perbedaannya. Faktanya, saya pikir dalam banyak kasus kompiler dapat mengoptimalkan perbedaan apa pun sama sekali.

Namun, jika saya harus menebak, saya akan memberikan string.Format()keunggulan untuk skenario yang lebih rumit. Tapi itu lebih merupakan firasat bahwa kemungkinan akan melakukan pekerjaan yang lebih baik dengan menggunakan buffer daripada menghasilkan beberapa string yang tidak dapat diubah, dan tidak berdasarkan data nyata apa pun.

Joel Coehoorn
sumber
1

Saya setuju dengan banyak poin di atas, poin lain yang saya percaya harus disebutkan adalah pemeliharaan kode. string.Format memungkinkan untuk lebih mudah mengubah kode.

yaitu saya memiliki pesan "The user is not authorized for location " + locationatau "The User is not authorized for location {0}"

jika saya ingin mengubah pesan menjadi: location + " does not allow this User Access"atau "{0} does not allow this User Access"

dengan string.Format yang harus saya lakukan adalah mengubah string. untuk penggabungan saya harus mengubah pesan itu

jika digunakan di banyak tempat bisa menghemat jatah waktu.

deankarn
sumber
1

Saya mendapat kesan bahwa string.format lebih cepat tampaknya 3 x lebih lambat dalam pengujian ini

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 membutuhkan waktu 4,6 detik dan ketika menggunakan '+' dibutuhkan 1,6 detik.

Kitemark76
sumber
7
Kompilator mengenali "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10"sebagai satu string literal, sehingga baris secara efektif menjadi "12345678910" + iyang lebih cepat dari sebelumnyastring.Format(...)
wertzui
0

string.Format mungkin merupakan pilihan yang lebih baik saat template format ("C {0}") disimpan dalam file konfigurasi (seperti Web.config / App.config)

Andrei Rînea
sumber
0

Saya melakukan sedikit pembuatan profil dari berbagai metode string termasuk string.Format, StringBuilder dan penggabungan string. Rangkaian string hampir selalu mengungguli metode lain dalam membangun string. Jadi, jika kinerja adalah kuncinya, maka itu lebih baik. Namun, jika kinerja tidak kritis maka saya pribadi mencari string.Format agar lebih mudah diikuti dalam kode. (Tapi itu alasan subjektif) Bagaimanapun, StringBuilder mungkin paling efisien sehubungan dengan pemanfaatan memori.

dviljoen.dll
sumber
0

Saya lebih suka String.Format terkait dengan kinerja

Farzad J
sumber
-1

Penggabungan string membutuhkan lebih banyak memori dibandingkan dengan String.Format. Jadi cara terbaik untuk menggabungkan string menggunakan String.Format atau System.Text.StringBuilder Object.

Mari kita ambil kasus pertama: "C" + rowIndex.ToString () Mari kita asumsikan rowIndex adalah tipe nilai sehingga metode ToString () harus Box untuk mengubah nilai menjadi String dan kemudian CLR membuat memori untuk string baru dengan kedua nilai disertakan.

Sedangkan string.Format mengharapkan parameter objek dan mengambil rowIndex sebagai objek dan mengubahnya menjadi string internal offcourse akan ada Boxing tetapi itu intrinsik dan juga tidak akan memakan banyak memori seperti pada kasus pertama.

Untuk string pendek tidak terlalu penting kurasa ...


sumber