2008-11-07 32 views
52

Nếu tôi định nghĩa một struct trong C# sử dụng thuộc tính tự động như thế này:Tại sao cần gọi: this() trên cấu trúc để sử dụng các thuộc tính tự động trong C#?

public struct Address 
{ 
    public Address(string line1, string line2, string city, string state, string zip) 
    { 
     Line1 = line1; 
     Line2 = line2; 
     City = city; 
     State = state; 
     Zip = zip; 
    } 

    public string Line1 { get; protected set; } 
    public string Line2 { get; protected set; } 
    public string City { get; protected set; } 
    public string State { get; protected set; } 
    public string Zip { get; protected set; } 
} 

Khi tôi cố gắng để xây dựng các tập tin, tôi nhận được một lỗi biên dịch nói The 'this' object cannot be used before all of its fields are assigned to. Điều này có thể được giải quyết bằng cách thay đổi hàm khởi tạo để thực hiện cuộc gọi bị xích tới hàm tạo mặc định như sau:

public Address(string line1, string line2, string city, string state, string zip): this() 
{ 
    Line1 = line1; 
    Line2 = line2; 
    City = city; 
    State = state; 
    Zip = zip; 
} 

Câu hỏi của tôi là, tại sao lại hoạt động và điều gì đang xảy ra? Tôi đoán, và tôi cố gắng chứng minh điều đó bằng cách nhìn vào IL, nhưng tôi chỉ đùa với bản thân nếu tôi nghĩ tôi có thể phá vỡ IL. Nhưng tôi đoán là, tính chất tự động hoạt động bằng cách có trình biên dịch tạo ra các trường cho thuộc tính của bạn đằng sau hậu trường. Những trường này không thể được truy cập thông qua mã, tất cả các thiết lập và nhận phải được thực hiện thông qua các thuộc tính. Khi tạo một cấu trúc, một hàm tạo mặc định không thể được định nghĩa rõ ràng. Vì vậy, đằng sau hậu trường, trình biên dịch phải tạo một hàm tạo mặc định để đặt các giá trị của các trường mà nhà phát triển không thể nhìn thấy.

Bất kỳ và tất cả pháp sư IL đều được hoan nghênh chứng minh hoặc bác bỏ lý thuyết của tôi.

+0

Thành viên 'protected' mới không được phép trong một' struct' giống như nó không được phép trong một 'lớp niêm phong'. –

Trả lời

52

Lưu ý: như C# 6, điều này là không cần thiết - nhưng bạn nên sử dụng chỉ đọc thuộc tính tự động-thực hiện với C# 6 anyway ...

this() đảm bảo rằng các lĩnh vực chắc chắn được gán cho đến khi trình biên dịch có liên quan - nó đặt tất cả các trường thành các giá trị mặc định của chúng. Bạn phải có cấu trúc được xây dựng hoàn chỉnh trước khi bạn có thể bắt đầu truy cập bất kỳ thuộc tính nào.

Thật khó chịu, nhưng đó là cách thực hiện. Bạn có chắc bạn thực sự muốn điều này là một cấu trúc không? Và tại sao sử dụng một setter được bảo vệ trên một cấu trúc (mà không thể được bắt nguồn từ)?

+0

Bộ bảo vệ là thói quen. Tôi không tạo ra nhiều cấu trúc. Và điều này không phải từ mã thực tế tôi đang viết, tôi đã viết nó một cách cụ thể để minh họa quan điểm của câu hỏi của tôi. Nhưng Địa chỉ là bất biến và là một lựa chọn tốt cho các cấu trúc. Mọi người chuyển đến địa chỉ mới, địa chỉ không thay đổi. – NerdFury

+7

Không thể thay đổi! = Struct. Nó không cảm thấy rất có cấu trúc đối với tôi. Nó không nhất thiết là một sự lựa chọn * xấu *, nhưng nó không phải là một sự lựa chọn của tôi. Tôi muốn làm cho sự bất biến rõ ràng hơn: sử dụng một thuộc tính "bình thường" (chỉ đọc) và một trường chỉ đọc. Đó là một sự xấu hổ rằng các thuộc tính tự động không thể có các bộ định vị "chỉ đọc" (tiếp theo) –

+5

... mà chỉ có thể truy cập được trong các nhà xây dựng, giống như cách gán cho một trường chỉ đọc. Trình biên dịch sau đó có thể tạo ra một trường chỉ đọc phía sau hậu trường và chuyển đổi quyền truy cập setter thành truy cập trường trực tiếp. –

0

Thuộc tính không có gì khác ngoài việc đóng gói phương thức Get và/hoặc phương thức Set. CLR có siêu dữ liệu chỉ ra rằng các phương thức cụ thể nên được coi là một thuộc tính, nghĩa là các trình biên dịch nên cho phép một số cấu trúc mà nó sẽ không cho phép với các phương thức. Ví dụ: nếu X là thuộc tính đọc-ghi của Foo, trình biên dịch sẽ dịch Foo.X += 5 thành Foo.SET_X_METHOD(Foo.GET_X_METHOD() + 5) (mặc dù các phương pháp được đặt tên khác và thường không thể truy cập theo tên).

