2013-03-07 66 views
8

我發現自己不得不創建很多不可變類,我想找到一種方法來做到這一點,沒有多餘的信息。我不能使用匿名類型,因爲我需要從方法返回這些類。我想要智能支持,所以我不想使用字典,動態或類似的東西。我也想要有名的屬性,這排除了Tuple <>。到目前爲止,一些模式我已經試過:在C#中創建不可變類的最簡潔方法是什麼?

// 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; } 
} 

這是比創建只讀字段,通過構造函數設置它們,並通過屬性暴露它們的「標準」模式既有點繁瑣少。但是,這兩種方法仍然有很多冗餘的樣板。

任何想法?

+2

你的第二種形式不是不可變的 - 你可以通過提取'Field1'來觀察變化,然後設置它,然後再次獲取它。 (除非'SetOnce'在一組之前阻止提取,這是你沒有描述過的。) – 2013-03-07 22:39:40

+0

@JonSkeet SetOnce在set之前不會阻止提取(我會推薦這篇文章)。 – ChaseMedallion 2013-03-07 22:52:52

+1

不幸的是,對於你提出的問題沒有解決方案。語言設計團隊非常清楚這一點。希望這個問題有一天能夠得到解決。 – 2013-03-07 22:54:21

回答

1

查看是否public {get;private set;}屬性適用於您的案例 - 比單獨的字段聲明更緊湊,語義完全相同。

更新:由於ChaseMedallion在問題中評論和內聯,此方法不提供自動生成的GetHashCodeEquals方法,而不像Tuple方法。

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

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

我喜歡Tuple方法,因爲它提供了可以在有趣的環境中安全使用的對象,並提供了很好的名字。如果我需要創建多種類型的這樣那樣的我會考慮重新實現Tuple類:

  • 在構建時預先計算GetHashCode並存儲爲對象的一部分,以避免對集合/串無限的檢查。可能是可選的,以允許加入Dictionary中通常用作鍵的情況。
  • 隱藏通用名稱(即protected或只是隱藏起來,EditorBrowsableAttribute),所以不存在關於2組名稱的混淆。
  • 考慮執行字段類型是在調試不變的建立/ FxCop的規則...

旁註:退房埃裏克利珀的series on immutable types

4

你可以使用自動屬性與私營制定者

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

我只使用這個屬性類型。 減少代碼,並在相同的模式下管理成員更好.. – 2013-03-07 22:45:39

+0

我知道這種模式,但它看起來像Tuple <>模式一樣冗長,但佔用了更多的線條,並沒有讓你等於()和GetHashCode() – ChaseMedallion 2013-03-07 22:51:13

1

不可變類是最有用的,當存在其中,其內容可以很容易地從一個不變型實例中加載或複製到一個新的不可改變相應的可變類類型的實例。如果有一個不可變的對象需要做2-3次以上的「更改」(即更改引用,使其指向不可變的,與原始的適當不同),將數據複製到可變對象,更改它,並且將其存儲回去通常比創建與原始方式不同的對象更方便,然後創建與另一方式不同的新對象等。

一個很好的簡單泛化的方法是定義一個(可能是內部的)暴露域結構類型,然後讓可變類和不可變類都擁有該類型的字段。訪問器屬性必須爲兩個類類型單獨定義,但可以將兩種類型'GetHashCodeEquals方法鏈接到基礎結構的相應方法。給定另一個實例的任何一種類型的實例可以使用單個結構賦值來完成,而不必單獨複製所有成員。

如果程序員知道結構是如何工作的(我認爲這種知識是基礎的,即使其他人不同意),也可以使結構類型公開。即使結構是公開的,擁有可變持有者類也可能是有用的,因爲有時參考語義是有用的(例如,可能希望能夠改變存儲在Dictionary中的某些內容而不必改變Dictionary本身),但是convert-to-mutable/change/convert-to-immutable模式在結構類型方面的效果要好於類類型。

相關問題