《Effective C#》Item 1:用屬性來訪問類的私有成員

在程序中,難免要訪問某個對象的私有成員。那麼以前實現這類功能的方法有兩種,第一種方法最簡單,就是把成員訪問符從“private”改爲“public”即可;而另一個就是提供公有的成員訪問函數來進行訪問。那麼現在用C#編寫程序,就不再需要採用前面所說的兩種方法了,而直接使用屬性來完成。

 

首先來看看三種方法的如何實現以及調用的,這裏用一個例子來說明,即訪問“EmployeeInfo”類的私有成員strName,具體如下表格所示。

 

private string strName;

訪問方法

修改成員訪問符

修改

private string strName;

public string strName;

EmployeeInfo empNew...;

string strNameValue = empNew.strName;

empNew.strName = "me";

 

公有成員函數

增加如下兩個成員函數

public string getName()

{

    return  strName;

}

public void setName( string Name )

{

    strName = Name;

}

 

EmployeeInfo empNew...;

string strNameValue = empNew.getName();

empNew.setName( "me" );

 

屬性

增加如下屬性

public string Name

{

    get{ return strName;}

    set{ strName = value; }

}

EmployeeInfo empNew...;

string strNameValue = empNew.Name;

empNew.Name = "me";

 

 

那麼這三種方法有什麼區別呢,用如下的表格,可以更好的說明三者的區別。

 

類的封裝性

代碼安全性

代碼繁瑣性

代碼效率

修改成員訪問符

破壞類的封裝

存在潛在危險

簡便

最高

公有成員函數

沒有破壞

安全

繁瑣,而且調用不直接

最低

屬性

沒有破壞

安全

簡便

僅次於第一種方法

(備註:這裏用紅色表明每一子項中最不好的)

 

       因此可以看出使用屬性不但沒有破壞類的封裝性,沒有減弱代碼的安全性,而且和第一種方法一樣簡便,只是在效率方面要略低於第一種方法。但總體看來,在C#中用屬性來訪問類的私有成員是不二的選擇。

 

不過對於使用屬性,以及如上表格所說的,難免會有人產生如下一些疑問。

疑問一:就是用屬性是否能達到成員函數那樣的效果,即完成複雜的代碼操作。

其實屬性的底層實現是藉助於成員函數,只不過這部分轉換是由系統幫忙做的,所以在編寫屬性的時候,可以像編寫成員函數一樣,即在成員函數中所能寫的代碼片斷,完全可以在屬性中套用。下面就貼出屬性所轉換的微軟中間語言(MSIL)代碼。

.property instance string Name()

{

       .get instance string NameSpace.EmployeeInfo::get_Name()

       .set instance void NameSpace.EmployeeInfo::set_Name(string)

}// end of property EmployeeInfo::Name

 

.method public hidebysig specialname instance string get_Name() cil managed

{

          ...

}// end of method EmployeeInfo::get_Name

 

.method public hidebysig specialname instance void set_Name(string 'value') cil managed

{

          ...

}// end of method EmployeeInfo::set_Name

 

如上就是前面EmployeeInfo類的Name屬性所轉換的中間語言代碼(不過省略了函數的具體實現代碼,因爲這裏並不是爲了研究中間語言代碼,如果需要對這部分有更多地瞭解,參看中間語言相關書籍)。

 

疑問二:就是用屬性的效率是否僅次於第一種方法。

從上面很容易看出,屬性在編譯的時候會轉換成和成員函數一樣的代碼,那麼它的效率應該和成員函數是一樣的。其實並不是這樣,因爲JIT編譯器會把屬性所轉換成的兩個成員函數作爲內聯函數,這樣效率會提高很多。(注:內聯函數是代碼被插入到調用者代碼處的函數,通過避免函數調用所產生的額外開銷,從而提高執行效率。不過書中也提到,即使不是內聯函數,成員函數相對於方法一的效率損失也是微乎其微的。)

 

