Có bao nhiêu đối tượng String sẽ được tạo khi sử dụng dấu cộng?


115

Có bao nhiêu đối tượng String sẽ được tạo khi sử dụng dấu cộng trong đoạn mã dưới đây?

String result = "1" + "2" + "3" + "4";

Nếu nó như dưới đây, tôi đã nói ba đối tượng String: "1", "2", "12".

String result = "1" + "2";

Tôi cũng biết rằng các đối tượng Chuỗi được lưu trong bộ đệm / Bảng chuỗi thực tập để cải thiện hiệu suất, nhưng đó không phải là câu hỏi.


Chuỗi chỉ được thực hiện nếu bạn gọi String.Itern một cách rõ ràng.
Joe White

7
@JoeWhite: là họ?
Igor Korkhov

13
Không hẳn. Tất cả các chuỗi ký tự được thực hiện tự động. Kết quả của các hoạt động chuỗi là không.
Stefan Paul Noack

Hơn nữa, trong ví dụ OP, chỉ có một chuỗi hằng số và nó được thực hiện. Tôi sẽ cập nhật câu trả lời của tôi để minh họa.
Chris Shain

+1. Đối với một ví dụ thực tế về nhu cầu mã hóa chuỗi catenation theo kiểu đó, phần Ví dụ của msdn.microsoft.com/en-us/l Library / trộm có một điều không thể thực hiện được nếu trình biên dịch không thể tối ưu hóa nó đến một hằng số duy nhất, vì các ràng buộc về các giá trị được gán cho các tham số thuộc tính.
ClickRick

Câu trả lời:


161

Đáng ngạc nhiên, nó phụ thuộc.

Nếu bạn làm điều này trong một phương pháp:

void Foo() {
    String one = "1";
    String two = "2";
    String result = one + two + "34";
    Console.Out.WriteLine(result);
}

sau đó trình biên dịch dường như phát ra mã bằng cách sử dụng String.Concatnhư @Joachim đã trả lời (+1 cho anh ta btw).

Nếu bạn định nghĩa chúng là hằng số , vd:

const String one = "1";
const String two = "2";
const String result = one + two + "34";

hoặc theo nghĩa đen , như trong câu hỏi ban đầu:

String result = "1" + "2" + "3" + "4";

sau đó trình biên dịch sẽ tối ưu hóa đi những +dấu hiệu đó. Nó tương đương với:

const String result = "1234";

Hơn nữa, trình biên dịch sẽ loại bỏ các biểu thức hằng không liên quan và chỉ phát ra chúng nếu chúng được sử dụng hoặc tiếp xúc. Ví dụ, chương trình này:

const String one = "1";
const String two = "1";
const String result = one + two + "34";

public static void main(string[] args) {
    Console.Out.WriteLine(result);
}

Chỉ tạo một chuỗi - hằng số result(bằng "1234"). onetwokhông hiển thị trong IL kết quả.

Hãy nhớ rằng có thể có tối ưu hóa hơn nữa trong thời gian chạy. Tôi chỉ đi theo những gì IL được sản xuất.

Cuối cùng, liên quan đến thực tập, hằng số và nghĩa đen được thực tập, nhưng giá trị được thực hiện là giá trị không đổi kết quả trong IL, không phải bằng chữ. Điều này có nghĩa là bạn có thể nhận được thậm chí ít đối tượng chuỗi hơn bạn mong đợi, vì nhiều hằng số hoặc nghĩa đen được xác định giống hệt nhau sẽ thực sự là cùng một đối tượng! Điều này được minh họa bằng cách sau:

public class Program
{
    private const String one = "1";
    private const String two = "2";
    private const String RESULT = one + two + "34";

    static String MakeIt()
    {
        return "1" + "2" + "3" + "4";
    }   

    static void Main(string[] args)
    {
        string result = "1" + "2" + "34";

        // Prints "True"
        Console.Out.WriteLine(Object.ReferenceEquals(result, MakeIt()));

        // Prints "True" also
        Console.Out.WriteLine(Object.ReferenceEquals(result, RESULT));
        Console.ReadKey();
    }
}

Trong trường hợp các Chuỗi được nối trong một vòng lặp (hoặc nói cách khác là động), bạn kết thúc với một chuỗi bổ sung cho mỗi chuỗi. Chẳng hạn, sau đây tạo ra 12 thể hiện chuỗi: 2 hằng số + 10 lần lặp, mỗi lần lặp lại dẫn đến một thể hiện Chuỗi mới:

public class Program
{
    static void Main(string[] args)
    {
        string result = "";
        for (int i = 0; i < 10; i++)
            result += "a";
        Console.ReadKey();
    }
}

Nhưng (cũng đáng ngạc nhiên), nhiều phép nối liên tiếp được trình biên dịch kết hợp thành một phép nối đa chuỗi đơn. Ví dụ, chương trình này cũng chỉ tạo ra 12 trường hợp chuỗi! Điều này là do " Ngay cả khi bạn sử dụng nhiều toán tử + trong một câu lệnh, nội dung chuỗi chỉ được sao chép một lần. "

public class Program
{
    static void Main(string[] args)
    {
        string result = "";
        for (int i = 0; i < 10; i++)
            result += "a" + result;
        Console.ReadKey();
    }
}

những gì về chuỗi kết quả = "1" + "2" + ba + bốn; trong đó hai và ba được khai báo như chuỗi ba = "3"; Chuỗi bốn = "4";?
Ánh sáng

Thậm chí điều đó dẫn đến một chuỗi. Tôi chỉ chạy nó qua LinqPad để tự kiểm tra lại.
Chris Shain

