[C#.NET] 應該避免屬性回傳類別,以免類別方法破壞內部結構

  • 9073
  • 0
  • 2013-07-05

[C#.NET] 應該避免屬性回傳類別,以免類別方法破壞內部結構

在上篇提到在設計一個類別時,我們應該使用屬性來處理取代全域變數,用來保護變數的存取權限,就像以下程式碼,我們可以確保它在類別裡回傳字串或其它實值型別,不會被用戶端修改,因為這些類別當中並沒有可以改變內部狀態的方法。

public class Class1
{
    public string Address { get; private set; }
    public string Name { get; private set; }
    public int Age { get; private set; }

    public void GetInfo()
    {
        this.Address = "高雄市";
        this.Age = 20;
        this.Name = "余小章";
    }
}

什麼叫改變內部狀態?我們來看看以下程式碼,我設計一個GetValue方法,Datas屬性會得到GateValue方法的結果;以下程式碼看起來很好,我們原本是要設計了一個不開放修改的屬性,但是確忽略了回傳的類別裡的方法可以修改屬性內部結構,往往大部份的人都會忽略掉這一點,反而替用心良苦的設計的類別開了個後門。

類別程式碼

public class Class1
{

    private List<int> _Datas = null;
    public List<int> Datas
    {
        get
        {
            if (this._Datas == null)
                this._Datas = new List<int>();
            return this._Datas;
        }
    }
    Random _random = new Random();
    public void GetValue()
    {
        for (int i = 0; i < 5; i++)
        {
            this.Datas.Add(this._random.Next(1, 1000));
        }
    }
}

用戶端程式碼

static void Main(string[] args)
{
    Class1 c = new Class1();
    c.GetValue();
    List<int> list = c.Datas;
    c.Datas.Add(0);
    c.Datas.ForEach((i) => Console.WriteLine(i.ToString()));
    Console.ReadKey();
}
 

用心良苦關的門,卻忘了把拔掉鎖頭上的鑰匙,讓其他人闖了進來,c.Datas.Add(0)就是最好的見証。

image


我把程式碼改成如下,對於公開的Datas返回IEnumerable,而不是List<int>,以便實施資料繫結,當然不限定只能返回IEnumerable介面,在這裡剛好使用IEnumerable能解決問題。

private List<int> _Datas = new List<int>();
public IEnumerable<int> Datas
{
    get { return this._Datas; }
}
Random _random = new Random();
public void GetValue()
{
    for (int i = 0; i < 5; i++)
    {
        this._Datas.Add(this._random.Next(1, 1000));
    }
}

用戶端程呼叫:

private void button2_Click(object sender, EventArgs e)
{
    Class1 c = new Class1();
    c.GetValue();
    var list = c.Datas;
    foreach (var item in list)
    {
        Console.WriteLine(item.ToString());
    }
    this.comboBox1.DataSource = list;
}

或者使用 ReadOnlyCollection 來保護

private List<int> _Datas = new List<int>();
public ReadOnlyCollection<int> Datas
{
    get
    {
        return new ReadOnlyCollection<int>(this._Datas);
    }
}
Random _random = new Random();
public void GetValue()
{
    for (int i = 0; i < 5; i++)
    {
        this._Datas.Add(this._random.Next(1, 1000));
    }
} 

以上兩個方法都沒公開類別的方法,用戶端自然就沒有辦法修改內部結構。

 


再舉個有問題的簡單例子,我有以下兩個類別Class1及Class2,Class2裡回傳了一個Class1的Data公開屬性

public class Class1
{
    public string Name { get; internal set; }
    public void GetName()
    {
        this.Name = "余小章 by Class1";
    }

}

public class Class2
{
    private Class1 _Data = new Class1();
    public Class1 Data 
    {
        get { return this._Data; } 
    }
    public void SetName(string Name)
    {
        this._Data.Name = Name;
    }
}

在用戶端的呼叫:

private void button3_Click(object sender, EventArgs e)
{
    Class2 c2 = new Class2();
    c2.SetName("Test");
    c2.Data.GetName();//故意破壞內部結構
    MessageBox.Show(c2.Data.Name);
}

原本只是想要公開c2.Data的屬性而已,卻沒注意到c2.Data的方法可以修改內部結構,事實上這樣的設計是有問題的,我們不應該公開在Class2中的Data屬性!

 


後記:

類別的資訊若要公開只希望用戶端查閱,應該要小心注意類別內有無改變結構的方法。

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo