2012-01-11 31 views
32

Tôi biết rằng tôi có thể sử dụng cú pháp tiết đối với tài sản:Có thể truy cập các trường sao lưu sau các thuộc tính được tự động triển khai không?

private string _postalCode; 

public string PostalCode 
{ 
    get { return _postalCode; } 
    set { _postalCode = value; } 
} 

Hoặc tôi có thể sử dụng tính năng tự động thực hiện.

public string PostalCode { get; set; } 

Tôi có thể truy cập vào trường sao lưu nằm phía sau thuộc tính được tự động triển khai không? (Trong ví dụ này sẽ là _ Mã bưu chính).


Sửa: Câu hỏi của tôi không phải là về thiết kế, nhưng thay vì về, chúng ta hãy nói, khả năng lý thuyết để làm như vậy.

+6

tò mò !! Tại sao bạn lại muốn điều đó? – Azodious

+0

Lập trình để thực hiện là nguy hiểm. – Eranga

+0

Bạn có thể truy cập nó với sự phản ánh riêng tư, nhưng đó là một ý tưởng tồi. – CodesInChaos

Trả lời

33

Tôi không biết về bạn, nhưng tôi đã viết mã trong các dự án ở công ty khác, và bây giờ tôi muốn để biết làm thế nào tôi đã làm một cái gì đó! Vì vậy, nó thường nhanh hơn để làm một tìm kiếm web cho câu trả lời, và nó mang lại cho tôi ở đây.

Tuy nhiên, lý do của tôi thì khác. Tôi là đơn vị thử nghiệm, và không quan tâm những gì thuần túy phải nói, nhưng như là một phần của một thiết lập cho một bài kiểm tra đơn vị, tôi đang cố gắng để gọi một trạng thái nhất định cho một đối tượng nhất định. Nhưng trạng thái đó nên được kiểm soát nội bộ. Tôi không muốn một số nhà phát triển khác vô tình lộn xộn với nhà nước, mà có thể có hiệu quả đến nay trên hệ thống. Vì vậy, nó phải được đặt riêng! Tuy nhiên, làm thế nào để bạn đơn vị kiểm tra một cái gì đó như thế mà không cần gọi hành vi (hy vọng) sẽ không bao giờ xảy ra? Trong những tình huống như vậy, tôi tin rằng việc sử dụng sự phản chiếu với thử nghiệm đơn vị là hữu ích.

Cách khác là phơi bày những thứ chúng tôi không muốn tiếp xúc, vì vậy chúng tôi có thể kiểm tra chúng! Vâng, tôi đã thấy điều này trong môi trường thực tế, và chỉ nghĩ về nó vẫn khiến tôi lắc đầu.

Vì vậy, tôi hy vọng rằng mã bên dưới có thể hữu ích.

Có hai phương pháp ở đây chỉ để tách mối quan tâm, thực sự và cũng để hỗ trợ dễ đọc. Phản ánh là công cụ quay đầu cho hầu hết các nhà phát triển, những người trong kinh nghiệm của tôi hoặc né tránh nó, hoặc tránh nó như bệnh dịch hạch!

private string _getBackingFieldName(string propertyName) 
{ 
    return string.Format("<{0}>k__BackingField", propertyName); 
} 

private FieldInfo _getBackingField(object obj, string propertyName) 
{ 
    return obj.GetType().GetField(_getBackingFieldName(propertyName), BindingFlags.Instance | BindingFlags.NonPublic); 
} 

Tôi không biết quy tắc mã nào bạn làm việc, nhưng cá nhân tôi thích phương thức trợ giúp riêng tư và bắt đầu bằng chữ thường. Tôi không thấy điều đó đủ rõ ràng khi đọc, vì vậy tôi cũng thích dấu gạch dưới trước đó.

Có thảo luận về các trường sao lưu và đặt tên tự động của chúng. Với mục đích kiểm tra đơn vị, bạn sẽ biết khá nhanh nếu nó đã thay đổi hay không! Nó sẽ không thảm khốc với mã thực sự của bạn, chỉ là các bài kiểm tra. Vì vậy, chúng tôi có thể đưa ra các giả định đơn giản về việc đặt tên cho các tên — như tôi đã nói ở trên. Bạn có thể không đồng ý, và điều đó là tốt.

Trình trợ giúp khó khăn hơn _getBackingField trả về một trong các loại phản ánh đó, FieldInfo. Tôi cũng đã đưa ra một giả định ở đây, rằng trường hậu thuẫn mà bạn đang theo sau là từ một đối tượng là một cá thể, trái ngược với việc là tĩnh. Bạn có thể phá vỡ điều đó thành các đối số để được thông qua nếu bạn muốn, nhưng các vùng biển chắc chắn sẽ lầy lội hơn với nhà phát triển trung bình, những người có thể muốn các chức năng nhưng không phải là sự hiểu biết.

Điều tiện dụng về FieldInfo s là chúng có thể đặt trường trên đối tượng khớp với FieldInfo. Điều này được giải thích tốt hơn với một ví dụ:

