Effective C# 第二版 中文 之01

原则一:用属性(property)替代可访问的数据成员(Data memberfield

         属性在C#语言中一直都是一等公民(译注:寓意属性在C#中有很高的地位)。从C#1.0版本以来,属性正变得越来越强大。你可以对数据的读取和赋值进行特定的、不同的约束。用属性代替数据成员,而隐式属性又能极大地减少手打输入的麻烦。如果你还在你的类中创建公有变量,如果你还在手写getset方法,那么从现在开始,抛弃这些不好的习惯吧。属性能够在像公有接口一样提供数据成员的访问,同时,它还提供了面向对象环境中的封装。属性访问起来像是数据成员,但它却是通过方法来实现的。

 

         类中确实有这样一些成员,它们最好还是用数据成员来表示,如:客户的名称,一个点的xy轴位置,去年的收入等等。属性使你能够创建一个数据访问接口,它能够像访问数据成员一样进行数据访问,又同时保留的方法的所有优点。在代码中访问属性就好像是在访问公有数据成员一样。但实际上它是通过方法来实现的,所以你可以限定访问者的行为。

 

         .NET框架假定你会用属性来替代你的公有数据成员。实际上,.NET框架的数据绑定类只支持属性,而不支持公有数据成员。在所以的数据绑定类库中都是这样的,如:WPFWindowsFormsWebForms, Silverlight等等。数据绑定将一个对象的属性绑定到一个用户界面的控件上。数据绑定机制使用反射(reflection)来找出类型中属性的名称,如:

         textBoxCity.DataBindings.Add(“Text”,address,”City”);

 

         上面这段代码,将textBoxCity控件的Text属性绑定到address对象的City属性。如果这是一个名为City的公有数据成员,将无法进行绑定。.net框架的类库并不支持这样的用法(公有数据成员)。公有数据成员是不好的编程习惯,所以,对它们的支持没有加入到类库中。他们(类库的编写者)的决定,又给了你另一个理由去遵循这样的面向对象技术。

 

         没错,数据绑定只应用在这样的一些类中:这些类含有一些在用户界面逻辑中显示的数据。但是这并不意味着属性只能被用在UI逻辑中。你在其它类和结构体(structure)中也应该使用属性。当你以后发现新的需求或行为时,属性将远远比数据成员更容易修改。你或许不久就会决定你的Customer类不能有空白的Name,如果你使用公有属性来定义Name,那么你只需要简单地做以下修改(灰底部分)

 

public  class  Customer
{
    private  string  Name;
    public  string  Name
    {
        get{return  Name;}
        set
        {
            if(string.IsNullOrEmpty(value))

                throw  new  ArgumentException(“Name  can  not  be  blank”,”Name”);

                Name = value;
        }
    }
}

 

         如果你使用公有数据成员,你就必须非常小心地找出每个对Name进行赋值的代码,并对它们进行修改。那将会花费更多的时间——多得多呢!

 

         因为属性是用方法实现的,所以为它添加多线程支持也将变得更加容易。你可以改进getset方法来实现对数据访问的同步。如以下代码所示:

 

public  class  Customer
{
    private  object  syncHandle = new  object();
    
    private  string  Name;
    public  string  Name
    {
        get
        {
            Lock(syncHandle)
                return  Name;
        }
        set
        {
            if(string.IsNullOrEmpty(value))
                throw  new  ArgumentException(“Name  can  not  be  blank”,”Name”);
            lock(syncHandle)
                Name = value;
        }
    }
}

 

         属性拥有方法的所有语言特性,它可以是虚的(虚方法virtual)。如:

 

public  class  Customer
    {
        public  string  Name
        {
            get;
            set;
        }

    }

 

         你可能已经注意到最后一个例子使用了C#3.0中的隐式属性的语法。创建一个(虚)属性并将其封入backing store(译注:不知道怎么翻译)是一种通用的做法。通常,在获取者(getter)或设置者(setter)并不需要进行验证。C#支持简化的隐式属性的语法,以便减少将一个数据成员变成属性所需要的的编码工作。编译器将为你创建一个私有的数据成员(通常叫做backing  store),并且为getset访问器实现一个公认(obvious)的逻辑。

 

         你可以将属性定义成虚的并将它放在接口(Interface)的定义中,隐式属性的语法与此类似。下面代码展示的是定义在一个通用接口(译注:或叫泛型接口,原文:generic interface)中的属性。注意,与隐式属性的语法一样,下面定义的接口中(属性)没有包含任何实际的实现。它定义了任何实现这个接口的类所必须满足的一个抽象目录。

 

public interface INameValuePair<T>
    {
        string Name
        {
            get;
        }
        
        T Value
        {
            get;
            set;
        }
    }

 

         属性是一种全面的、一等的语言特性,它是对对数据进行访问或修改的方法(method)的拓展。任何你能用成员方法(或叫成员函数)实现的的东西,你都能用属性搞定。

 

         属性的访问器(accessors)在类中会被编译成两个独立的方法。在C#中,你可以为getset制定不同的访问修饰符。这将给你更大的灵活性去控制那些对外暴露为属性的数据成员的访问权限。

 

public class Customer
    {
        public virtual string Name
        {
            get;
            protected set;
        }
        //
具体的实现就省略了吧

    }

 

         属性的语法对字简单的数据字段进行了扩展。如果类中包含了被序列化了的元素(典型的就是数组)作为它的接口的一部分,那你可以使用索引器(一组被参数化了的属性,译注:关于索引器自己去看看相关的材料,如果有空的话,我尽量抽空找些出来写在我的博客里)。对于那些要返回一组序列化了的元素的情况,这就非常有用。

 

public int this[int index]
        {
            get{return TheValues[index];}
            set{TheValues[index] = value;}
        }
        

        //
通过索引器访问

        int val = someObject[i];

 

         索引器拥有所有与单个属性相同的语言支持。它们可以由你所写的方法来实现,所以在索引器里你可以进行检查或者计算。索引器可以是虚的(virtual)或抽象的(abstract),可以定义在接口中,可以是只读的也可以是读写都可以的。一维的以数字作为参数的索引可以参与到数据绑定中。其它的索引器可以使用非整形的参数去定义图或者字典(如哈希表的字典)。(译注:其实此处也隐含地告诉了索引器是可以使用非整形参数的,这个在学习索引器的时候要注意的)

 

public Address this[string Name]
        {
            get{return addressValues[Name];}            
            set{addressValues[Name] = value;}
        }

 

         为了与C#的多维数组对应,你可以创建多维索引器,各个维度的参数类型可以是相同的也可以是不同的。如下:

 

public int this[int x,int y]
        {
            get{return ComputeValues(x,y);}
        }
        
        public int this[int x,string Name]
        {
            get{return ComputeValues(x,Name);}
        }

 

         注意,所有的索引器都是用this关键字定义的。在C#中你不能给索引器命名。所以,对于同一个类中的不同索引器,必须定义不同的索引参数列表以避免混淆(译注:参数列表不同包括类型不同和数量不同)。几乎所有的属性的特点都引申到索引器上了:索引器可以是虚的或者抽象的,getset可以有不同的访问权限。但不能声明隐式的索引器,这点与属性不同。

 

         属性的这些功能都是好的并且有益的,是很不错的改进(译注:作者太啰嗦了)。但是你可能仍然喜欢先定义一些数据成员,直到你需要用到属性那些好处的时候才用属性去替代它们。这貌似是个不错的策略——但它是错误的。看下面的这一段代码:

 

    //使用公有的数据成员,这是不好的习惯
    public class Customer
    {
        public string Name;
    }

 

         它定义了一个Customer类,里面含有一个Name字段。你可以用普通的成员符号去获取或设置Name的值。

 

    string name = customerOne.Name;
    customerOne.Name= "This Company,Inc.";

 

         这样确实简单而且直接。你也许在想,以后你用属性来替代Name字段的时候,代码可以不经过任何修改而仍然能够正确运行。嗯,这在某种程度上来讲确实是正确的。属性在访问的时候确实是有意地要表现得像数据成员一样,这也是这种语法后面所隐含的目的(译注:即C#的设计者故意要使属性能像数据成员一样访问)。但属性终究不是数据成员。通过属性访问会产生不同于通过数据成员访问的中间代码(MSILMicrosoft  Intermediate  Language)(译注:中间代码,.NET托管代码会先编译成中间代码,然后在运行的时候再实时编译成机器代码。当然,为了提高效率,编译成的机器代码会保留下来。)。

 

         尽管属性和数据成员在源代码上是兼容的,但它们在二进制编码中确是不兼容的(译注:但机器最终执行的确是二进制编码)。所以,很明显,这就意味着当你将一个公有的数据成员改成等效的公有的属性的时候,你必须重新编译所有用到这个公有数据成员的代码。C#将二进制程序集作为一等公民对待(即二进制代码地位比未编译的源代码高)。这个语言的一个目的是你可以单独的发布一个程序集的新版本而不用去更新整个应用程序。但是这样一个简单的从数据成员改为属性的变更却破坏的二进制的兼容性。它使得更新一个已部署的单独的程序集变得更加困难。

 

         当你审视属性的中间代码的时候,你可能会怀疑属性和数据成员间的性能差异。属性的访问不会比数据成员更快,但可能也不会比它慢。JIT编译器(译注:前面所说的在从中间语言编译成机器代码就是由JIT编译器做的。JITJust  In Time。即只在需要的时候编译)将一些函数做成内联的形式(参见内联函数),对于属性它也是这么做的。当JIT编译器将属性做成内联的时候,对它的访问的效率就和数据成员一样了。即便当属性没有被编译成内联形式的时候,它们之间的效率,也只不过是多了一个微不足道的函数调用而已(特别是在机器硬件速度越来越快的今天,这种差别几乎可以忽略不计)。而且这种情况(属性没有被编译成内联形式)也是仅在极少数情况下才出现的。

 

         属性是这样一种函数,从调用代码来看它表现得与数据成员一样。这就在你的用户的头脑中加进了一些期望。他们将看到属性在访问时表现得好像它就是数据成员一样。但是,那仅仅是它看起来像。你的属性访问者绝对不会辜负这种期望。Get访问者不应当拥有观察方的效果(译注:此处我认为应该是应当拥有,但原文确实是有个大大的not,不知道是不是排版的错误)。Set访问者修改状态,并且用户应该能够看到这些改变。

 

         用户也对属性的性能抱有一定的期待。属性访问,看起来像是数据成员访问。它与简单的数据成员访问间不应该存在明显的性能差别。属性不能花费更长的时间去计算,或者做一些跨程序访问(如数据库查询),或者做一些长时间的操作,这些是与用户对属性的期待背道而驰的。

 

         无论何时,你想要在你的类中对外暴露一些publicprotected的数据成员接口时,请使用属性。对于队列和字典可以使用索引器。所有的数据成员都应该是private的,没有例外。这样,你马上就能获得数据绑定的支持,并且在以后你要对这些方法的实现做任何修改都将更加简单。将变量封装到属性中所产生的额外工作量(主要是打字)在现在只不过是花费你一到两分钟。但如果你直到日后发现你需要用属性的时候,才动手去修改的话可能要花上几小时的时间。现在花费少量的时间,将来会为你自己节省大量的时间。

 

         小结:花了几天的业余时间终于将第1节翻译完了,总共有50节,任重而道远啊。所谓万事开头难,英语已经好久不用了,不过相信以后会越来越快的。还是那句话,也许中文版已经出来了,也许已经有别人已经提供翻译得比我更好的版本了,但我还是会坚持下去。主要是锻炼一下自己的毅力和英语能力,同时也与广大网友分享一下我的劳动成果。

 

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值