Effective C# Item1:Always Use Properties Instead of Accessible Data Members

      在C#语言中,属性的地位已经从编程习惯提升至语言的特性(这里用到first-class language。这个含义可以参见wikipedia)。你不必为你的类型创建公有数据成员,也不必手工创建属性中的get和set方法。属性让你可以通过公有接口暴露数据成员,同时又可以提供oo环境中你所希望的封装。属性在可达性上就像一个公有数据成员,但是实现上像一个方法。

      对于一些类型的成员来讲,例如客户的姓名、xy坐标等,数据型是它们最好的表现形式。属性允许你创建一个接口,使得对它们的操作可以像公有数据成员一样,同时又保留了使用成员函数一样的优势。

      在.net framework中已经假设你会为你的公有数据成员使用属性。实际上,在.net framework的数据绑定中支持的也是属性而不是公有的数据成员。不论是web控件还是win form控件,都是通过属性来实现数据绑定的。下面的例子中,数据绑定机制通过反射在已知类型中寻找特定属性:

textBoxCity.DataBindings.Add("Text", address, "City");

      上面的代码将adress中的City属性绑定到名为textBoxCity控件的Text属性上。如果City不是属性而是adress中的一个公有成员,上面的代码就不能实现。

      当遇到新的需求或行为变更时,属性更容易修改。例如你现在决定客户的名字不能为空,那你只要在对应属性的set中添加判断就可以了。

    public class Customer
    
{
        
private string _name;
        
public string Name
        
{
            
get
            
{
                
return _name;
            }

            
set
            
{
                
if ((value == null|| (value.Length == 0))
                
{
                    
throw new ArgumentException();
                }

                _name 
= value;
            }

        }

    }

      但是如果你使用的是公有数据成员,那你必须检查你所有的代码并一一添加判断,那将消耗大量的时间。

      属性对于多线程的操作支持也很方便。一般的只要在get和set时加锁就可以了。

    public class Customer
    
{
        
private string _name;
        
public string Name
        
{
            
get
            
{
                
lock (this)
                
{
                    
return _name;
                }

            }

            
set
            
{
                
lock (this)
                
{
                    _name 
= value;
                }

            }

        }

    }

      属性拥有方法的语言特性。属性可以为虚拟成员(virtual)

    public class Customer
    
{
        
private string _name;
        
public virtual string Name
        
{
            
get
            
{
                
return _name;
            }

            
set
            
{

                _name 
= value;
            }

        }

    }

      同样属性可以是抽象的(abstract)或是接口定义中的一部分

    public interface ICustomer
    
{
        
string Name
        
{
            
get;
        }

    }

      我们还可以通过接口属性来创建常量

    public interface ICustomer
    
{
        
string Name
        
{
            
get;
            
//没有set
        }

    }

    
public class Customer : ICustomer
    
{
        
string _name;

        
ICustomer Members
    }

      在C#2.0中可以对get和set设定不同的可达性,这使得程序可以更加可控。下例中对Name属性的set为protected

    public class Customer
    
{
        
private string _name;
        
public virtual string Name
        
{
            
get
            
{
                
return _name;
            }

            
protected set
            
{

                _name 
= value;
            }

        }

    }

      属性不仅可以应用在简单数据类型领域。你可以使用索引来创建返回序列项的属性:

    public class Customer
    
{
        
private string[] _name;
        
public string this[int index]
        
{
            
get
            
{
                
return _name[index];
            }

            
set
            
{
                _name[index] 
= value;
            }

        }

    }

      索引拥有所有简单属性所包含的特性,可以在索引内部进行校验和计算,可以为虚拟或抽象成员,可以在接口中声明,可以设为只读或读写。一维整型参数的索引可以参与数据绑定,其他非整型参数索引可以定义映射(map)和字典(dictionary)。

      C#中你可以创建多维索引如下例所示:

    public class Customer
    
{
        
private string[,] _name;
        
public string this[int index1,int index2]
        
{
            
get
            
{
                
return _name[index1,index2];
            }

            
set
            
{
                _name[index1,index2] 
= value;
            }

        }

        
//当然这里string型的index2是不行的了,我只是举个例子而已
        public string this[int index1, string index2]
        
{
            
get
            
{
                
return _name[index1, index2];
            }

            
set
            
{
                _name[index1, index2] 
= value;
            }

        }

    }

      注意所有的索引在声明时都使用this关键字。索引不能自定义命名。在一个类型中,同样参数的索引最多只能有一个。

      虽然属性有很多好处,但是有人会依旧先创建公有成员,当需要用到属性的特性时在将其修改为属性。这看起来好像是蛮有道理的,不过这是不好的做法。对于属性和公有成员来说,它们的实现代码是一致的,这会为我们造成误解,认为只要修改公有成员为属性就万事大吉了。实际上这种说法只是部分的正确。

      在C#的源码中使用属性和公有成员的代码虽然相同,但通过编译器生成的中间语言却是不同的。它们是代码兼容的,重新编译就可以正确运行,但却不是二进制兼容的。(对于二进制兼容这点,我也不是十分清楚… 我觉得这种兼容是应该与开发平台无关的,因此上面的改变才会引发不兼容性。望指教)。

      在效率上虽然使用属性并不会比使用公有数据成员快,但是一般情况下也不会比它慢。属性在JIT编译器编译时做为内联函数(inline)处理,相当程度上减少了效率的损失。

      译自   Effective C#:50 Specific Ways to Improve Your C#                      Bill Wagner著

     P.S.  Item1还是比较简单的,就不写什么了。在我刚参加工作的时候就在老一辈的指导下养成了这个习惯,虽然声明时候会麻烦一些,但是比起事后的修改工作实在是好太多了… 另外可将其在不同权限下分别设为只读或读写非常实用。

      回到目录

posted on 2006-08-29 22:18  aiya  阅读(1617)  评论(1编辑  收藏  举报