Đầu ra chuỗi: định dạng hoặc concat trong C #?


178

Giả sử bạn muốn xuất chuỗi hoặc nối chuỗi. Bạn thích phong cách nào sau đây?

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

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

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

Bạn có sử dụng định dạng hay chỉ đơn giản là nối chuỗi? Sở thích của bạn là gì? Là một trong những điều này làm tổn thương đôi mắt của bạn?

Bạn có bất kỳ đối số hợp lý để sử dụng một và không phải là đối số khác?

Tôi sẽ đi cho cái thứ hai.

Câu trả lời:


88

Hãy thử mã này.

Đây là một phiên bản sửa đổi một chút của mã của bạn.
1. Tôi đã xóa Console.WriteLine vì có thể vài lệnh có cường độ chậm hơn so với những gì tôi đang cố gắng đo.
2. Tôi đang khởi động Đồng hồ bấm giờ trước vòng lặp và dừng ngay sau đó, theo cách này tôi sẽ không mất độ chính xác nếu chức năng lấy ví dụ 26,4 tích tắc để thực thi.
3. Cách bạn chia kết quả cho một số lần lặp là sai. Xem điều gì xảy ra nếu bạn có 1000 mili giây và 100 mili giây. Trong cả hai tình huống, bạn sẽ nhận được 0 ms sau khi chia cho 1000000.

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

Đó là kết quả của tôi:

1000000 x result = string.Format ("{0} {1}", p.FirstName, p.LastName); đã lấy: 618ms - 2213706 đánh dấu
1000000 x kết quả = (p.FirstName + "" + p.LastName); mất: 166ms - 595610 tick


1
Rất thú vị. Tôi nhận được trung bình là 224ms so với 48ms, cải thiện x4.66, thậm chí tốt hơn so với x3.72 của bạn. Tôi tự hỏi nếu có một công cụ biên dịch hậu kỳ có thể viết lại IL string.Formatmà không sử dụng bất kỳ tính năng định dạng tổng hợp nào (tức là chỉ đơn giản {0}) và thay thế chúng bằng cách nối chuỗi nhanh hơn đáng kể. Tôi tự hỏi một kỳ tích như vậy là có thể đạt được với một trình viết lại IL hiện có như PostSharp.
Allon Guralnek

31
Các chuỗi là bất biến, điều này có nghĩa là cùng một phần bộ nhớ nhỏ được sử dụng lặp đi lặp lại trong mã của bạn. Thêm hai chuỗi giống nhau lại với nhau và tạo cùng một chuỗi mới lặp đi lặp lại không ảnh hưởng đến bộ nhớ. .Net đủ thông minh chỉ để sử dụng cùng một tham chiếu bộ nhớ. Do đó, mã của bạn không thực sự kiểm tra sự khác biệt giữa hai phương thức concat. Xem mã trong câu trả lời của tôi dưới đây.
Ludington

1
Thành thật mà nói, tôi luôn luôn kết nối vì nó dễ đọc hơn và nhanh hơn :)
puretppc

Vì vậy, tốc độ là lý do duy nhất để chọn cái này hơn cái kia?
niico

158

Tôi ngạc nhiên khi rất nhiều người ngay lập tức muốn tìm mã thực thi nhanh nhất. Nếu MỘT TRIỆU lần lặp VẪN CẦN mất ít hơn một giây để xử lý, thì điều này có xảy ra trong BẤT K W CÁCH NÀO đáng chú ý cho người dùng cuối không? Không có khả năng lắm.

Tối ưu hóa sớm = FAIL.

Tôi sẽ đi với String.Format tùy chọn, chỉ vì nó có ý nghĩa nhất từ ​​quan điểm kiến ​​trúc. Tôi không quan tâm đến hiệu suất cho đến khi nó trở thành một vấn đề (và nếu có, tôi tự hỏi: Tôi có cần ghép một triệu tên cùng một lúc không? Chắc chắn tất cả chúng sẽ không phù hợp trên màn hình ...)

Xem xét nếu sau đó khách hàng của bạn muốn thay đổi nó để họ có thể định cấu hình xem hiển thị "Firstname Lastname"hay "Lastname, Firstname."với tùy chọn Định dạng, điều này thật dễ dàng - chỉ cần trao đổi chuỗi định dạng. Với concat, bạn sẽ cần thêm mã. Chắc chắn rằng điều đó không giống như một vấn đề lớn trong ví dụ cụ thể này nhưng ngoại suy.