var field = _getBackingField(myObjectToChange, "State"); 
field.SetValue(myObjectToChange, ObjectState.Active); 

Trong trường hợp này, trường có kiểu liệt kê được gọi là ObjectState. Tên đã được thay đổi để bảo vệ người vô tội! Vì vậy, trong dòng thứ hai, bạn có thể thấy rằng bằng cách truy cập vào các FieldInfo trả về trước đó, tôi có thể gọi theo phương pháp SetValue, mà bạn có thể nghĩ rằng nên có liên quan đến đối tượng của bạn, nhưng không! Đây là bản chất của sự phản chiếu - FieldInfo phân tách một trường từ nơi nó đến, vì vậy bạn phải nói cho nó biết ví dụ nào để làm việc với (myObjectToChange) và do đó, giá trị bạn muốn nó có, trong trường hợp này là ObjectState.Active. Vì vậy, để lập một câu chuyện dài, lập trình hướng đối tượng sẽ ngăn cản chúng ta làm những điều khó chịu như truy cập vào các trường tư nhân, và tệ hơn, thay đổi chúng khi nhà phát triển mã không có ý định. Cái nào tốt! Đó là một trong những lý do C# rất có giá trị và được các nhà phát triển ưa thích.

Tuy nhiên, Microsoft đã cho chúng tôi phản ánh, và thông qua nó, chúng tôi sử dụng một vũ khí hùng mạnh. Nó có thể xấu xí, và rất chậm, nhưng đồng thời, nó phơi bày chiều sâu bên trong của các hoạt động bên trong của MSIL (MicroSoft Intermediate Language) —IL cho ngắn — và cho phép chúng ta phá vỡ mọi quy tắc trong cuốn sách, là một ví dụ điển hình.

2

Không, không có. Nếu bạn muốn truy cập trường sao lưu, thì không sử dụng thuộc tính tự động và cuộn thuộc tính của riêng bạn.

Từ tài liệu cho Auto-Implemented Properties:

Khi bạn khai báo một tài sản như trong ví dụ sau đây, trình biên dịch sẽ tạo ra một, lĩnh vực ủng hộ vô danh riêng mà chỉ có thể được truy cập thông qua get của tài sản và thiết lập bộ truy xuất.

+0

Bạn có bất kỳ tài liệu tham khảo chính thức nào không? –

+0

Được cung cấp trong câu trả lời của tôi. – Krizz

+0

Đã cập nhật câu trả lời của tôi. – KMan

3

Xem đoạn trích sau đây từ this document:

thực hiện tự động tài sản (tính năng tự động thực hiện) tự động mô hình này. Cụ thể hơn, khai báo thuộc tính không trừu tượng được phép có các phần tử accessicolon accessor. Cả hai accessors phải có mặt và cả hai đều phải có các dấu chấm phẩy, nhưng chúng có thể có các bộ điều biến truy cập khác nhau. Khi một thuộc tính được chỉ định như thế này, một trường sao lưu sẽ tự động được tạo cho thuộc tính và các trình truy cập sẽ được triển khai để đọc và ghi vào trường sao lưu đó. Tên của trường sao lưu là trình biên dịch được tạo và không thể truy cập được đối với người dùng.

Do đó, không có cách nào để truy cập các trường. Sử dụng cách tiếp cận đầu tiên của bạn. Thuộc tính được tự động triển khai dành riêng cho trường hợp bạn không cần truy cập trường sao lưu.

8

này xuất phát trực tiếp từ MSDN:

Trong C# 3.0 và sau, thuộc tính tự động thực hiện làm tài sản kê khai chính xác hơn khi không có logic bổ sung là cần trong accessors tài sản. Chúng cũng cho phép mã máy khách tạo các đối tượng . Khi bạn khai báo thuộc tính như được hiển thị trong ví dụ sau, trình biên dịch tạo trường sao lưu riêng tư, ẩn danh chỉ có thể được truy cập thông qua trình truy cập nhận và đặt của thuộc tính.

Vì vậy, bạn không thể.

+3

Kết luận 'Vì vậy, không bạn không thể' không đúng. Nó không nói bất cứ điều gì về việc không thể truy cập được. Ví dụ, các trường riêng tư vẫn có thể được truy cập bằng cách sử dụng Phản chiếu. – Didii

1

Auto-implemented properties là phiên bản "nhỏ hơn" của thuộc tính được thực hiện theo cách thủ công với trường sao lưu. Vì chúng không cho phép bất kỳ logic bổ sung nào, điều duy nhất bạn có thể làm với chúng là đọc hoặc viết trường riêng "ẩn".Bạn vẫn có thể sử dụng công cụ sửa đổi private để truy cập giới hạn cho một trong những người truy cập (thường là set sẽ là người riêng tư trong trường hợp đó), nếu bạn muốn các phương thức riêng của mình có đặc quyền này.

