Admirer Of Nature

Finding Wonderland of .Net

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

Effective C#: Item 1 Always use properties instead of accessible data members

Item 1:
当设计类时,永远用Property, 而不是可直接访问的Data Member

 

C#里,Property已经晋升为一类公民。如果你的类里还有Public的变量,Stop! 如果你还在手写get and set 方法,Stop! Property在不破坏你类的封装的情况下,仍可以把类的data member变成public interface的一部分。访问Property的方式和访问data member的方式一样,但Property是用methods实现的。

 

有些类的成员只能用data最好的表示,比如:你一个客户的名字,一个点的坐标,等等。而Property就是用来欺骗使用你类的客户,让它们错误的认为它们在访问你类的public变量。你还可以通过Property的实现方法来控制Property的访问。

 

.Net Framework假定你使用Property来让外界访问你类里想让外界访问到的data member (也就是public data member) 。实际上也是这样的,因为.Netdata binding只支持Property,而不支持public data member的访问。Data binding的目的就是把一个objectProperty绑定到一个用户界面的control上,web control或者windows form control. Data binding是通过reflection来实现的,如下例:

 

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

 

这段code就是把textBoxCityText Property绑定到address这个objectCity Property上。如果你把addressCity Property改成public data member,这段code是不会运行的。因为.Net Framework Class Library的设计者不支持你的这种行为,他们认为public data member是非常不好的行为和习惯,所以他们不会支持,他们想让你遵从正确的Object Oriented设计方法。Data binding也不会去找get and set methods,所以一定要用Property,而不是传统的get and set methods.

 

你也许要说,data binding只适用于那些含有要显示在用户界面的元素的类。但实际情况并不是这样,对于你所有的类,都要使用Property而不是public data member。因为当有新的需求时,通过修改Property的实现方法来适应这个新的需求,要比在你的程序里修改所有的public data member去适应这个需求容易太多了。比如说你以前定义了一个类customer,现在你发现由于当初的粗心没有强制customer姓名不能为空,如果你使用了Property,你可以非常轻松的添加一个检查机制,如下面这段code:

 

      public class Customer

      {

            private string _name;

            public string Name

            {

                  get

                  {

                        return _name;

                  }

                  set

                  {

                        if (( value == null) || ( value.Length == 0 ))

                        {

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

                        }

                        _name = value;

                  }

            }

 

            //...

      }

 

如果你使用了public data member,你就要找遍你的程序,在每个地方都修改,那样就很愚蠢了。而且浪费了无数青春好时光。

 

因为Property是用methods实现的,所以添加multi-threaded的支持是非常方便的。比如想要添加同步访问的支持:

 

public string Name

            {

                  get

                  {

                        lock( this )

                        {

                              return _name;

                        }

                  }

                  set

                  {

                        lock( this )

                        {

                              _name = value;

                        }

                  }

            }

 

因为Property是用methods实现的,所以它拥有methods所拥有的一切。Property可以被定义为virtual:

 

public class Customer

      {

            private string _name;

            public virtual string Name

            {

                  get

                  {

                        return _name;

                  }

                  set

                  {

                        _name = value;

                  }

            }

 

            //...

      }

 

显而易见,你也可以把Property扩展为abstract,甚至成为interface的一部分。

 

public interface INameValuePair

      {

            object Name

            {

                  get;

            }

 

            object Value

            {

                  get;

                  set;

            }

      }

 

你当然也可以扩展出constnonconst版本的interface

 

public interface IConstNameValuePair

      {

            object Name

            {

                  get;

            }

 

            object Value

            {

                  get;

            }

      }

 

      public interface INameValuePair

      {

            object Value

            {

                  get;

                  set;

            }

      }

 

//usage:

      public class Stuff : IConstNameValuePair, INameValuePair

      {

            private string _name;

            private object _value;

 

            #region IConstNameValuePair Members

            public object Name

            {

                  get

                  {

                        return _name;

                  }

            }

            object IConstNameValuePair.Value

            {

                  get

                  {

                        return _value;

                  }

            }

            #endregion

 

            #region INameValuePair Members

            public object Value

            {

                  get

                  {

                        return _value;

                  }

                  set

                  {

                        _value = value;

                  }

            }

            #endregion

      }

 