47
Điểm hay về "Tối ưu hóa sớm == FAIL", vâng. Nhưng, khi bạn bắt đầu trả tiền cho dấu chân thực thi (đám mây và cơ sở hạ tầng như một dịch vụ, bất cứ ai?) Và / hoặc bạn bắt đầu hỗ trợ 1 triệu người dùng trên một cái gì đó thì phản hồi cho một người dùng theo yêu cầu không phải là câu hỏi. Chi phí phục vụ yêu cầu cho người dùng là chi phí cho điểm mấu chốt của bạn cũng như vấn đề quy mô nếu / khi hàng ngàn cuộc gọi khác đến thông qua ...
Aidanapword

23
Điều này là hoàn toàn sai. Trong môi trường phát triển web, thường thì mã tạo chuỗi của bạn sẽ nằm sâu trong cả mô hình, chế độ xem và bộ điều khiển của bạn và có thể được gọi hàng chục nghìn lần mỗi lần tải trang. Giảm 50% thời gian đánh giá mã tạo chuỗi bằng 50% có thể là một chiến thắng to lớn.
Benjamin Sussman

2
Một câu hỏi như thế này sẽ không được áp dụng trong trường hợp của OP. Câu trả lời là loại điều mà mọi người có thể nhớ là "tôi nên lắp ráp chuỗi theo cách nào?" khi họ viết tất cả mã của họ.
Phil Miller

6
@Benjamin: ... trong trường hợp đó, bạn sẽ lập hồ sơ và thấy đó là nút cổ chai của mình. Tuy nhiên, tôi sẽ đặt cược rằng bạn chỉ đang rút tiền từ nơi đó; đã từng viết và lập hồ sơ một số ứng dụng web trong quá khứ, tôi hầu như luôn thấy nút cổ chai trong thời gian phản hồi (trên máy chủ) là các truy vấn cơ sở dữ liệu.
BlueRaja - Daniel Pflughoeft

2
Điều này chắc chắn nhất là KHÔNG tối ưu hóa sớm. Khá sai lầm. Hiệu suất chuỗi hoàn toàn có thể cản trở các UI, đặc biệt là trong .NET nếu bạn đang thực hiện nhiều định dạng và xây dựng chuỗi. ubiquity.acm.org/article.cfm?id=1513451
user99999991

54

Ôi trời ơi - sau khi đọc một trong những câu trả lời khác, tôi đã thử đảo ngược thứ tự của các thao tác - vì vậy hãy thực hiện nối đầu tiên, sau đó là 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

Vì vậy, thứ tự của các hoạt động tạo ra sự khác biệt lớn, hay đúng hơn là hoạt động đầu tiên LUÔN chậm hơn nhiều.

Đây là kết quả của một hoạt động trong đó các hoạt động được hoàn thành nhiều lần. Tôi đã cố gắng thay đổi các đơn đặt hàng nhưng mọi thứ thường tuân theo các quy tắc tương tự, một khi kết quả đầu tiên bị bỏ qua:

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

Như bạn có thể thấy các lần chạy tiếp theo của cùng một phương thức (tôi đã tái cấu trúc mã thành 3 phương thức) sẽ tăng nhanh hơn. Nhanh nhất có vẻ là phương thức Console.WriteLine (String.Concat (...)), tiếp theo là nối thông thường, và sau đó là các hoạt động được định dạng.

Sự chậm trễ ban đầu khi khởi động có khả năng là khởi tạo Console Stream, khi đặt Console.Writeline ("Bắt đầu!") Trước khi thao tác đầu tiên đưa mọi thứ trở lại dòng.


2
Sau đó loại bỏ Console.WriteLine hoàn toàn khỏi các bài kiểm tra của bạn. Đó là sai lệch kết quả!
CShark

Tôi luôn bắt đầu với một kịch bản vứt bỏ hoặc "kiểm soát" khi chạy các bài kiểm tra hiệu suất vì lý do chính xác này
drzaus

36

