2013-03-07 25 views
8

Tôi thấy mình phải tạo ra rất nhiều lớp học bất biến và tôi muốn tìm cách để làm điều đó mà không cần thông tin dư thừa. Tôi không thể sử dụng một kiểu ẩn danh vì tôi cần trả về các lớp này từ các phương thức. Tôi muốn hỗ trợ intellisense, vì vậy tôi không muốn sử dụng từ điển, năng động hoặc bất cứ điều gì như thế. Tôi cũng muốn các thuộc tính được đặt tên tốt, quy định ra Tuple <>. Cho đến nay, một số mô hình tôi đã cố gắng:Cách súc tích nhất để tạo ra một lớp không thay đổi trong C# là gì?

// inherit Tuple<>. This has the added benefit of giving you Equals() and GetHashCode() 
public class MyImmutable : Tuple<int, string, bool> { 
    public MyImmutable(int field1, string field2, bool field3) : base(field1, field2, field3) { } 

    public int Field1 { get { return this.Item1; } } 
    public string Field2 { get { return this.Item2; } } 
    public bool Field3 { get { return this.Item3; } } 
} 

/////////////////////////////////////////////////////////////////////////////////// 

// using a custom SetOnce<T> struct that throws an error if set twice or if read before being set 
// the nice thing about this approach is that you can skip writing a constructor and 
// use object initializer syntax. 
public class MyImmutable { 
    private SetOnce<int> _field1; 
    private SetOnce<string> _field2; 
    private SetOnce<bool> _field3; 


