2015-08-10 12 views
9

Xét đoạn mã sau:Chuỗi .NET có thực sự được coi là không thay đổi?

unsafe 
{ 
    string foo = string.Copy("This can't change"); 

    fixed (char* ptr = foo) 
    { 
     char* pFoo = ptr; 
     pFoo[8] = pFoo[9] = ' '; 
    } 

    Console.WriteLine(foo); // "This can change" 
} 

này tạo ra một con trỏ tới ký tự đầu tiên của foo, reassigns nó trở thành có thể thay đổi, và thay đổi các ký tự 8 và 9 vị trí lên đến ' '.

Thông báo Tôi chưa bao giờ thực sự được giao lại foo; thay vào đó, tôi đã thay đổi giá trị của nó bằng cách sửa đổi trạng thái của nó hoặc biến đổi chuỗi. Do đó, các chuỗi .NET có thể thay đổi được.

này hoạt động rất tốt, trên thực tế, đó là đoạn mã sau:

unsafe 
{ 
    string bar = "Watch this"; 

    fixed (char* p = bar) 
    { 
     char* pBar = p; 
     pBar[0] = 'C'; 
    } 

    string baz = "Watch this"; 
    Console.WriteLine(baz); // Unrelated, right? 
} 

sẽ in "Catch this" do chuỗi interning chữ.

này có rất nhiều công dụng được áp dụng, ví dụ này:

string GetForInputData(byte[] inputData) 
{ 
    // allocate a mutable buffer... 
    char[] buffer = new char[inputData.Length]; 

    // fill the buffer with input data 

    // ...and a string to return 
    return new string(buffer); 
} 

được thay thế bằng:

string GetForInputData(byte[] inputData) 
{ 
    // allocate a string to return 
    string result = new string('\0', inputData.Length); 

    fixed (char* ptr = result) 
    { 
     // fill the result with input data 
    } 

    return result; // return it 
} 

Điều này có thể tiết kiệm chi phí phân bổ bộ nhớ/hiệu suất có khả năng rất lớn nếu bạn làm việc trong một speed- trường quan trọng (ví dụ như mã hóa). Tôi đoán bạn có thể nói rằng điều này không được tính vì nó "sử dụng hack" để làm cho con trỏ có thể thay đổi, nhưng sau đó lại là các nhà thiết kế ngôn ngữ C# đã hỗ trợ gán chuỗi cho con trỏ ở vị trí đầu tiên. (Trong thực tế, điều này được thực hiện allthetime nội bộ trong StringStringBuilder, vì vậy về mặt kỹ thuật bạn có thể làm StringBuilder của riêng bạn với điều này.)

Vì vậy, nên NET chuỗi thực sự được coi là bất biến?

+0

Chúng không thay đổi khi sử dụng API công khai. Nếu bạn sử dụng mã không an toàn hoặc phản chiếu để bỏ qua API công khai đó thì không. – MarcinJuraszek

+0

@MarcinJuraszek Con trỏ * là * một phần của API công khai, cũng xem đoạn cuối của tôi. –

+1

Tôi đang nói về API công khai của lớp 'string' - các phương thức, thuộc tính mà nó hiển thị. – MarcinJuraszek

Trả lời

6

§ 18.6 của đặc tả ngôn ngữ C# (Các fixed tuyên bố) giải quyết cụ thể các trường hợp thay đổi một chuỗi thông qua một con trỏ cố định, và chỉ ra rằng làm như vậy có thể dẫn đến hành vi undefined:

đối tượng Sửa đổi loại được quản lý thông qua con trỏ cố định có thể dẫn đến hành vi không xác định. Ví dụ, bởi vì các chuỗi là bất biến, nên trách nhiệm của người lập trình là đảm bảo rằng các ký tự được tham chiếu bởi một con trỏ tới một chuỗi cố định không bị sửa đổi.

+0

Thú vị, tôi chỉ từng nghe thuật ngữ "hành vi không xác định" được sử dụng trong thông số C/C++ (mọi lúc). Nhìn thấy nó trong C# là một cái gì đó mới. –

+1

@JamesKo Thậm chí còn có thể hiện hành vi không xác định trong thông số C# không liên quan đến mã 'không an toàn' (mã duy nhất tôi có thể tìm thấy trong tìm kiếm nhanh): nếu bạn sử dụng tùy chỉnh chờ với 'async' /' await' và tùy chỉnh awaiter của bạn gọi sự tiếp tục nhiều lần, hành vi là không xác định. – hvd

+0

* Đây là lý do tại sao bạn nên downvote câu hỏi này ...câu trả lời được trình bày rõ ràng trong thông số ngôn ngữ, và dường như được các tác giả coi là rất có thể là một mối quan tâm, họ nghĩ về nó trước thời hạn –

1

Tôi chỉ phải chơi thử nghiệm này và thử nghiệm để xác nhận xem địa chỉ của chuỗi ký tự có trỏ vào cùng một vị trí bộ nhớ hay không.

Kết quả là:

string foo = "Fix value?"; //New address: 0x02b215f8 
string foo2 = "Fix value?"; //Points to same address: 0x02b215f8 
string fooCopy = string.Copy(foo); //New address: 0x021b2888 

fixed (char* p = foo) 
{ 
    p[9] = '!'; 
} 

Console.WriteLine(foo); 
Console.WriteLine(foo2); 
Console.WriteLine(fooCopy); 

//Reference is equal, which means refering to same memory address 
Console.WriteLine(string.ReferenceEquals(foo, foo2)); //true 

//Reference is not equal, which creates another string in new memory address 
Console.WriteLine(string.ReferenceEquals(foo, fooCopy)); //false 

Chúng ta thấy rằng foo khởi một chuỗi chữ mà chỉ vào 0x02b215f8 địa chỉ bộ nhớ trong máy tính của tôi. Việc gán cùng một chuỗi ký tự cho foo2 sẽ tham chiếu cùng một địa chỉ bộ nhớ. Và tạo một bản sao của cùng một chuỗi ký tự đó tạo ra một bản sao mới.Kiểm tra thêm thông qua string.ReferenceEquals() cho thấy rằng chúng thực sự là bằng nhau cho foofoo2 trong khi tham chiếu khác nhau cho foofooCopy.

Thật thú vị khi xem cách chuỗi ký tự có thể được thao tác trong bộ nhớ và ảnh hưởng đến các biến khác chỉ tham chiếu đến nó. Một trong những điều mà chúng ta nên cẩn thận vì hành vi này tồn tại.

Các vấn đề liên quan