1
@Servy - Nhận xét dường như đã được cập nhật. Khi bạn thay đổi một bình luận, nó không được đánh dấu là đang thay đổi.
Chó săn an ninh

1
Một trường hợp sẽ tốt đẹp để xem xét cho sự hoàn chỉnh là nối trong một vòng lặp. Ví dụ: Có bao nhiêu đối tượng chuỗi mà mã sau phân bổ:string s = ""; for (int i = 0; i < n; i++) s += "a";
Joren

1
Tôi sử dụng LINQPad ( linqpad.net ) hoặc Reflector ( reflector.net ). Cái trước cho bạn thấy IL của các đoạn mã tùy ý, đoạn mã sau dịch ngược các cụm thành IL và có thể tạo lại C # tương đương từ IL đó. Ngoài ra còn có một công cụ tích hợp có tên ILDASM ( msdn.microsoft.com/en-us/l Library / f7dy01k1 (v = vs.80) .aspx ) Hiểu IL là một điều khó khăn - xem codebetter.com/raymondlewallen/2005/ 02/07 /
Khải

85

Câu trả lời của Chris Shain là rất tốt. Là người đã viết trình tối ưu hóa nối chuỗi, tôi sẽ chỉ thêm hai điểm thú vị khác.

Đầu tiên là trình tối ưu hóa ghép nối về cơ bản bỏ qua cả dấu ngoặc đơn và tính kết hợp trái khi nó có thể thực hiện một cách an toàn. Giả sử bạn có một phương thức M () trả về một chuỗi. Nếu bạn nói:

string s = M() + "A" + "B";

sau đó trình biên dịch lý do rằng toán tử bổ sung được kết hợp lại, và do đó, điều này giống như:

string s = ((M() + "A") + "B");

Nhưng điều này:

string s = "C" + "D" + M();

giống như

string s = (("C" + "D") + M());

do đó, đó là nối của chuỗi không đổi "CD" với M().

Trong thực tế, trình tối ưu hóa ghép nối nhận ra rằng nối chuỗi là kết hợp và tạo ra String.Concat(M(), "AB")ví dụ đầu tiên, mặc dù điều đó vi phạm tính kết hợp trái.

Bạn thậm chí có thể làm điều này:

string s = (M() + "E") + ("F" + M()));

và chúng tôi vẫn sẽ tạo ra String.Concat(M(), "EF", M()).

Điểm thú vị thứ hai là các chuỗi rỗng và rỗng được tối ưu hóa đi. Vì vậy, nếu bạn làm điều này:

string s = (M() + "") + (null + M());

bạn sẽ nhận được String.Concat(M(), M())

Một câu hỏi thú vị sau đó được đặt ra: những gì về điều này?

string s = M() + null;

Chúng tôi không thể tối ưu hóa điều đó xuống

string s = M();

bởi vì M()có thể trả về null, nhưng String.Concat(M(), null)sẽ trả về một chuỗi rỗng nếu M()trả về null. Vì vậy, những gì chúng ta làm là thay vì giảm

string s = M() + null;

đến

string s = M() ?? "";

Qua đó chứng minh rằng nối chuỗi không thực sự cần gọi String.Concat.

Để đọc thêm về chủ đề này, xem

Tại sao String.Concat không được tối ưu hóa thành StringBuilder.Append?


Tôi nghĩ rằng một vài lỗi có thể đã xảy ra ở đó. Chắc chắn, ("C" + "D") + M())tạo ra String.Concat("CD", M()), không String.Concat(M(), "AB"). Và tiếp tục xuống, (M() + "E") + (null + M())nên tạo ra String.Concat(M(), "E", M()), không String.Concat(M(), M()).
hammar

21
+1 cho đoạn bắt đầu. :) Câu trả lời như thế này là điều luôn làm tôi ngạc nhiên về Stack Overflow.
brichin

23

Tôi tìm thấy câu trả lời tại MSDN. Một.

Cách: Nối nhiều chuỗi (Hướng dẫn lập trình C #)

Ghép nối là quá trình nối thêm một chuỗi vào cuối chuỗi khác. Khi bạn nối chuỗi ký tự chuỗi hoặc hằng chuỗi bằng cách sử dụng toán tử +, trình biên dịch sẽ tạo ra một chuỗi. Không có thời gian chạy nối xảy ra. Tuy nhiên, các biến chuỗi chỉ có thể được nối với nhau khi chạy. Trong trường hợp này, bạn nên hiểu ý nghĩa hiệu suất của các phương pháp khác nhau.


22

Chỉ một. Trình biên dịch C # sẽ gấp các hằng chuỗi và do đó về cơ bản nó sẽ biên dịch thành

String result = "1234";

Tôi nghĩ bất cứ khi nào bạn sử dụng "", nó sẽ tạo ra một đối tượng String.
Ánh sáng

1
@William nói chung có. Nhưng việc gấp liên tục sẽ loại bỏ các bước trung gian không cần thiết
JaredPar

13

Tôi nghi ngờ điều này là bắt buộc bởi bất kỳ tiêu chuẩn hoặc thông số kỹ thuật. Một phiên bản có khả năng có thể làm một cái gì đó khác với một phiên bản khác.


3
Đây là hành vi được ghi lại ít nhất là cho trình biên dịch C # của Microsoft cho VS 2008 và 2010 (xem câu trả lời của @ David-Stratton). Điều đó nói rằng, bạn đã đúng - theo như tôi có thể nói từ một sự nhìn chăm chú nhanh chóng, thông số C # không chỉ định điều này và có lẽ nó nên được coi là một chi tiết triển khai.
Chris Shain

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.