Các chuỗi là bất biến, điều này có nghĩa là cùng một phần bộ nhớ nhỏ được sử dụng lặp đi lặp lại trong mã của bạn. Thêm hai chuỗi giống nhau lại với nhau và tạo cùng một chuỗi mới lặp đi lặp lại không ảnh hưởng đến bộ nhớ. .Net đủ thông minh chỉ để sử dụng cùng một tham chiếu bộ nhớ. Do đó, mã của bạn không thực sự kiểm tra sự khác biệt giữa hai phương thức concat.

Hãy thử điều này cho kích thước:

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

Đầu ra mẫu:

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
Đã thêm StringBuilder và kết quả đầu ra mẫu vào câu trả lời
mikechuld

Tôi thấy cách sử dụng string.Formatlà giá trị hiệu suất nhỏ đạt được ở đây. Về mặt kiến ​​trúc, nó tốt hơn vì nó có nghĩa là bạn có thể thay đổi định dạng dễ dàng hơn. Nhưng người xây dựng chuỗi tôi thực sự không thấy quan điểm của. Mọi luồng khác ở đây đều nói rằng bạn nên sử dụng Stringbuilder thay vì nối chuỗi. Lợi thế là gì? Rõ ràng không phải tốc độ, như tiêu chuẩn này chứng minh.
roryok

22

Đáng tiếc cho các dịch giả nghèo

Nếu bạn biết ứng dụng của bạn sẽ ở lại bằng tiếng Anh, thì tốt thôi, hãy lưu lại đồng hồ. Tuy nhiên, nhiều nền văn hóa thường sẽ thấy Lastname Firstname trong các địa chỉ.

Vì vậy, sử dụng string.Format(), đặc biệt là nếu bạn sẽ có ứng dụng của bạn đi bất cứ nơi nào mà tiếng Anh không phải là ngôn ngữ đầu tiên.


2
Làm thế nào sẽ string.Format()cư xử khác nhau trong các nền văn hóa khác nhau? Nó sẽ không in tên đầu tiên sau đó tên cuối cùng? Có vẻ như bạn phải tính đến văn hóa khác nhau trong cả hai tình huống. Tôi cảm thấy như tôi đang thiếu một cái gì đó ở đây.
Broots Waymb

2
Tôi đồng ý với @DangerZone .. làm thế nào để string.Format()biết bạn đang sử dụng tên cho một địa chỉ? Nếu string.Format()hoán đổi {0} {1}dựa trên văn hóa, tôi sẽ xem xét nó bị hỏng.
Alex McMillan

2
Tôi tin rằng điểm mà Jeremy đang cố gắng đưa ra là trong kịch bản được mô tả để hỗ trợ các quốc gia khác nhau, có thể phù hợp để trích xuất chuỗi định dạng thành tài nguyên ngôn ngữ. Đối với hầu hết các quốc gia, chuỗi đó sẽ là "{0} {1}", nhưng đối với các quốc gia có họ đầu tiên là hoạt động tiêu biểu (ví dụ: Hungary, Hồng Kông, Campuchia, Trung Quốc, Nhật Bản, Hàn Quốc, Madagascar, Đài Loan, Việt Nam và thay vào đó là các phần của Ấn Độ) chuỗi đó sẽ là "{1} {0}".
Richard J Foster

Thật. Hoặc, tinh tế hơn, thêm chuỗi định dạng như một thuộc tính của người. Tôi, ví dụ, muốn có họ của mình sau tên đầu tiên của tôi, nhưng đồng nghiệp của tôi không có.
Jeremy McGee

14

Đây là kết quả của tôi hơn 100.000 lần lặp:

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

Và đây là mã băng ghế dự bị:

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

Vì vậy, tôi không biết ai trả lời đánh dấu là câu trả lời :)


Tại sao màu xanh nền cho câu trả lời này?
dùng88637

@yossi nó màu xanh vì người trả lời giống như người hỏi
Davy8

9

Chuỗi kết nối là tốt trong một kịch bản đơn giản như thế - nó phức tạp hơn với bất cứ điều gì phức tạp hơn thế, ngay cả LastName, FirstName. Với định dạng bạn có thể thấy, trong nháy mắt, cấu trúc cuối cùng của chuỗi sẽ là gì khi đọc mã, với sự kết hợp, gần như không thể nhận ra ngay kết quả cuối cùng (ngoại trừ một ví dụ rất đơn giản như thế này).