    public int Field1 { get { return this._field1.Value; } set { this._field1.Value = value; } 
    public string Field2 { get { return this._field2.Value; } set { this._field2.Value = value; } 
    public bool Field3 { get { return this._field3.Value; } set { this._field3.Value = value; } 
} 

/////////////////////////////////////////////////////////////////////////////////// 

// EDIT: another idea I thought of: create an Immutable<T> type which allows you to 
// easily expose types with simple get/set properties as immutable 
public class Immutable<T> { 
    private readonly Dictionary<PropertyInfo, object> _values;  

    public Immutable(T obj) { 
     // if we are worried about the performance of this reflection, we could always statically cache 
     // the getters as compiled delegates 
     this._values = typeof(T).GetProperties() 
      .Where(pi => pi.CanRead) 
      // Utils.MemberComparer is a static IEqualityComparer that correctly compares 
      // members so that ReflectedType is ignored 
      .ToDictionary(pi => pi, pi => pi.GetValue(obj, null), Utils.MemberComparer); 
    } 

    public TProperty Get<TProperty>(Expression<Func<T, TProperty>> propertyAccessor) { 
     var prop = (PropertyInfo)((MemberExpression)propertyAccessor.Body).Member; 
     return (TProperty)this._values[prop]; 
    } 
} 

// usage 
public class Mutable { int A { get; set; } } 

// we could easily write a ToImmutable extension that would give us type inference 
var immutable = new Immutable<Mutable>(new Mutable { A = 5 }); 
var a = immutable.Get(m => m.A); 

// obviously, this is less performant than the other suggestions and somewhat clumsier to use. 
// However, it does make declaring the immutable type quite concise, and has the advantage that we can make 
// any mutable type immutable 

/////////////////////////////////////////////////////////////////////////////////// 

// EDIT: Phil Patterson and others mentioned the following pattern 
// this seems to be roughly the same # characters as with Tuple<>, but results in many 
// more lines and doesn't give you free Equals() and GetHashCode() 
public class MyImmutable 
{ 
    public MyImmutable(int field1, string field2, bool field3) 
    { 
     Field1 = field1; 
     Field2 = field2; 
     Field3 = field3; 
    } 

    public int Field1 { get; private set; } 
    public string Field2 { get; private set; } 
    public bool Field3 { get; private set; } 
} 

Cả hai đều là hơi ít tiết hơn so với mô hình "chuẩn" của việc tạo ra các lĩnh vực readonly, đặt chúng thông qua một nhà xây dựng, và phơi bày chúng thông qua thuộc tính. Tuy nhiên, cả hai phương pháp này vẫn có nhiều bản mẫu dự phòng.

Bất kỳ ý tưởng nào?

+2

Biểu mẫu thứ hai của bạn không thay đổi - bạn có thể quan sát thay đổi bằng cách tìm nạp 'Field1', sau đó đặt nó, sau đó tìm nạp lại. (Trừ khi 'SetOnce' ngăn cản tìm nạp trước một tập hợp, mà bạn chưa mô tả.) –

+0

@JonSkeet SetOnce ngăn chặn tìm nạp trước khi được thiết lập (Tôi sẽ ghi lại bài đăng). – ChaseMedallion

+1

Thật không may là không có giải pháp slam-dunk cho vấn đề bạn đặt ra. Nhóm thiết kế ngôn ngữ nhận thức rõ điều đó. Hy vọng rằng một ngày nào đó sẽ được giải quyết. –

Trả lời

1

Xem nếu public {get;private set;} thuộc tính hoạt động cho trường hợp của bạn - nhỏ hơn một chút so với khai báo trường riêng biệt, chính xác cùng một ngữ nghĩa.

Cập nhật: Như ChaseMedallion đã nhận xét và được nêu trong câu hỏi, cách tiếp cận này không cung cấp phương thức tự động được tạo ra GetHashCodeEquals không giống như phương pháp Tuple.

class MyImmutable 
{ 
    public int MyProperty {get; private set;} 

    public MyImmutable(int myProperty) 
    { 
     MyProperty = v; 
    } 
} 

Cách tiếp cận có thể sử dụng an toàn trong ngữ cảnh thú vị và cung cấp tên đẹp. Nếu tôi sẽ cần phải tạo nhiều loại thuộc loại đó tôi sẽ xem xét để tái thực hiện Tuple lớp:

  • lúc xây dựng trước tính toán GetHashCode và lưu trữ như một phần của đối tượng để tránh kiểm tra vô biên cho các bộ sưu tập/chuỗi. Có thể tùy chọn để cho phép chọn tham gia cho các trường hợp thường được sử dụng làm khóa trong Dictionary.
  • giấu tên chung (ví dụ: protected hoặc chỉ ẩn nội dung với EditorBrowsableAttribute) để không có nhầm lẫn về 2 bộ tên.
  • xem xét thực thi kiểu trường này để không thay đổi trong Debug xây dựng/quy tắc FxCop ...

Side lưu ý: kiểm tra Eric Lippert của series on immutable types.

4

Bạn có thể sử dụng các thuộc tính tự động với setters tin

public class MyImmutable 
{ 
    public MyImmutable(int field1, string field2, bool field3) 
    { 
     Field1 = field1; 
     Field2 = field2; 
     Field3 = field3; 
    } 

    public int Field1 { get; private set; } 
    public string Field2 { get; private set; } 
    public bool Field3 { get; private set; } 
} 
+0

Tôi chỉ sử dụng loại thuộc tính này. Viết ít mã hơn và quản lý thành viên ở cùng một chế độ tốt hơn .. –

+0

Tôi biết về mẫu này, nhưng nó có vẻ như là mô hình Tuple <>, nhưng chiếm nhiều dòng hơn và không giúp bạn bằng() và GetHashCode() – ChaseMedallion

1

lớp Immutable hữu ích nhất khi có tồn tại một lớp học có thể thay đổi tương ứng mà có nội dung có thể dễ dàng được nạp từ một thể hiện bất biến kiểu hoặc sao chép vào một bất biến mới -trường hợp kiểu. Nếu có một đối tượng bất biến cần phải thực hiện hơn 2-3 "thay đổi" cho nó (nghĩa là thay đổi tham chiếu để nó trỏ đến một biến đổi khác với bản gốc), sao chép dữ liệu vào đối tượng có thể thay đổi, thay đổi nó, và lưu trữ nó trở lại thường thực tế hơn là tạo một đối tượng khác với bản gốc theo một cách, sau đó tạo một đối tượng mới khác với đối tượng đó theo cách khác, v.v.

Cách tiếp cận dễ dàng tổng quát là xác định (có thể là nội bộ) kiểu cấu trúc trường tiếp xúc, và sau đó có cả các lớp có thể thay đổi và bất biến giữ một trường kiểu đó. Các thuộc tính accessor sẽ phải được định nghĩa riêng cho cả hai loại lớp, nhưng có thể có cả hai loại phương thức 'GetHashCodeEquals' với các phương thức tương ứng của cấu trúc cơ bản.Việc tạo một cá thể của một trong hai kiểu được đưa ra một thể hiện của một kiểu khác có thể được thực hiện bằng cách sử dụng một phép gán cấu trúc đơn, thay vì phải sao chép tất cả các thành viên một cách riêng lẻ.

Nếu lập trình viên của một người hiểu cấu trúc hoạt động như thế nào (tôi coi cơ sở tri thức đó là cơ bản, ngay cả khi những người khác không đồng ý). Có một lớp chủ có thể thay đổi có thể hữu ích ngay cả khi cấu trúc là công khai, vì có những lúc ngữ nghĩa tham chiếu hữu ích (ví dụ: có thể muốn thay đổi thứ gì đó được lưu trữ trong Dictionary mà không cần phải tự thay đổi chính mình Dictionary) mô hình chuyển đổi thành có thể thay đổi/thay đổi/chuyển đổi thành không thay đổi hoạt động tốt hơn với các loại cấu trúc hơn so với các loại lớp.

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