Effective C# Item23:避免返回內部類對象的引用

    當我們將屬性置爲只讀後,就可以認爲調用者對該屬性的值不可以進行更改了嗎?答案是否定的。對於值類型來說,將其置爲只讀,確實可以讓調用者不能夠對其進行修改;但是對於引用類型來說,調用者還是可以調用引用對象的公有成員,包括那些可以修改屬性狀態的成員。

    我們來看下面的代碼。

    public struct Employee
    {
        private string m_strName;
        public string Name
        {
            get { return m_strName; }
            set { m_strName = value; }
        }

        private int m_nAge;
        public int Age
        {
            get { return m_nAge; }
            set { m_nAge = value; }
        }

        public Employee(string name, int age)
        {
            m_strName = name;
            m_nAge = age;
        }

        public override string ToString()
        {
            return string.Format("Name : {0}, Age : {1}.", m_strName, m_nAge);
        }
    }


    下面是測試代碼。

    private static void Test()
    {
        Employee emp = new Employee("Wing", 24);
        Console.WriteLine(emp.ToString());
        ChangeName(emp.Name);
        ChangeAge(emp.Age);
        Console.WriteLine(emp.ToString());
        ChangeEmp(emp);
        Console.WriteLine(emp.ToString());

    }

    private static int ChangeAge(int age)
    {
        age += 1;
        return age;
    }

    private static string ChangeName(string name)
    {
        name = "UnKnown";
        return name;
    }

    private static void ChangeEmp(Employee emp)
    {
        emp.Name = "Unkown";
        emp.Age += 1;
    }


    下面是執行結果。

    通過上述代碼,我們可以看到,對於值類型來說,在方法傳遞的過程中,是將對應的值進行復制後傳遞的;但是對於引用類型來說,是直接傳遞的引用對象的值。因此,通過調用方法對值類型的改變並不會對原有值類型的值,但是對於引用類型來說,就直接改變了原有引用對象的值。

    我們可以通過以下四種方式來防止類型內部的數據結構遭到無意的改變:

  1. 值類型,就像上面代碼中的Age,屬於int類型。
  2. 常量類型,就像上面代碼中的Name,屬於string類型,對它的任何改變,都會直接創建一個新的string對象。
  3. 接口,在方法傳遞的過程中,不直接傳遞類的類型,而改用類實現的接口的類型,這樣可以降低調用方可以調用的方法,它只能調用接口中定義的成員。
  4. 包裝器,我們可以新建一個類,用於返回指定類型的數據,在返回的邏輯中,可以添加邏輯,對返回值是否只讀進行限制。

    上述四種方式中,前三種方式都已經在示例代碼中進行說明了,我們主要看第四種方式,來看下面的代碼。

public class MyBusinessObject
{
  // Read Only property providing access to a
  // private data member:
   private DataSet _ds;
  public IList this[ string tableName ]
  {
    get
    {
      DataView view =
        _ds.DefaultViewManager.CreateDataView
        ( _ds.Tables[ tableName ] );
      view.AllowNew = false;
      view.AllowDelete = false;
      view.AllowEdit = false;
      return view;
    }
  }
}


// Access the dataset:
    IList dv = bizOjb[ "customers" ];
    foreach ( DataRowView r in dv )
      Console.WriteLine( r[ "name" ] );


    上述代碼中,結合了第三種方式和第四種方式,首先屬性的返回值由DateView變成了IList,這樣調用方只能夠調用IList類型中定義的成員;其次,我們在返回IList之前,將DataView的AllowNew、AllowDelete和AllowEdit屬性都設置爲false,這樣也能夠阻止調用方對其進行改動。 

    總結:將引用類型通過公共接口暴露給外界,將使得類型的用戶不用通過我們定義的方法和屬性,就能夠更改對象的內部結構,這違反了我們通常的直覺,會導致常見的錯誤。如果我們到處的是引用而非值,那就需要改變類型的接口,如果只是簡單的返回內部數據,那麼我們實際上就給外界賦予了訪問內部成員的權限。客戶代碼可以調用成員中任何可用的方法。通過使用接口或者包裝器對象向外界提供內部的私有數據,我們可以限制外界對它們的訪問能力。當希望客戶代碼更改內部數據元素時,我們應該實現Observer模式,以使對象可以對更改進行校驗或響應。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章