Điều đó có nghĩa là về lâu dài là khi bạn quay lại để thay đổi định dạng chuỗi của mình, bạn sẽ có khả năng bật và thực hiện một vài điều chỉnh cho chuỗi định dạng hoặc nhăn mày và bắt đầu di chuyển xung quanh các loại tài sản truy cập trộn lẫn với văn bản, có nhiều khả năng giới thiệu các vấn đề.

Nếu bạn đang sử dụng .NET 3.5, bạn có thể sử dụng một phương thức mở rộng như phương pháp này và dễ dàng thực hiện, tắt cú pháp cuff như thế này:

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

Cuối cùng, khi ứng dụng của bạn phát triển phức tạp, bạn có thể quyết định rằng để duy trì hoàn toàn các chuỗi trong ứng dụng của mình, bạn muốn chuyển chúng vào một tệp tài nguyên để bản địa hóa hoặc đơn giản là một trình trợ giúp tĩnh. Điều này sẽ dễ dàng đạt được hơn nếu bạn luôn sử dụng các định dạng và mã của bạn có thể được tái cấu trúc khá đơn giản để sử dụng một cái gì đó như

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

7

Đối với thao tác rất đơn giản, tôi sẽ sử dụng phép nối, nhưng một khi bạn vượt quá 2 hoặc 3 yếu tố Định dạng sẽ trở thành IMO phù hợp hơn.

Một lý do khác để thích String.Format là các chuỗi .NET là bất biến và thực hiện theo cách này sẽ tạo ra ít bản sao tạm thời / trung gian hơn.


6

Mặc dù tôi hoàn toàn hiểu sở thích về phong cách và chọn cách ghép cho câu trả lời đầu tiên của tôi một phần dựa trên sở thích của riêng tôi, một phần quyết định của tôi dựa trên suy nghĩ rằng việc ghép nối sẽ nhanh hơn. Vì vậy, vì tò mò, tôi đã thử nghiệm nó và kết quả thật đáng kinh ngạc, đặc biệt là đối với một chuỗi nhỏ như vậy.

Sử dụng mã sau:

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

Tôi đã nhận được kết quả như sau:

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

Sử dụng phương thức định dạng chậm hơn 100 lần !! Sự kết hợp thậm chí không đăng ký là 1ms, đó là lý do tại sao tôi cũng xuất ra các dấu thời gian.


2
Nhưng tất nhiên bạn nên thực hiện thao tác nhiều lần để lấy số đo.
erikkallen

2
Và mất cuộc gọi đến Console.Writeline () vì nó nằm ngoài phạm vi của câu hỏi?
Aidanapword

bạn đã thử nghiệm với một nhà xây dựng chuỗi? ;)
niico

6

Bắt đầu từ các chuỗi nội suy C # 6.0 có thể được sử dụng để làm điều này, giúp đơn giản hóa định dạng hơn nữa.

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

Một biểu thức chuỗi được nội suy trông giống như một chuỗi mẫu có chứa các biểu thức. Một biểu thức chuỗi được nội suy tạo ra một chuỗi bằng cách thay thế các biểu thức được chứa bằng các phản hồi ToString của các kết quả của biểu thức.

Các chuỗi nội suy có hiệu suất tương tự String.Format, nhưng được cải thiện khả năng đọc và cú pháp ngắn hơn, do thực tế là các giá trị và biểu thức được chèn vào trong dòng.

Vui lòng tham khảo bài viết dotnetperls này về nội suy chuỗi.

Nếu bạn đang tìm kiếm một cách mặc định để định dạng chuỗi của mình, điều này có ý nghĩa về khả năng đọc và hiệu suất (trừ khi micro giây sẽ tạo ra sự khác biệt trong trường hợp sử dụng cụ thể của bạn).


5

Để nối chuỗi cơ bản, tôi thường sử dụng kiểu thứ hai - dễ đọc và đơn giản hơn. Tuy nhiên, nếu tôi đang thực hiện kết hợp chuỗi phức tạp hơn, tôi thường chọn String.Format.

String.Format tiết kiệm rất nhiều trích dẫn và điểm cộng ...

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

Chỉ có một vài nhà tài trợ được lưu, nhưng tôi nghĩ, trong ví dụ này, định dạng làm cho nó sạch hơn nhiều.


5

