2010-06-05 31 views
24

Tôi đang cố gắng hiểu cách gán "tham chiếu" vào trường lớp trong C#.Làm cách nào để gán "tham chiếu" vào trường lớp trong C#?

Tôi có ví dụ sau để xem xét:

public class X 
{ 

    public X() 
    { 

    string example = "X"; 

    new Y(ref example); 

    new Z(ref example); 

    System.Diagnostics.Debug.WriteLine(example); 

    } 

} 

public class Y 
{ 

    public Y(ref string example) 
    { 
    example += " (Updated By Y)"; 
    } 

} 

public class Z 
{ 

    private string _Example; 

    public Z(ref string example) 
    { 

    this._Example = example; 

    this._Example += " (Updated By Z)"; 

    } 

} 

var x = new X(); 

Khi chạy đoạn mã trên đầu ra là:

X (Cập nhật By Y)

Và không:

X (Cập nhật theo Y) (Cập nhật theo Z)

Như tôi đã hy vọng.

Dường như việc gán "tham số ref" cho trường sẽ mất tham chiếu.

Có cách nào để giữ tham chiếu khi gán cho một trường không?

Cảm ơn.

Trả lời

8

Số ref hoàn toàn là quy ước gọi điện. Bạn không thể sử dụng nó để đủ điều kiện một trường. Trong Z, _Example được đặt thành giá trị của tham chiếu chuỗi được truyền vào. Sau đó, bạn gán một tham chiếu chuỗi mới cho nó bằng cách sử dụng + =. Bạn không bao giờ gán cho ví dụ, do đó, ref không có hiệu lực.

Chỉ làm việc xung quanh cho những gì bạn muốn là có một đối tượng bao bọc có thể thay đổi được chia sẻ (một mảng hoặc một StringWrapper giả định) có chứa tham chiếu (một chuỗi ở đây). Nói chung, nếu bạn cần điều này, bạn có thể tìm thấy một đối tượng có thể thay đổi lớn hơn cho các lớp để chia sẻ.

public class StringWrapper 
{ 
    public string s; 
    public StringWrapper(string s) 
    { 
    this.s = s; 
    } 

    public string ToString() 
    { 
    return s; 
    } 
} 

public class X 
{ 
    public X() 
    { 
    StringWrapper example = new StringWrapper("X"); 
    new Z(example) 
    System.Diagnostics.Debug.WriteLine(example); 
    } 
} 

public class Z 
{ 
    private StringWrapper _Example; 
    public Z(StringWrapper example) 
    { 
    this._Example = example; 
    this._Example.s += " (Updated By Z)"; 
    } 
} 
+0

Bạn có muốn mở rộng đoạn thứ hai một chút không? "một đối tượng có thể thay đổi lớn hơn" không hoàn toàn rõ ràng đối với tôi. Ngoài ra, StringWrapper sẽ hoạt động như thế nào nếu không có cách nào để giữ tham chiếu chuỗi trong một trường? –

+0

Tôi đã đưa ra một ví dụ mà nên làm rõ. Như tôi đã nói, trong một thiết kế thực, StringWrapper có lẽ sẽ là một đối tượng kinh doanh đang nắm giữ nhiều hơn một chuỗi. –

+0

Cảm ơn ý tưởng. Ví dụ của tôi có lẽ đã quá đơn giản vì tôi thực sự có một đối tượng (không phải là một chuỗi) trong mã thực của tôi mà tôi đang cố gắng gán theo tham chiếu. Hành vi mà tôi đang tìm kiếm là cho phép người dùng của một đối tượng có thể gán giá trị null cho đối tượng đã nói, hoặc thậm chí gán một cá thể mới của kiểu đó từ các phương thức của nó. Tôi vượt qua đối tượng trong thông qua các nhà xây dựng như là một phần của một số tiêm phụ thuộc và do đó, các phương pháp khác chỉ có thể nhìn thấy các đối tượng bằng cách truy cập vào một lĩnh vực. Nó có vẻ như những gì tôi muốn không thể đạt được mà là một sự xấu hổ. – Jamie

3

Bạn quên cập nhật các tài liệu tham khảo trong lớp Z:

public class Z { 
    private string _Example; 

    public Z(ref string example) { 
     example = this._Example += " (Updated By Z)"; 
    } 
} 

Output: X (Cập nhật By Y) (Cập nhật bởi Z)

điểm cần lưu ý là các + = toán tử cho một chuỗi gọi phương thức String.Concat(). Tạo đối tượng chuỗi mới, nó không cập nhật giá trị của chuỗi. Đối tượng chuỗi không thay đổi, lớp chuỗi không có bất kỳ phương thức hoặc trường nào cho phép bạn thay đổi giá trị. Rất khác với hành vi mặc định của loại tham chiếu thông thường.

Vì vậy, nếu bạn sử dụng một phương thức hoặc toán tử chuỗi, bạn luôn phải gán giá trị trả lại cho một biến. Đây là cú pháp khá tự nhiên, các kiểu giá trị hoạt động theo cùng một cách. Mã của bạn sẽ rất giống nhau nếu bạn sử dụng một int thay vì một chuỗi.

