2013-01-21 46 views
2

我一直在想這個問題一段時間,一直沒有找到關於這個主題的很多評論,也沒有能夠自己得出結論。不可變的只讀對象vs具有自動屬性的對象

創建對象時,接受最佳做法是使對象在暴露數據和允許操縱數據方面儘可能有限。如果可以使對象不可變,尤其是在多線程應用程序中,那麼最好這樣做。

話雖如此,C#似乎贊成開發人員不遵循此規則,允許類的定義更簡單,更重要的是,易於維護。

採取以下只讀不可變類:

public class ActiveDirectoryUser 
{ 
    private readonly string firstName, lastName, office, username, email; 

    public ActiveDirectoryUser(string firstName, string lastName, string office, string username, string email) 
    { 
     this.firstName = firstName; 
     this.lastName = lastName; 
     this.office = office; 
     this.username = username; 
     this.email = email; 
    } 

    public string FirstName 
    { 
     get { return firstName; } 
    } 

    public string LastName 
    { 
     get { return lastName; } 
    } 

    ... 

    public string Email 
    { 
     get { return email; } 
    } 
} 

而且如下比較它更簡單的例子,這是不是隻讀的。

public class ActiveDirectoryUser 
{ 
    public string FirstName 
    { 
     get; 
     set; 
    } 

    public string LastName 
    { 
     get; 
     set; 
    } 

    ... 

    public string Email 
    { 
     get; 
     set; 
    } 
} 

可與下面的實例化是:

ActiveDirectoryUser user = 
    new ActiveDirectoryUser 
     { FirstName= "Sam", LastName = "Shiles", ..., Email ="[email protected]" }; 

鑑於簡單的定義,代碼的行數更少,爲開發者錯誤的機會就越少,並且易於與其他開發人員可以瞭解代碼(特別是對於比我們簡單的例子更真實的例子),創建適當的只讀,不可變對象的價值是值得的嗎?

而且,做其他人認爲,在C#中做不可變對象應如作出與自動彼岸語法簡單:

public string FirstName {get; readonly set;} 
+3

你的第二個例子真的有用嗎?據我所知,你仍然需要提供一個構造函數。 –

+0

不,它不能,你的第二個例子不會工作 – devdigital

+0

對不起,更新了這個問題。 –

回答

8

鑑於定義越簡單,代碼行越少,開發人員錯誤的機會越少,並且其他開發人員可以輕鬆地理解代碼(特別是使用比我們的簡單示例更真實的示例),創建適當的只讀,不可變對象的價值是否值得?

是的。

我注意到,沒有什麼是從說

int Foo { get; private set; } 

,然後使用二傳手只能在構造函數中,以保證物業的不變性阻止你。你只是沒有得到漂亮的對象初始化語法,但你可以很容易地創建一個構造函數。

而且,做其他人認爲,在C#中做一個不可改變的對象應當具有自動彼岸的語法更容易...

是。特別是C#語言設計團隊認爲。 C#3.0增加了許多使C#更加「不變易用」語言的特性:匿名類型,查詢解析和表達式樹特別鼓勵不可變數據「功能」編程風格。它還添加了使C#成爲更「易變」語言的功能:自動屬性,對象初始值設定項和類型推斷數組可以讓人想起。

語言設計團隊非常清楚,後者的這些特性使得在DLR團隊製作大量不可變表達式樹類型庫的同時創建可變對象變得更加容易。諷刺並沒有讓他們失去,我可以向你保證。

圍繞語言設計團隊提出了許多建議,以便爲製作不可變對象提供更好的語法糖。事實上,需要可變性的對象初始值設定項與匿名類型初始值設定項幾乎相同的語法是不變的,這是一個明顯的探索起點,但不是唯一的。當然,首先,我不再代表語言設計團隊發言,其次,除了Roslyn項目之外,還沒有宣佈將來的C#版本。因此,對假設的未來語言版本的可能特徵集的猜測就是:我的猜測。

+0

您是否仍然不得不說現在您不在MS工作的「假設性語言的未來版本」?當然你知道是否C#6。0在你離開的時候已經預算了。 – MgSam

+3

@mgsam我現在對計劃瞭解的更少,而不是更多。假設我知道時間表,我不知道。我是否可能會破壞我與我的前團隊中與我的朋友的良好工作關係,並通過發佈祕密信息爲自己承擔法律責任? –

+0