Một thử nghiệm tốt hơn sẽ là xem bộ nhớ của bạn bằng cách sử dụng bộ đếm Perfmon và bộ nhớ CLR. Tôi hiểu rằng toàn bộ lý do bạn muốn sử dụng String.Format thay vì chỉ nối các chuỗi là vì các chuỗi là bất biến, bạn không cần thiết phải gánh bộ thu gom rác với các chuỗi tạm thời cần được thu hồi trong lần tiếp theo.

StringBuilder và String.Format, mặc dù có khả năng chậm hơn, nhưng hiệu quả bộ nhớ cao hơn.

Điều gì là xấu về nối chuỗi?


Tôi đồng ý; mỗi hoạt động chuỗi tạo ra một bản sao mới của chuỗi. Tất cả bộ nhớ đó sẽ được người thu gom rác thu hồi sớm hay muộn. Vì vậy, phân bổ rất nhiều chuỗi có thể quay lại cắn bạn sau này.
Marnix van Valen

5

Nói chung tôi thích cái trước, đặc biệt là khi các chuỗi dài, nó có thể dễ đọc hơn nhiều.

Lợi ích khác là tôi tin rằng một trong các hiệu năng, vì cái sau thực sự thực hiện 2 câu lệnh tạo chuỗi trước khi chuyển chuỗi cuối cùng sang phương thức Console.Write. String.Format sử dụng StringBuilder dưới vỏ bọc mà tôi tin, do đó, nhiều kết nối được tránh.

Tuy nhiên, cần lưu ý rằng nếu các tham số bạn truyền vào String.Format (và các phương thức khác như Console.Write) là các loại giá trị thì chúng sẽ được đóng hộp trước khi truyền vào, có thể cung cấp các lần truy cập hiệu năng của chính nó. Blog bài viết về điều này ở đây .


1
Bài đăng trên blog đó hiện có tại: jeffbarnes.net/blog/post/2006/08/08/ . Tôi chịu đựng không đủ rep để chỉnh sửa.
Richard Slater

5

Một tuần kể từ ngày 19 tháng 8 năm 2015, câu hỏi này sẽ chính xác là bảy (7) tuổi. Bây giờ có một cách tốt hơn để làm điều này. Tốt hơn về khả năng bảo trì vì tôi chưa thực hiện bất kỳ thử nghiệm hiệu năng nào so với việc chỉ nối các chuỗi (nhưng liệu nó có quan trọng trong những ngày này không? Một vài mili giây khác nhau?). Cách làm mới với C # 6.0 :

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

Tính năng mới này tốt hơn , IMO và thực sự tốt hơn trong trường hợp của chúng tôi vì chúng tôi có các mã nơi chúng tôi xây dựng các truy vấn có giá trị phụ thuộc vào một số yếu tố. Hãy tưởng tượng một chuỗi truy vấn trong đó chúng ta có 6 đối số. Vì vậy, thay vì làm một, ví dụ:

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

trong có thể được viết như thế này và nó dễ đọc hơn:

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

Thật vậy, cách mới của C # 6.0 tốt hơn so với các lựa chọn thay thế trước đây - ít nhất là từ quan điểm dễ đọc.
Philippe

Đúng rồi. Và nó cũng an toàn hơn vì bạn không phải lo lắng về việc đối tượng sẽ đi đến chỉ mục nào (giữ chỗ) vì bạn sẽ trực tiếp đặt các đối tượng mà bạn muốn.
von v.

BTW, nó thực sự gọi Format (ít nhất là với Roslyn).
Philippe

BTW, cái mà poster này đề cập đến được gọi là "nội suy chuỗi" và được đề cập ở nơi khác trong chuỗi này.
CShark

4
  1. Định dạng là cách làm .NET .NET. Một số công cụ tái cấu trúc (Refactor! For one) thậm chí sẽ đề xuất cấu trúc lại mã kiểu concat để sử dụng kiểu định dạng.
  2. Định dạng dễ dàng hơn để tối ưu hóa cho trình biên dịch (mặc dù cái thứ hai có thể sẽ được cấu trúc lại để sử dụng phương thức 'concat' nhanh).
  3. Định dạng thường dễ đọc hơn (đặc biệt là với định dạng của Fancy Fancy).
  4. Định dạng có nghĩa là các cuộc gọi ngầm định đến '.ToString' trên tất cả các biến, điều này tốt cho khả năng đọc.
  5. Theo Càng hiệu quả của C #, các triển khai .NET 'WriteLine' và 'Format' bị rối tung, chúng tự động hộp tất cả các loại giá trị (rất tệ). Càng hiệu quả C # mà khuyên bạn nên thực hiện các cuộc gọi '.ToString' một cách rõ ràng, mà IMHO là không có thật (xem bài đăng của Jeff )
  6. Hiện tại, các gợi ý loại định dạng không được trình biên dịch kiểm tra, dẫn đến lỗi thời gian chạy. Tuy nhiên, điều này có thể được sửa đổi trong các phiên bản trong tương lai.

