在程序中,难免要访问某个对象的私有成员。那么以前实现这类功能的方法有两种,第一种方法最简单,就是把成员访问符从“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#写程序,一提到属性,大家都会编写。其实在属性中,可以产生很多应用,接着来就分别说明。
<!--[if !supportlists]-->1. <!--[endif]-->在属性中使用索引符,例如像“arraylist[i]”来访问arraylist某个成员。这里需要注意的是,属性名以及索引参数的编码格式是固定的,如“this […]”。不过索引参数可以是多个,而且不光支持整型参数,还可以使用其他类型参数。例如:
public returnvaluetype this[ partype1 parvalue1, partype2 parvalue2]
{
get{...}
set{...}
}
<!--[if !supportlists]-->2. <!--[endif]-->可以给属性操作加上互斥锁,以防止多线程操作时而产生的并发错误,具体如下。
public string name
{
get
{
lock(this)
{
return strname;
}
}
set
{
lock(this)
{
strname = value;
}
}
}
<!--[if !supportlists]-->3. <!--[endif]-->书上还提到属性的其他应用,例如:通过接口来实现在一个类中同时提供只读属性以及非只读属性。但是我个人认为,虽然这样可以实现,但是会产生歧义,即在一个类中提供两个不同版本的属性,破坏了类的一致性,所以我并不推荐这么做。
接着,要说说编写属性的时候,需要注意些什么,我个人认为有如下两点大的方面。
第一个就是编写属性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程序最基本的。不过很遗憾的是,网上在回答此类问题的时候,很多人都建议用第一种方法来解决。