Mặc dù autoproperty triển khai một cặp phương thức get/set truy cập một trường riêng theo cách hoạt động nhiều hơn hoặc ít hơn như một trường, từ quan điểm của bất kỳ mã nào bên ngoài thuộc tính, một autoproperty là một cặp phương thức get/set giống như bất kỳ thuộc tính nào khác. Do đó, một tuyên bố như Foo.X = 5; được dịch là Foo.SET_X_METHOD(5). Vì trình biên dịch C# chỉ thấy rằng là một cuộc gọi phương thức và vì các phương thức không bao gồm bất kỳ siêu dữ liệu nào để chỉ ra các trường nào họ đọc hoặc viết, trình biên dịch sẽ cấm cuộc gọi phương thức trừ khi nó biết mọi trường của Foo đã được viết.

Cá nhân, lời khuyên của tôi là tránh sử dụng autoproperties với các loại cấu trúc. Autoproperties có ý nghĩa với các lớp, vì nó có thể cho các thuộc tính lớp để hỗ trợ các tính năng như thông báo cập nhật. Ngay cả khi phiên bản đầu tiên của lớp không hỗ trợ thông báo cập nhật, thì các phiên bản đó sử dụng trường tự động thay vì trường sẽ có nghĩa là các phiên bản sau có thể thêm tính năng thông báo cập nhật mà không yêu cầu người dùng phải làm lại lớp. Tuy nhiên, các cấu trúc không thể hỗ trợ một cách có ý nghĩa hầu hết các loại tính năng mà người ta có thể muốn thêm vào các thuộc tính giống trường.

Hơn nữa, sự khác biệt hiệu suất giữa các trường và thuộc tính lớn hơn nhiều với cấu trúc lớn hơn so với các loại lớp. Thật vậy, phần lớn đề xuất để tránh các cấu trúc lớn là hậu quả của sự khác biệt này. Các cấu trúc lớn thực sự có thể rất hiệu quả, nếu chúng tránh được việc sao chép chúng một cách không cần thiết. Thậm chí nếu ai có một cấu trúc khổng lồ HexDecet<HexDecet<HexDecet<Integer>>>, nơi HexDecet<T> nó chứa tiếp xúc với lĩnh vực F0 .. F15 loại T, một tuyên bố như Foo = MyThing.F3.F6.F9; sẽ chỉ đơn giản là yêu cầu đọc một số nguyên từ MyThing và lưu trữ nó để Foo, mặc dù MyThing sẽ do lớn theo tiêu chuẩn struct (4096 số nguyên chiếm 16K). Ngoài ra, người ta có thể cập nhật phần tử đó rất dễ dàng, ví dụ: MyThing.F3.F6.F9 += 26;. Ngược lại, nếu F0 .. F15 đã được tự động tài sản, báo cáo kết quả Foo = MyThing.F3.F6.F9 sẽ yêu cầu sao chép 1K dữ liệu từ MyThing.F3 để tạm thời (gọi nó là temp1, sau đó 64 byte dữ liệu từ temp1.F6 để temp2) trước khi cuối cùng nhận được xung quanh để đọc 4 byte dữ liệu từ temp2.F9. Ick. Tồi tệ hơn, cố gắng thêm 26 vào giá trị trong MyThing.F3.F6.F9 sẽ yêu cầu một cái gì đó như var t1 = MyThing.F3; var t2 = t1.F6; t2.F9 += 26; t1.F6 = f2; MyThing.F3 = t1;.

Nhiều khiếu nại lâu dài về "loại cấu trúc có thể thay đổi" thực sự là khiếu nại về các loại cấu trúc có thuộc tính đọc/ghi. Chỉ cần thay thế các thuộc tính bằng các trường và các vấn đề sẽ biến mất.

PS: Có những lúc có thể hữu ích khi có cấu trúc có thuộc tính truy cập đối tượng lớp mà nó chứa tham chiếu. Ví dụ, nó sẽ là tốt đẹp để có một phiên bản của một lớp học ArraySegment<T> cho phép một để nói Var foo[] = new int[100]; Var MyArrSeg = New ArraySegment<int>(foo, 25, 25); MyArrSeg[6] += 9;, và có tuyên bố cuối cùng thêm chín vào phần tử (25 + 6) của foo. Trong các phiên bản cũ của C# người ta có thể làm điều đó. Thật không may, việc sử dụng thường xuyên các autoproperties trong Framework nơi các trường sẽ thích hợp hơn dẫn đến các khiếu nại phổ biến về trình biên dịch cho phép các trình định vị thuộc tính được gọi là vô dụng trên các cấu trúc chỉ đọc; do đó, việc gọi bất kỳ setter thuộc tính nào trên một cấu trúc chỉ đọc bây giờ bị cấm, cho dù thuộc tính setter có thực sự sửa đổi bất kỳ trường nào của cấu trúc hay không. Nếu mọi người chỉ đơn giản là kiềm chế không làm cho các cấu trúc có thể thay đổi được thông qua các bộ định vị thuộc tính (làm cho các trường có thể truy cập trực tiếp khi có khả năng thích hợp), các trình biên dịch sẽ không bao giờ phải thực hiện hạn chế đó.

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