如前所述,Property是访问内部数据的method的扩展,它拥有member function的一切特性。

 

因为实现Property访问的方法get and set是独立的两个method,在C# 2.0中,你可以给它们定义不同的访问级别,来更好的控制类成员的可见性,如下例:

 

public class Customer

      {

            private string _name;

            public virtual string Name

            {

                  get

                  {

                        return _name;

                  }

                  protected set

                  {

                        _name = value;

                  }

            }

 

            //...

      }

 

Property的语法已经超越了单纯的data field。如果你的类包含indexed item,你可以使用indexer(参数化的Property),你可以创建一个可返回一个序列元素的Property,如下例:

public int this [ int index ]

      {

            get

            {

                  return _theValues [ index ];

            }

            set

            {

                  _theValues [ index ] = value;

            }

      }

 

      //usage:

      int val = MyObject[ i ];

 

indexer和单元素Property有着相同的特性。一维的indexer可以用于data binding,二维和多维的indexer可以用来实现其他的数据结构,比如mapdictionary

 

public Address this [ string name ]

      {

            get

            {

                  return _theValues[ name ];

            }

            set

            {

                  _theValues[ name ] = value;

            }

      }

 

多维的indexer的每个axis上的数据类型可以相同,也可以不同:

 

public int this [ int x, int y ]

      {

            get

            {

                  return ComputeValue( x, y );

            }

      }

 

      public int this [ int x, string name ]

      {

            get

            {

                  return ComputeValue( x, name );

            }

      }

 

所有的indexer都必须也只能用this来定义,所以参数表相同的indexer,每个类最多只能有一个。

 

因为使用Propertydata member对于数据访问的code没有什么区别,比如:

 

public class Customer

      {

            public string Name;

 

            //...

      }

 

在这个类中使用了public data member,数据访问的code如下:

 

string name = CustomerOne.Name;

      CustomerOne.Name = "customer name";

 

你也许会想,如果在以后的修改中,用Property来代替public data member是可行的,因为数据访问的code相同,但实际上这是行不通的。确实,访问Property和访问data membercode是相同的,但Property不是data,访问Property所产生的IL code和数据访问的IL code是不一样的。所以访问Property和访问data member只具有code兼容性,而不具有binary的兼容性。如果有兴趣,你可以使用Reflector (http://www.aisto.com/roeder/dotnet/ )来分析使用Propertypublic data member的类。

 

你会发现在使用Property的类中,存在.property directive,这个directive定义了Property的类型以及get and set实现方法。Get and set都被标注为hidebysig, specialname。也就是说它们不能被C#源代码直接调用,它们也不是正是的类型定义。你只能通过Property来访问它们。

 

C#的编译器会根据类的情况(是用Property还是data member)来自动产生不同的IL code。如上所述,访问Property和访问data member只具有code兼容性,而不具有binary的兼容性。所以,如果你改变最初的设计,用Property来代替public data member的话,你必须重新编译整个程序。这使得升级已经部署的程序或assembly是非常的麻烦。

 

那么两种实现谁的效率更好呢?Property确实不会比public data member快,但也不一定会慢。因为JITProperty的存取方法set and get进行inline的优化。这时,Propertypublic data member的效率是一样的。即使Property的存取方法没有被inline优化,它和public data member的效率差别也只是一个可以忽略的function call。只有在很少的情况下,这种差别才可以被测量出来。

 

总而言之,当你想让你类内部的数据被外界访问到时(不管是public还是protected),一定要用Property。对于序列和字典,使用indexer。你类的data member永远应该是private,绝无例外。使用Property,你可以得到如下好处:

1.Data binding支持

2.对于需求变化有更强的适应性,更方便的修改实现方法

 

记住,现在多花1分钟使用Property,会在你修改程序以适应设计变化时,为你节约n小时。

 

 

 

本系列文章只是作者读书笔记,版权完全属于原作者 (Bill Wagner),任何人及组织不得以任何理由以商业用途使用本文,任何对本文的引用和转载必须通知作者:zphillm@hotmail.com

 

posted on 2005-02-22 06:58  Admirer Of Nature  阅读(1640)  评论(4编辑  收藏  举报