Khi tôi chuyển một hàm string
cho một hàm, một con trỏ đến nội dung của chuỗi có được truyền hay không, hay toàn bộ chuỗi được chuyển cho hàm trên ngăn xếp giống như a struct
?
Khi tôi chuyển một hàm string
cho một hàm, một con trỏ đến nội dung của chuỗi có được truyền hay không, hay toàn bộ chuỗi được chuyển cho hàm trên ngăn xếp giống như a struct
?
Câu trả lời:
Một tham chiếu được thông qua; tuy nhiên, về mặt kỹ thuật nó không được thông qua bằng tham chiếu. Đây là một sự khác biệt tinh tế, nhưng rất quan trọng. Hãy xem xét đoạn mã sau:
void DoSomething(string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomething(strMain);
Console.WriteLine(strMain); // What gets printed?
}
Có ba điều bạn cần biết để hiểu điều gì xảy ra ở đây:
strMain
không được chuyển bằng tham chiếu. Đó là một kiểu tham chiếu, nhưng bản thân tham chiếu được truyền theo giá trị . Bất kỳ khi nào bạn chuyển một tham số mà không có ref
từ khóa (không tính out
tham số), bạn đã truyền một giá trị nào đó.Vì vậy, điều đó có nghĩa là bạn đang ... chuyển một tham chiếu theo giá trị. Vì đó là một loại tham chiếu, chỉ tham chiếu được sao chép vào ngăn xếp. Nhưng điều đó có nghĩa gì?
Biến C # là kiểu tham chiếu hoặc kiểu giá trị . Các tham số C # được truyền bởi tham chiếu hoặc được truyền bởi giá trị . Thuật ngữ là một vấn đề ở đây; những điều này nghe có vẻ giống nhau, nhưng không phải.
Nếu bạn chuyển một tham số BẤT KỲ loại nào và bạn không sử dụng ref
từ khóa, thì bạn đã chuyển nó theo giá trị. Nếu bạn đã vượt qua nó theo giá trị, những gì bạn thực sự đã vượt qua là một bản sao. Nhưng nếu tham số là một kiểu tham chiếu, thì thứ bạn sao chép là tham chiếu, không phải bất cứ thứ gì nó đang trỏ vào.
Đây là dòng đầu tiên của Main
phương pháp:
string strMain = "main";
Chúng tôi đã tạo ra hai thứ trên dòng này: một chuỗi có giá trị main
được lưu trữ trong bộ nhớ ở đâu đó và một biến tham chiếu được gọi là strMain
trỏ đến nó.
DoSomething(strMain);
Bây giờ chúng tôi chuyển tham chiếu đó tới DoSomething
. Chúng tôi đã vượt qua nó theo giá trị, vì vậy có nghĩa là chúng tôi đã tạo một bản sao. Đó là một loại tham chiếu, vì vậy điều đó có nghĩa là chúng tôi đã sao chép tham chiếu, không phải chính chuỗi. Bây giờ chúng ta có hai tham chiếu mà mỗi tham chiếu trỏ đến cùng một giá trị trong bộ nhớ.
Đây là đầu của DoSomething
phương pháp:
void DoSomething(string strLocal)
Không có ref
từ khóa, vì vậy strLocal
và strMain
là hai tham chiếu khác nhau trỏ đến cùng một giá trị. Nếu chúng tôi chỉ định lại strLocal
...
strLocal = "local";
... chúng tôi đã không thay đổi giá trị được lưu trữ; chúng tôi lấy tham chiếu được gọi strLocal
và nhắm nó vào một chuỗi hoàn toàn mới. Điều gì xảy ra strMain
khi chúng ta làm điều đó? Không có gì. Nó vẫn đang chỉ vào chuỗi cũ.
string strMain = "main"; // Store a string, create a reference to it
DoSomething(strMain); // Reference gets copied, copy gets re-pointed
Console.WriteLine(strMain); // The original string is still "main"
Hãy thay đổi kịch bản trong một giây. Hãy tưởng tượng chúng ta không làm việc với các chuỗi, nhưng một số loại tham chiếu có thể thay đổi, như một lớp bạn đã tạo.
class MutableThing
{
public int ChangeMe { get; set; }
}
Nếu bạn làm theo tham chiếu objLocal
đến đối tượng mà nó trỏ tới, bạn có thể thay đổi các thuộc tính của nó:
void DoSomething(MutableThing objLocal)
{
objLocal.ChangeMe = 0;
}
Vẫn chỉ có một MutableThing
trong bộ nhớ và cả tham chiếu đã sao chép và tham chiếu gốc vẫn trỏ đến nó. Các thuộc tính của MutableThing
chính nó đã thay đổi :
void Main()
{
var objMain = new MutableThing();
objMain.ChangeMe = 5;
Console.WriteLine(objMain.ChangeMe); // it's 5 on objMain
DoSomething(objMain); // now it's 0 on objLocal
Console.WriteLine(objMain.ChangeMe); // it's also 0 on objMain
}
Ah, nhưng các chuỗi là bất biến! Không có thuộc ChangeMe
tính nào để đặt. Bạn không thể làm strLocal[3] = 'H'
trong C # như bạn có thể làm với char
mảng kiểu C ; thay vào đó bạn phải tạo một chuỗi hoàn toàn mới. Cách duy nhất để thay đổi strLocal
là trỏ tham chiếu vào một chuỗi khác và điều đó có nghĩa là không có gì bạn làm strLocal
có thể ảnh hưởng strMain
. Giá trị là không thay đổi và tham chiếu là một bản sao.
Để chứng minh có sự khác biệt, đây là những gì sẽ xảy ra khi bạn chuyển một tham chiếu bằng tham chiếu:
void DoSomethingByReference(ref string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomethingByReference(ref strMain);
Console.WriteLine(strMain); // Prints "local"
}
Lần này, chuỗi trong Main
thực sự bị thay đổi bởi vì bạn đã chuyển tham chiếu mà không sao chép nó trên ngăn xếp.
Vì vậy, mặc dù các chuỗi là kiểu tham chiếu, việc chuyển chúng theo giá trị có nghĩa là bất cứ điều gì xảy ra trong callee sẽ không ảnh hưởng đến chuỗi trong trình gọi. Nhưng vì chúng là kiểu tham chiếu, bạn không cần phải sao chép toàn bộ chuỗi vào bộ nhớ khi bạn muốn chuyển nó.
ref
từ khóa. Để chứng minh rằng việc lướt
ref
từ khóa có tiện ích, tôi chỉ cố gắng giải thích tại sao một từ khóa có thể nghĩ về việc chuyển một loại tham chiếu theo giá trị trong C # có vẻ giống như khái niệm "truyền thống" (tức là C) về việc chuyển theo tham chiếu (và chuyển một loại tham chiếu bằng cách tham chiếu trong C # có vẻ giống như việc chuyển một tham chiếu đến một tham chiếu theo giá trị).
Foo(string bar)
có thể được coi là Foo(char* bar)
trong khi Foo(ref string bar)
sẽ như thế nào Foo(char** bar)
(hoặc Foo(char*& bar)
hoặc Foo(string& bar)
trong C ++). Chắc chắn, đó không phải là cách bạn nên nghĩ về nó hàng ngày, nhưng nó thực sự đã giúp tôi cuối cùng hiểu được những gì đang xảy ra.
Các chuỗi trong C # là các đối tượng tham chiếu bất biến. Điều này có nghĩa là các tham chiếu đến chúng được truyền xung quanh (theo giá trị) và khi một chuỗi được tạo, bạn không thể sửa đổi nó. Các phương thức tạo ra các phiên bản đã sửa đổi của chuỗi (chuỗi con, phiên bản bị cắt xén, v.v.) tạo ra các bản sao đã sửa đổi của chuỗi gốc.
Chuỗi là trường hợp đặc biệt. Mỗi trường hợp là bất biến. Khi bạn thay đổi giá trị của một chuỗi, bạn đang cấp phát một chuỗi mới trong bộ nhớ.
Vì vậy, chỉ có tham chiếu được chuyển đến hàm của bạn, nhưng khi chuỗi được chỉnh sửa, nó sẽ trở thành một phiên bản mới và không sửa đổi phiên bản cũ.
Uri
(class) và Guid
(struct) cũng là các trường hợp đặc biệt. Tôi không thấy cách System.String
hoạt động giống như một "kiểu giá trị" hơn các kiểu bất biến khác ... của nguồn gốc lớp hoặc cấu trúc.
Uri
& Guid
- bạn chỉ có thể gán giá trị chuỗi-chữ cho một biến chuỗi. Chuỗi dường như có thể thay đổi, giống như một chuỗi int
được gán lại, nhưng nó đang tạo một đối tượng một cách ngầm định - không có new
từ khóa.