我說,「*在你離開時*」,這意味着你的知識最好。毫無疑問,MS認爲這是他們是否繼續開發他們的旗艦語言的「祕密」。對於我們這些使用C#謀生的人來說,最好能比每兩年更頻繁地瞭解前面的事情;這個問題現在將會與C#團隊沒有公開的面孔混淆。 – MgSam

3

是建立適當的只讀,不可變對象的值,值得的成本?

這取決於您關心不可變性,線程安全以及您相信其他開發人員不會犯錯誤的程度。

我會問相反的 - 是否可能的線程錯誤的成本值得創建可變對象的價值非常快速和簡單?

+3

使C#更復雜的成本當然值得讓定義更容易的不可變類的價值。 – CodesInChaos

0

不要擔心你的第二個例子是不正確的(它不是不可變的,你應該添加一個構造函數,它的參數與你的字段匹配)我可以這樣說,它可以是隻讀修飾符表示你在寫類時的意圖。這意味着將來當其他人修改類時,他們會知道這些字段是隻讀的,而不是它們只是有效的只讀。

+0

沒有定義構造函數時,使用默認的構造函數,所以第二個例子是正確的。 – ken2k

+0

@ ken2k好點。雖然它仍然不是不變的。我會編輯它。 –

1

當我思考這些問題時,我傾向於將代碼分成幾個類別。

  • 應用水平
  • 內部框架水平
  • 公共API級別

在應用層面上,你的類將最經常是被設計到您的項目中實現一個非常特定的功能葉類範圍。它們很少用於重用,並且它們的唯一依賴關係是應用程序本身中的其他特定類。

在這裏,我允許自己一些自由,我會說,一些公共財產不傷害任何人。無論如何,它都是非常本地化的,隨着應用程序的變化,您仍然需要了解您的代碼如何交互以對此代碼進行任何更改。然而,自由並不等於草率。

在框架層面上,我指的是在項目之間高度可重用的子系統。可能是你的salt + hash密碼類,多年來已被證明可以工作。或者也許是您在幾個項目中依賴的整個第三方產品的包裝。

一旦你參考了這樣一個程序集,或者如果源文件被添加到你的解決方案中,你的應用程序仍然可以被看作是該代碼的唯一使用者。但是,該守則仍然必須非常謹慎,並應承諾遵守合同。如果公共屬性允許對象具有腐敗狀態,我會說這是一個相當蹩腳的框架。

在Api級別上,事物發生了巨大的變化。現在,您正在設計許多用戶應該使用的代碼。只是不要混淆術語,這裏明顯的候選人是.NET Framework本身。但如果你是提供api的人,那麼人們甚至可以爲保修付費;你真的想要一個穩定的系統,不容易被濫用。

我認爲有趣的(也有點兒有趣的)是所有來自OOP純粹主義者和純粹主義者(以及其他純粹主義者)的良好原則似乎都默認假設他們的所有代碼都在高端支持api級別。

因此,要回答你的問題,我給你幾個問題來思考

  • 請問這個對象改變狀態?
  • 其他代碼塊會改變腐敗假設嗎?
  • 如果是這樣,我的對象的任何理智的使用會被濫用嗎?
  • 如果我的對象被瘋狂虐待,這是一個本地錯誤還是主要支持問題?
  • 我在這裏需要什麼樣的保護水平?
  • 我應該付出多少努力?
  • 這是否帶有任何文檔?

一個明確的例子。在一個我很自豪的Web項目中,大量數據被緩存在單例中(真正的靜態類)。我們確實鎖定以確保GetOrSet上的線程安全。除了一個小細節。我們將對緩存中實際對象的引用分配給多個線程。他們是可變的,任何企圖寫入他們的公共財產可能會導致各種問題。現在我們克隆它們嗎?讓他們不變?採取複製這些對象的性能打擊,以便它們不會造成傷害?不。由於我們是我們自己緩存的唯一消費者,因此我們只是決定將來自服務的所有數據視爲只讀。當我們需要複製時,我們使用Automapper。我們在代碼審查期間對此進行檢查。它工作得很好。雖然我們有一些錯誤,但還沒有寫入可變對象造成的。

病人: - 當我像這樣跳躍時,我的腿受傷。

醫生: - 那麼不要這樣跳!

+0

真的很好的答案。如果不是來自埃裏克的回覆,這將是答案。 –

+0

我可以住在那=) –

+0

說到埃裏克。比較我的緩存策略和Roslyn的設計。恰恰相反。我的代碼將被我的代碼使用,而Roslyn將被其他人使用。 –