C#寫程序,一提到屬性,大家都會編寫。其實在屬性中,可以產生很多應用,接着來就分別說明。

1.  在屬性中使用索引符,例如像“ArrayList[i]”來訪問ArrayList某個成員。這裏需要注意的是,屬性名以及索引參數的編碼格式是固定的,如“this […]”。不過索引參數可以是多個,而且不光支持整型參數,還可以使用其他類型參數。例如:

public ReturnValueType this[ ParType1 parValue1, ParType2 parValue2]

{

    get{...}

    set{...}

}

 

2.  可以給屬性操作加上互斥鎖,以防止多線程操作時而產生的併發錯誤,具體如下。

public string Name

{

    get

    {

        lock(this)

        {

            return strName;

        }

    }

    set

    {

        lock(this)

        {

            strName = value;

        }

    }

}

 

3.  書上還提到屬性的其他應用,例如:通過接口來實現在一個類中同時提供只讀屬性以及非只讀屬性。但是我個人認爲,雖然這樣可以實現,但是會產生歧義,即在一個類中提供兩個不同版本的屬性,破壞了類的一致性,所以我並不推薦這麼做。

 

接着,要說說編寫屬性的時候,需要注意些什麼,我個人認爲有如下兩點大的方面。

第一個就是編寫屬性get部分的時候,如果當前屬性的類型是引用類型的話,且不想通過屬性來修改局部成員的話,最好返回局部成員的copy,而不是成員本身。

例如:

    public class class1

    {

        string _data;

        public class1( string data )

        {

            _data = data;

        }

 

        public string Data

        {

            get{ return _data;}

            set{ _data = value;}

        }

    }

 

    public class class2

    {

        private class1 myClass1 = null;

        public class1 Class1

        {

            get{ return myClass1; }

        }

 

        public string Data

        {

            get{ return myClass1.Data;}

        }

 

        public class2( string data )

        {

            myClass1 = new class1( data );

        }

    }

 

如果按照如上所寫,那麼class2對象可以通過Class1.Data屬性訪問和修改局部成員myClass1某些值,這樣就可以修改了myClass2的私有成員myClass1的值,即會產生潛在錯誤。

例如:

    class1 myClass1 = myClass2.Class1;

    myClass1.Data = "test2";

 

如何避免這類錯誤呢,那麼首先需要修改Class1屬性的編寫,其次在class1類需要提供Clone函數或者其他copy函數,具體如下

    public class class1:ICloneable

    {

        string _data;

        public class1( string data )

        {

            _data = data;

        }

 

        public string Data

        {

            get{ return _data;}

            set{ _data = value;}

        }

 

        #region ICloneable Members

 

        public object Clone()

        {

            // TODO:  Add class1.Clone implementation

            return new class1( _data );

        }

 

        #endregion

    }

 

    public class class2

    {

        private class1 myClass1 = null;

        public class1 Class1

        {

            get{ return myClass1.Clone() as class1; }

        }

 

        public string Data

        {

            get{ return myClass1.Data;}

        }

 

        public class2( string data )

        {

            myClass1 = new class1( data );

        }

    }

 

第二個需要注意的是編寫屬性set部分的時候,這裏需要對參數進行有效性檢查。因爲屬性是外界修改類的私有成員的入口,爲了避免因爲私有成員不正確而產生的錯誤,所以在進行屬性set的時候要進行有效性檢查,從而保證私有成員對於整個類來說是有效的。

 

那麼在實際應用當中,與屬性密切相關的就是實現兩個窗體之間數據訪問,這可能是寫WinForm程序最基本的。不過很遺憾的是,網上在回答此類問題的時候,很多人都建議用第一種方法來解決。爲此,我也專門寫了一篇文章,詳情參看:

http://blog.csdn.net/knight94/archive/2006/03/18/628285.aspx

 

可能之前的文章沒有這篇說的那麼透徹,所以我也希望大家在看完之後,能真正的把屬性應用到程序當中。

發佈了87 篇原創文章 · 獲贊 9 · 訪問量 100萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章