+1

Cảm ơn ý tưởng Hans. Ví dụ của tôi là có lỗi - để giữ cho nó đơn giản, tôi đã thực hiện cập nhật trong hàm tạo. Trong thực tế, trường sẽ được đặt trong hàm tạo nhưng bản cập nhật diễn ra theo một phương thức khác sau khi đối tượng được khởi tạo. – Jamie

56

Như những người khác đã lưu ý, bạn không thể có trường "ref to variable". Tuy nhiên, chỉ biết rằng bạn không thể làm điều đó có lẽ là không hài lòng; bạn có thể cũng muốn biết đầu tiên, tại sao không, và thứ hai, làm thế nào để có được xung quanh hạn chế này.

Lý do tại sao là bởi vì chỉ có ba khả năng:

1) Không cho phép lĩnh vực loại ref

2) Cho phép các lĩnh vực không an toàn của loại ref

3) Không sử dụng lưu trữ tạm thời nhóm cho các biến cục bộ (hay còn gọi là "ngăn xếp")

Giả sử chúng tôi cho phép các trường kiểu ref. Sau đó, bạn có thể làm

public ref int x; 
void M() 
{ 
    int y = 123; 
    this.x = ref y; 
} 

và bây giờ y có thể được truy cập sau M hoàn tất. Điều này có nghĩa là chúng tôi đang trong trường hợp (2) - truy cập this.x sẽ sụp đổ và chết khủng khiếp vì lưu trữ cho y không còn tồn tại - hoặc chúng tôi đang trong trường hợp (3) và địa phương y được lưu trữ trên thùng rác thu thập đống, không phải là bộ nhớ tạm thời.

Chúng tôi thích tối ưu hóa các biến cục bộ được lưu trữ trên hồ bơi tạm thời ngay cả khi chúng được chuyển qua ref và chúng tôi ghét ý tưởng bạn có thể để lại một quả bom thời gian xung quanh. Do đó, tùy chọn nó là: không có trường ref.

Lưu ý rằng đối với các biến cục bộ là các biến đóng kín của các hàm ẩn danh, chúng tôi chọn tùy chọn (3); các biến cục bộ đó không được cấp phát khỏi nhóm tạm thời.

Điều gì sau đó đưa chúng ta đến câu hỏi thứ hai: làm thế nào để bạn vượt qua nó? Nếu lý do bạn muốn một trường ref là tạo một getter và setter của một biến khác, điều đó hoàn toàn hợp pháp:

sealed class Ref<T> 
{ 
    private readonly Func<T> getter; 
    private readonly Action<T> setter; 
    public Ref(Func<T> getter, Action<T> setter) 
    { 
     this.getter = getter; 
     this.setter = setter; 
    } 
    public T Value { get { return getter(); } set { setter(value); } } 
} 
... 
Ref<int> x; 
void M() 
{ 
    int y = 123; 
    x = new Ref<int>(()=>y, z=>{y=z;}); 
    x.Value = 456; 
    Console.WriteLine(y); // 456 -- setting x.Value changes y. 
} 

Và ở đó bạn đi. y được lưu trữ trên gc heap, và x là một đối tượng có khả năng nhận và đặt y.

Lưu ý rằng CLR không hỗ trợ người dân địa phương và ref phương thức trả lại, mặc dù C# thì không. Có lẽ một phiên bản tương lai giả định của C# sẽ hỗ trợ các tính năng này; Tôi có prototyped nó và nó hoạt động tốt. Tuy nhiên, đây không phải là thực sự cao trong danh sách ưu tiên, vì vậy tôi sẽ không có được hy vọng của tôi.

CẬP NHẬT: Tính năng được đề cập trong đoạn trên được thực hiện cuối cùng cho thực tế trong C# 7. Tuy nhiên, bạn vẫn không thể lưu trữ một ref trong một trường.

+0

Tại sao người yêu cầu getter và setter - là việc đóng cửa quan trọng để có được y vẫn còn đóng hộp?Nó sẽ có thể chỉ đơn giản là sử dụng Ref {public T Value {get; bộ; } } thay thế? –

+1

@ChrisMoschini: Chắc chắn, điều đó hoàn toàn có thể. Nhưng sau đó bạn đang đi xung quanh một tham chiếu đến * một giá trị *, và không * một biến *. Toàn bộ điểm của câu hỏi là "làm thế nào để lưu trữ một tham chiếu đến một biến?" Giả sử thay vì biến cục bộ y, bạn muốn lưu trữ một "tham chiếu" đến phần tử thứ 12 của một mảng, để biến đổi tham chiếu đã biến đổi phần tử thứ 12 của một mảng. Loại được đề xuất của bạn sẽ không trợ giúp trong trường hợp đó. –

+0

Đúng, mặc dù không đi qua .someMethod (mảng ref [12]), mà dường như là nhiều hơn với tinh thần của câu hỏi. Cả hai đề xuất của chúng tôi đều cho bạn một loại giá trị được đóng hộp và vẫn được đóng hộp, thật không may. Nullable thực sự có thể là đối sánh gần nhất với những gì người hỏi đang cố gắng. –

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