2010-04-27 35 views
13

Tôi có một lớp học với nhiều thuộc tính công khai khác nhau mà tôi cho phép người dùng chỉnh sửa thông qua lưới thuộc tính. Đối với sự kiên trì, lớp này cũng được tuần tự hóa/deserialized thành/từ một tệp XML thông qua DataContractSerializer.Làm cách nào để tạo phiên bản chỉ đọc của một lớp học?

Đôi khi tôi muốn người dùng có thể lưu (thay đổi) các thay đổi mà họ đã thực hiện cho một phiên bản của lớp. Tuy nhiên, vào những lúc khác, tôi không muốn cho phép người dùng lưu thay đổi của họ và thay vào đó, hãy xem tất cả thuộc tính trong lưới thuộc tính là chỉ đọc. Tôi không muốn cho phép người dùng thực hiện thay đổi mà họ sẽ không bao giờ có thể lưu sau này. Tương tự như cách MS Word sẽ cho phép người dùng mở các tài liệu hiện đang được mở bởi người khác nhưng chỉ được đọc dưới dạng chỉ đọc.

Lớp của tôi có thuộc tính boolean xác định xem lớp có nên chỉ đọc hay không, nhưng có thể sử dụng thuộc tính này bằng cách nào đó thêm thuộc tính chỉ đọc vào thuộc tính lớp vào thời gian chạy không? Nếu không phải là một giải pháp thay thế là gì? Tôi có nên quấn lớp của mình trong lớp trình bao bọc chỉ đọc không?

Trả lời

16

Tính bất biến là khu vực mà C# vẫn còn chỗ để cải thiện. Trong khi tạo các loại bất biến đơn giản với các thuộc tính readonly là có thể, một khi bạn cần kiểm soát phức tạp hơn khi loại có thể thay đổi bạn bắt đầu chạy vào chướng ngại vật.

Có ba lựa chọn mà bạn có, tùy thuộc vào cách mạnh bạn cần phải "thực thi" read-only hành vi:

  1. Sử dụng một chỉ đọc lá cờ trong loại của bạn (như bạn đang làm) và để người gọi có trách nhiệm không cố gắng thay đổi thuộc tính trên loại - nếu một nỗ lực viết được thực hiện, hãy ném một ngoại lệ.

  2. Tạo giao diện chỉ đọc và để loại của bạn triển khai giao diện đó. Bằng cách này, bạn có thể chuyển loại thông qua giao diện đó tới mã chỉ nên thực hiện lần đọc.

  3. Tạo lớp trình bao kết hợp loại của bạn và chỉ hiển thị các hoạt động đọc.

Tùy chọn đầu tiên thường là dễ dàng nhất, ở chỗ nó có thể đòi hỏi ít refactoring mã hiện có, nhưng cung cấp cơ hội nhất cho tác giả của một loại để thông báo cho người tiêu dùng khi một thể hiện là không thay đổi so với khi nó là không phải. Tùy chọn này cũng cung cấp sự hỗ trợ tối thiểu từ trình biên dịch trong việc phát hiện việc sử dụng không phù hợp - và từ chối phát hiện lỗi đến thời gian chạy.

Tùy chọn thứ hai thuận tiện, vì việc triển khai giao diện là có thể mà không cần nỗ lực tái cấu trúc nhiều. Thật không may, người gọi vẫn có thể truyền sang loại cơ bản và cố gắng viết chống lại nó. Thông thường, tùy chọn này được kết hợp với cờ chỉ đọc để đảm bảo tính bất biến không bị vi phạm.

Tùy chọn thứ ba là mạnh nhất, theo như thực thi, nhưng có thể dẫn đến trùng lặp mã và có nhiều nỗ lực tái cấu trúc hơn. Thông thường, sẽ hữu ích khi kết hợp tùy chọn 2 và 3 để tạo mối quan hệ giữa trình bao bọc chỉ đọc và đa hình loại có thể thay đổi.

Cá nhân, tôi có xu hướng suy ra tùy chọn thứ ba khi viết mã mới mà tôi mong đợi cần thực thi bất biến. Tôi thích thực tế rằng nó không thể "bỏ đi" các wrapper bất biến, và nó thường cho phép bạn tránh viết lộn xộn nếu chỉ đọc-ném-ngoại trừ kiểm tra vào mỗi setter.

+0

Cảm ơn! Thật không may tùy chọn 2 không làm việc cho tôi vì lưới thuộc tính sẽ sử dụng sự phản chiếu để tìm ra các thuộc tính thực sự đằng sau một giao diện trong thực tế không chỉ đọc. Vì vậy, có vẻ như tùy chọn 3 là câu trả lời tốt nhất cho tôi. Mặc dù tôi thích đề xuất của bạn về việc kết hợp các tùy chọn 2 và 3. –

+0

Tôi không thể diễn tả câu trả lời này là một trong những câu trả lời ngắn gọn và hoàn chỉnh nhất mà tôi tìm thấy trên web. –

1