4

Tôi chọn dựa trên khả năng đọc. Tôi thích tùy chọn định dạng khi có một số văn bản xung quanh các biến. Trong ví dụ này:

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

bạn hiểu ý nghĩa ngay cả khi không có tên biến, trong khi concat lộn xộn với dấu ngoặc kép và dấu + và làm tôi bối rối:

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

(Tôi đã mượn ví dụ của Mike vì tôi thích nó)

Nếu chuỗi định dạng không có ý nghĩa nhiều nếu không có tên biến, tôi phải sử dụng concat:

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

Tùy chọn định dạng làm cho tôi đọc tên biến và ánh xạ chúng đến các số tương ứng. Tùy chọn concat không yêu cầu điều đó. Tôi vẫn còn bối rối bởi dấu ngoặc kép và dấu +, nhưng sự thay thế là tồi tệ hơn. Hồng ngọc?

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

Hiệu suất khôn ngoan, tôi hy vọng tùy chọn định dạng sẽ chậm hơn sau đó là concat, vì định dạng yêu cầu chuỗi phải được phân tích cú pháp . Tôi không nhớ phải tối ưu hóa loại hướng dẫn này, nhưng nếu tôi đã làm, tôi sẽ xem xét stringcác phương pháp như Concat()Join().

Ưu điểm khác với định dạng là chuỗi định dạng có thể được đặt trong tệp cấu hình. Rất tiện dụng với các thông báo lỗi và văn bản UI.


4

Tôi sẽ sử dụng String.Format, nhưng tôi cũng sẽ có chuỗi định dạng trong các tệp tài nguyên để nó có thể được bản địa hóa cho các ngôn ngữ khác. Sử dụng một chuỗi concat đơn giản không cho phép bạn làm điều đó. Rõ ràng nếu bạn không bao giờ cần phải bản địa hóa chuỗi đó, đây không phải là lý do để suy nghĩ. Nó thực sự phụ thuộc vào những gì chuỗi là cho.

Nếu nó được hiển thị cho người dùng, tôi sẽ sử dụng String.Format để tôi có thể bản địa hóa nếu tôi cần - và FxCop sẽ kiểm tra chính tả cho tôi, chỉ trong trường hợp :)

Nếu nó chứa số hoặc bất kỳ thứ không phải chuỗi nào khác (ví dụ: ngày), tôi sẽ sử dụng String.Format vì nó cho tôi quyền kiểm soát nhiều hơn về định dạng .

Nếu đó là để xây dựng một truy vấn như SQL, tôi sẽ sử dụng Linq .

Nếu để nối các chuỗi bên trong một vòng lặp, tôi sẽ sử dụng StringBuilder để tránh các vấn đề về hiệu năng.

Nếu nó cho một số đầu ra, người dùng sẽ không thấy và sẽ không ảnh hưởng đến hiệu suất Tôi sử dụng String.Format vì dù sao tôi cũng có thói quen sử dụng nó và tôi chỉ quen với nó :)


3

Nếu bạn đang xử lý một thứ gì đó dễ đọc (và đây là hầu hết mã), tôi sẽ gắn bó với phiên bản quá tải của nhà điều hành UNLESS:

  • Mã cần được thực thi hàng triệu lần
  • Bạn đang thực hiện hàng tấn concats (hơn 4 là một tấn)
  • Mã được nhắm mục tiêu theo Khung nhỏ gọn

Trong ít nhất hai trong số các trường hợp này, tôi sẽ sử dụng StringBuilder thay thế.


3

Nếu bạn có ý định bản địa hóa kết quả, thì String.Format là điều cần thiết bởi vì các ngôn ngữ tự nhiên khác nhau thậm chí có thể không có dữ liệu theo cùng một thứ tự.