Nếu bạn muốn truy cập vào các lĩnh vực cá nhân một số khác lớp (giống như một lớp từ BCL), bạn có thể sử dụng Reflection để làm điều đó (như đã giải thích ở these examples), nhưng nó sẽ là một Hack khó chịu mà không có ai sẽ đảm bảo rằng một thay đổi một chữ cái trong nguồn của khung công tác sẽ không phá vỡ mã của bạn trong tương lai.

Nhưng vì bạn đã chọn tham gia triển khai tự động, tôi không thấy lý do nào có thể muốn truy cập trường sao lưu. Truy cập trực tiếp trường hoặc thông qua các trình truy cập thuộc tính được tự động triển khai không có sự khác biệt và không có lợi ích. Ví dụ, bạn có thể tranh luận rằng bạn có thể sử dụng Interlocked để sửa đổi trường nguyên tử (mà bạn không thể thực hiện đối với thuộc tính), nhưng nó không thực thi bất kỳ "chuỗi nào an toàn" khi mọi người khác có quyền truy cập vào trường thông qua thuộc tính không có nguyên tử.

Chưa kể rằng trình biên dịch hầu như có thể nội tuyến mọi cuộc gọi, do đó, cũng có no performance difference.

+0

Đây là một điều phổ biến để làm trong VB, khi bạn muốn thiết lập giá trị trường sao lưu trong constructor của lớp mà thuộc tính tự động thực hiện thuộc về. Tôi đồng ý không có lý do hợp lệ để muốn đặt nó bên ngoài của nhà xây dựng. Tôi đã khá sốc (và do đó việc tìm kiếm dẫn tôi ở đây) để thấy rằng bạn không thể làm điều đó trong C#. Nhưng tôi thực sự bối rối ngay bây giờ, bởi vì bạn nói "điều duy nhất bạn có thể làm với họ là đọc hoặc viết" trường riêng "ẩn". –

+0

@Yann: Tôi không sử dụng VB, vì vậy tôi không chắc chắn nếu tôi hiểu bạn, nhưng có một 'readonly' sửa đổi trong C# cho phép bạn thiết lập các trường riêng trong constructor chỉ. Nếu chỉ có thể được áp dụng cho các trường, không phải thuộc tính, vì vậy không có cách nào để sử dụng nó nếu bạn sử dụng các thuộc tính được tự động triển khai. – Groo

5

Trong Visual Studio 2010, ít nhất, bạn có thể lấy danh sách các lĩnh vực tư nhân trong một lớp học sử dụng phản ánh nếu bạn dứt khoát tuyên bố rằng ông muốn các instance fields ngoài công lập:

FieldInfo[] myInfo = ClassWithPostalCode.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic); 

Bạn có thể sau đó lặp qua mảng FieldInfo. Trong trường hợp này bạn sẽ thấy rằng tên của lĩnh vực sao lưu có lẽ sẽ

< Bưu chính > k__BackingField

tôi đã nhận thấy rằng tất cả các thuộc tính tự động dường như đi theo mô hình của tên thuộc tính trong góc dấu ngoặc vuông theo sau là "k__BackingField", nhưng hãy nhớ rằng đây không phải là chính thức và có thể thay đổi trong các phiên bản sau của .Net. Tôi không hoàn toàn chắc chắn nó không phải là khác nhau trong các phiên bản trước đây, cho rằng vấn đề.

Một khi bạn biết tên trường, bạn có thể nhận được giá trị của nó theo cách này:

object oValue = obj.GetType().InvokeMember(fi.Name 
    , BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance 
    , null, obj, null); 
3

CẬP NHẬT: https://github.com/jbevain/mono.reflection đi kèm với phương thức trình giải quyết trường sao lưu hoạt động với các thuộc tính tự động được tạo bởi C#, VB.NET và F #. Gói NuGet ở số https://www.nuget.org/packages/Mono.Reflection/

ORIGINAL: Tôi đã kết thúc bằng phương pháp khá linh hoạt này chỉ cho C# thuộc tính tự động. Khi các câu trả lời khác làm rõ, đây không phải là di động và sẽ không hoạt động nếu việc triển khai trình biên dịch sử dụng lược đồ đặt tên trường sao lưu khác ngoài <PropertyName>k__BackingField. Theo như tôi đã thấy, tất cả các hiện thực của trình biên dịch C# hiện đang sử dụng lược đồ đặt tên này. Trình biên dịch VB.NET và F # sử dụng một lược đồ đặt tên khác sẽ không hoạt động với mã này.

private static FieldInfo GetBackingField(PropertyInfo pi) { 
    if (!pi.CanRead || !pi.GetGetMethod(nonPublic:true).IsDefined(typeof(CompilerGeneratedAttribute), inherit:true)) 
     return null; 
    var backingField = pi.DeclaringType.GetField($"<{pi.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); 
    if (backingField == null) 
     return null; 
    if (!backingField.IsDefined(typeof(CompilerGeneratedAttribute), inherit:true)) 
     return null; 
    return backingField; 
} 
Các vấn đề liên quan