Tôi sẽ sử dụng lớp trình bao bọc để giữ mọi thứ chỉ đọc. Điều này là dành cho khả năng mở rộng, độ tin cậy và khả năng đọc chung.

Tôi không thấy trước bất kỳ phương pháp nào khác để thực hiện việc này sẽ cung cấp ba lợi ích đã đề cập ở trên cũng như một số nội dung khác. Chắc chắn sử dụng một lớp bao bọc ở đây theo ý kiến ​​của tôi.

2

Tại sao không phải cái gì như:

private int someValue; 
public int SomeValue 
{ 
    get 
    { 
     return someValue; 
    } 
    set 
    { 
     if(ReadOnly) 
       throw new InvalidOperationException("Object is readonly"); 
     someValue= value; 
    } 
+1

Bởi vì nó sẽ không hoạt động (như mong đợi) cho các loại tham chiếu. Mã của bạn tương đương với từ khóa chỉ đọc, nó chỉ mơ hồ hơn. – greenoldman

+0

Tại sao nó sẽ tương đương với từ khóa chỉ đọc? Đây là từ khóa năng động, chỉ đọc là không. –

0

Would một cái gì đó giống như sự giúp đỡ này:

class Class1 
{ 
    private bool _isReadOnly; 

    private int _property1; 
    public int Property1 
    { 
     get 
     { 
      return _property1; 
     } 
     set 
     { 
      if (_isReadOnly) 
       throw new Exception("At the moment this is ready only property."); 
      _property1 = value; 
     } 
    } 
} 

Bạn cần phải bắt ngoại lệ khi thiết lập các thuộc.

Tôi hy vọng đây là điều bạn đang tìm kiếm.

+0

Điều đó sẽ luôn luôn ném ngoại lệ, bạn không bao giờ nên ném một trường hợp ngoại lệ bằng cách này. –

+0

@Oskar Đúng, tôi đã thay đổi nó. – user218447

0

Bạn không thể nhận được kiểm tra thời gian biên dịch (như được cung cấp với từ khóa readonly) bằng cách thay đổi thuộc tính thành chỉ đọc khi chạy. Vì vậy, không có cách nào khác, như để kiểm tra thủ công và ném một ngoại lệ.

Nhưng tốt hơn là nên thiết kế lại quyền truy cập vào lớp học. Ví dụ: tạo "lớp nhà văn", kiểm tra xem liệu lớp dữ liệu "underling" có thể được viết hay không.

0

Bạn có thể sử dụng PostSharp để tạo OnFieldAccessAspect sẽ không chuyển giá trị mới cho bất kỳ trường nào khi _readOnly sẽ được đặt thành true. Với sự lặp lại mã khía cạnh đã biến mất và sẽ không có trường bị lãng quên.

1

Nếu bạn đang tạo thư viện, bạn có thể xác định giao diện công khai với lớp riêng/nội bộ. Bất kỳ phương thức nào cần trả lại một thể hiện của lớp chỉ đọc của bạn cho một người tiêu dùng bên ngoài, thay vào đó, hãy trả về một thể hiện của giao diện chỉ đọc thay thế. Giờ đây, việc truyền xuống một loại cụ thể là không thể vì loại không được hiển thị công khai.

Utility Library

public interface IReadOnlyClass 
{ 
    string SomeProperty { get; } 
    int Foo(); 
} 
public interface IMutableClass 
{ 
    string SomeProperty { set; } 
    void Foo(int arg); 
} 

thư viện của bạn

internal MyReadOnlyClass : IReadOnlyClass, IMutableClass 
{ 
    public string SomeProperty { get; set; } 
    public int Foo() 
    { 
     return 4; // chosen by fair dice roll 
        // guaranteed to be random 
    } 
    public void Foo(int arg) 
    { 
     this.SomeProperty = arg.ToString(); 
    } 
} 
public SomeClass 
{ 
    private MyThing = new MyReadOnlyClass(); 

    public IReadOnlyClass GetThing 
    { 
     get 
     { 
      return MyThing as IReadOnlyClass; 
     } 
    } 
    public IMutableClass GetATotallyDifferentThing 
    { 
     get 
     { 
      return MyThing as IMutableClass 
     } 
    } 
} 

Bây giờ, bất cứ ai sử dụng SomeClass sẽ liên lạc lại gì trông giống như hai đối tượng khác nhau. Tất nhiên, họ có thể sử dụng phản ánh để xem các loại cơ bản, điều này sẽ cho họ biết rằng đây thực sự là cùng một đối tượng có cùng loại. Nhưng định nghĩa của loại đó là riêng tư trong một thư viện bên ngoài. Tại thời điểm này, nó vẫn là kỹ thuật có thể để có được ở định nghĩa, nhưng nó đòi hỏi Heavy Wizardry để kéo ra.

Tùy thuộc vào dự án của bạn, bạn có thể kết hợp các thư viện trên thành một. Không có gì ngăn cản điều đó; chỉ cần không bao gồm các mã trên trong bất kỳ DLL bạn muốn hạn chế quyền truy cập của.

Tín dụng cho XKCD cho nhận xét.

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