2

Tôi nghĩ điều này phụ thuộc rất nhiều vào mức độ phức tạp của đầu ra. Tôi có xu hướng chọn bất kỳ kịch bản nào hoạt động tốt nhất tại thời điểm đó.

Chọn công cụ phù hợp dựa trên công việc: D Bất cứ nơi nào trông sạch sẽ nhất!


2

Tôi thích thứ hai là tốt nhưng tôi không có lý lẽ hợp lý tại thời điểm này để hỗ trợ vị trí đó.


2

Đẹp quá

Chỉ cần thêm

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

Và nó thậm chí còn nhanh hơn (tôi đoán chuỗi.Concat được gọi trong cả hai ví dụ, nhưng cái đầu tiên yêu cầu một số loại dịch).

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
Phải mất chính xác cùng một lượng thời gian kể từ khi nối chuỗi dựa trên toán tử được trình biên dịch dịch sang các lệnh gọi string.Concat(...). Nó được thực hiện trong quá trình biên dịch nên nó không ảnh hưởng đến hiệu năng thời gian chạy. Nếu bạn chạy thử nghiệm nhiều lần hoặc chạy chúng trên các mẫu thử lớn hơn, bạn sẽ thấy chúng giống hệt nhau.
Allon Guralnek

2

Vì tôi không nghĩ câu trả lời ở đây bao gồm tất cả mọi thứ, tôi muốn thực hiện một bổ sung nhỏ ở đây.

Console.WriteLine(string format, params object[] pars) các cuộc gọi string.Format . '+' Hàm ý nối chuỗi. Tôi không nghĩ rằng điều này luôn luôn phải làm với phong cách; Tôi có xu hướng kết hợp hai phong cách tùy thuộc vào bối cảnh tôi đang ở.

Câu trả lời ngắn

Quyết định mà bạn phải đối mặt phải thực hiện với phân bổ chuỗi. Tôi sẽ cố gắng làm cho nó đơn giản.

Nói rằng bạn có

string s = a + "foo" + b;

Nếu bạn thực hiện điều này, nó sẽ đánh giá như sau:

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

tmpđây không thực sự là một biến cục bộ, nhưng nó là tạm thời cho JIT (nó được đẩy trên ngăn xếp IL). Nếu bạn đẩy một chuỗi trên ngăn xếp (chẳng hạn như ldstrtrong IL cho bằng chữ), bạn đặt tham chiếu đến một con trỏ chuỗi trên ngăn xếp.

Khoảnh khắc bạn gọi concat tham chiếu này trở thành một vấn đề, bởi vì không có bất kỳ tham chiếu chuỗi nào có sẵn có cả hai chuỗi. Điều này có nghĩa là .NET cần phân bổ một khối bộ nhớ mới và sau đó lấp đầy nó bằng hai chuỗi. Lý do đây là một vấn đề, là vì phân bổ tương đối đắt tiền.

Điều này thay đổi câu hỏi thành: Làm thế nào bạn có thể giảm số lượng concat hoạt động?

Vì vậy, câu trả lời sơ bộ là: string.Formatcho> 1 concats, '+' sẽ hoạt động tốt chỉ với 1 concat. Và nếu bạn không quan tâm đến việc thực hiện tối ưu hóa hiệu suất vi mô,string.Format sẽ hoạt động tốt trong trường hợp chung.

Một lưu ý về văn hóa

Và rồi có một thứ gọi là văn hóa ...

string.Format cho phép bạn sử dụng CultureInfo trong định dạng của bạn. Một toán tử đơn giản '+' sử dụng văn hóa hiện tại.

Điều này đặc biệt quan trọng nếu bạn đang viết định dạng tệp và f.ex. doublecác giá trị mà bạn 'thêm' vào một chuỗi. Trên các máy khác nhau, bạn có thể kết thúc bằng các chuỗi khác nhau nếu bạn không sử dụng string.Formatvới một rõ ràngCultureInfo .

F.ex xem xét những gì xảy ra nếu bạn thay đổi một '.' đối với ',' trong khi viết tệp giá trị được phân tách bằng dấu phẩy ... bằng tiếng Hà Lan, dấu phân cách thập phân là dấu phẩy, vì vậy người dùng của bạn có thể nhận được một bất ngờ 'hài hước'.

Thêm câu trả lời

Nếu bạn không biết trước kích thước chính xác của chuỗi, tốt nhất nên sử dụng chính sách như thế này để tổng thể bộ đệm bạn sử dụng. Không gian chùng được lấp đầy trước tiên, sau đó dữ liệu được sao chép vào.

Phát triển có nghĩa là phân bổ một khối bộ nhớ mới và sao chép dữ liệu cũ vào bộ đệm mới. Khối bộ nhớ cũ sau đó có thể được phát hành. Bạn nhận được điểm mấu chốt tại thời điểm này: phát triển là một hoạt động đắt tiền.

Cách thực tế nhất để làm điều này là sử dụng chính sách tổng thể. Chính sách phổ biến nhất là tổng thể bộ đệm trong các quyền hạn của 2. Tất nhiên, bạn phải làm điều đó thông minh hơn một chút (vì sẽ không có ý nghĩa gì khi tăng từ 1,2,4,8 nếu bạn đã biết bạn cần 128 ký tự ) nhưng bạn có được hình ảnh. Chính sách này đảm bảo bạn không cần quá nhiều thao tác đắt tiền mà tôi đã mô tả ở trên.

StringBuilderlà một lớp về cơ bản tổng thể bộ đệm cơ bản trong quyền hạn của hai. string.Formatsử dụng StringBuilderdưới mui xe.

Điều này làm cho quyết định của bạn trở thành sự đánh đổi cơ bản giữa tổng thể và bổ sung (-multipl) (văn hóa w / wo) hoặc chỉ phân bổ và nối thêm.


1

Cá nhân, cái thứ hai như mọi thứ bạn đang sử dụng theo thứ tự trực tiếp, nó sẽ được xuất ra. Trong khi đó với cái đầu tiên bạn phải ghép {0} và {1} với var thích hợp, rất dễ gây rối.

Ít nhất nó không tệ như chạy nước rút C ++, nếu bạn lấy sai loại biến, toàn bộ mọi thứ sẽ nổ tung.

Ngoài ra, vì thứ hai là tất cả nội tuyến và nó không phải thực hiện bất kỳ tìm kiếm và thay thế nào cho tất cả {0} thứ, nên thứ hai sẽ nhanh hơn ... mặc dù tôi không biết chắc chắn.


1

Tôi thực sự thích cái đầu tiên bởi vì khi có rất nhiều biến xen kẽ với văn bản, nó có vẻ dễ đọc hơn đối với tôi. Thêm vào đó, việc xử lý dấu ngoặc kép dễ dàng hơn khi sử dụng định dạng chuỗi.Format (), uh,. Dưới đây là phân tích hợp lý của nối chuỗi.


1

Tôi đã luôn đi theo chuỗi String.Format (). Có thể lưu trữ các định dạng trong các biến như ví dụ của Nathan là một lợi thế lớn. Trong một số trường hợp tôi có thể nối thêm một biến nhưng một khi có nhiều hơn 1 biến được ghép lại, tôi tái cấu trúc để sử dụng định dạng.


1

Ồ, và chỉ để hoàn thiện, sau đây là một vài tích tắc nhanh hơn so với cách ghép thông thường:

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

1

Cái đầu tiên (định dạng) có vẻ tốt hơn đối với tôi. Nó dễ đọc hơn và bạn không tạo thêm các đối tượng chuỗi tạm thời.


1

Tôi tò mò nơi StringBuilder đứng với các bài kiểm tra này. Kết quả bên dưới ...

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

Các kết quả:

Concat: 406 tick
Concat: 356 tick
Concat: 411 tick
Concat: 299 tick
Concat: 266 tick
Định dạng: 5269 tick
Định dạng: 954 tick
Định dạng: 1004 tick
Định dạng: 984 tick
Định dạng: 974 tick
StringBuilder: 629 tick
StringBuilder: 484 tick
StringBuilder: 482 tick
StringBuilder: 508 tick
StringBuilder: 504 tick

1

Theo tài liệu chuẩn bị MCSD, Microsoft đề nghị sử dụng toán tử + khi xử lý một số lượng rất nhỏ các kết nối (có thể là 2 đến 4). Tôi vẫn không chắc tại sao, nhưng đó là điều cần xem xét.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.