详解C#中的属性和属性的使用
属性
属性是一种成员,它提供灵活的机制来读取、写入或计算私有字段的值。属性可用作公共数据成员,但它们实际上是称为“访问器”的特殊方法。这使得可以轻松访问数据,还有助于提高方法的安全性和灵活性。
在此示例中,TimePeriod类存储时间段。该类在内部以秒为单位存储时间,但是名为Hours的属性允许客户端以小时为单位指定时间。Hours属性的访问器执行小时与秒之间的转换。
classTimePeriod { privatedoubleseconds; publicdoubleHours { get{returnseconds/3600;} set{seconds=value*3600;} } } classProgram { staticvoidMain() { TimePeriodt=newTimePeriod(); //AssigningtheHourspropertycausesthe'set'accessortobecalled. t.Hours=24; //EvaluatingtheHourspropertycausesthe'get'accessortobecalled. System.Console.WriteLine("Timeinhours:"+t.Hours); } }
输出:
Timeinhours:24
表达式主体定义
直接只返回表达式结果的属性很常见。下面的语法快捷方式使用=>来定义这些属性:
publicstringName=>First+""+Last;
属性必须为只读,并且你不能使用get访问器关键字。
使用属性
属性结合了字段和方法的多个方面。对于对象的用户,属性显示为字段,访问该属性需要相同的语法。对于类的实现者,属性是一个或两个代码块,表示一个get访问器和/或一个set访问器。当读取属性时,执行get访问器的代码块;当向属性分配一个新值时,执行set访问器的代码块。不具有set访问器的属性被视为只读属性。不具有get访问器的属性被视为只写属性。同时具有这两个访问器的属性是读写属性。
属性具有多种用法:它们可在允许更改前验证数据;它们可透明地公开某个类上的数据,该类的数据实际上是从其他源(例如数据库)检索到的;当数据被更改时,它们可采取行动,例如引发事件或更改其他字段的值。
属性在类块中是按以下方式来声明的:指定字段的访问级别,接下来指定属性的类型和名称,然后跟上声明get访问器和/或set访问器的代码块。例如:
publicclassDate { privateintmonth=7;//Backingstore publicintMonth { get { returnmonth; } set { if((value>0)&&(value<13)) { month=value; } } } }
在此示例中,Month是作为属性声明的,这样set访问器可确保Month值设置为1和12之间。Month属性使用私有字段来跟踪该实际值。属性的数据的真实位置经常称为属性的“后备存储”。属性使用作为后备存储的私有字段是很常见的。将字段标记为私有可确保该字段只能通过调用属性来更改。
。
get访问器
get访问器体与方法体相似。它必须返回属性类型的值。执行get访问器相当于读取字段的值。例如,当正在从get访问器返回私有变量并且启用了优化时,对get访问器方法的调用由编译器进行内联,因此不存在方法调用的系统开销。然而,由于在编译时编译器不知道在运行时实际调用哪个方法,因此无法内联虚拟get访问器。以下是返回私有字段name的值的get访问器:
classPerson { privatestringname;//thenamefield publicstringName//theNameproperty { get { returnname; } } }
当引用属性时,除非该属性为赋值目标,否则将调用get访问器以读取该属性的值。例如:
Personperson=newPerson(); //... System.Console.Write(person.Name);//thegetaccessorisinvokedhere
get访问器必须以return或throw语句终止,并且控制权不能离开访问器体。
通过使用get访问器更改对象的状态不是一种好的编程风格。例如,以下访问器在每次访问number字段时都会产生更改对象状态的副作用。
privateintnumber; publicintNumber { get { returnnumber++;//Don'tdothis } }
get访问器可用于返回字段值,或用于计算并返回字段值。例如:
classEmployee { privatestringname; publicstringName { get { returnname!=null?name:"NA"; } } }
在上一个代码段中,如果不对Name属性赋值,它将返回值NA。
set访问器
set访问器类似于返回类型为void的方法。它使用称为value的隐式参数,此参数的类型是属性的类型。在下面的示例中,将set访问器添加到Name属性:
classPerson { privatestringname;//thenamefield publicstringName//theNameproperty { get { returnname; } set { name=value; } } }
当对属性赋值时,用提供新值的参数调用set访问器。例如:
Personperson=newPerson(); person.Name="Joe";//thesetaccessorisinvokedhere System.Console.Write(person.Name);//thegetaccessorisinvokedhere
在set访问器中,对局部变量声明使用隐式参数名称value是错误的。
此例说明了实例、静态和只读属性。它从键盘接受雇员的姓名,按1递增NumberOfEmployees,并显示雇员的姓名和编号。
publicclassEmployee { publicstaticintNumberOfEmployees; privatestaticintcounter; privatestringname; //Aread-writeinstanceproperty: publicstringName { get{returnname;} set{name=value;} } //Aread-onlystaticproperty: publicstaticintCounter { get{returncounter;} } //AConstructor: publicEmployee() { //Calculatetheemployee'snumber: counter=++counter+NumberOfEmployees; } } classTestEmployee { staticvoidMain() { Employee.NumberOfEmployees=107; Employeee1=newEmployee(); e1.Name="ClaudeVige"; System.Console.WriteLine("Employeenumber:{0}",Employee.Counter); System.Console.WriteLine("Employeename:{0}",e1.Name); } }
输出:
Employeenumber:108 Employeename:ClaudeVige
此示例说明如何访问基类中由派生类中具有同一名称的另一个属性所隐藏的属性。
publicclassEmployee { privatestringname; publicstringName { get{returnname;} set{name=value;} } } publicclassManager:Employee { privatestringname; //Noticetheuseofthenewmodifier: publicnewstringName { get{returnname;} set{name=value+",Manager";} } } classTestHiding { staticvoidMain() { Managerm1=newManager(); //Derivedclassproperty. m1.Name="John"; //Baseclassproperty. ((Employee)m1).Name="Mary"; System.Console.WriteLine("Nameinthederivedclassis:{0}",m1.Name); System.Console.WriteLine("Nameinthebaseclassis:{0}",((Employee)m1).Name); } }
输出:
Nameinthederivedclassis:John,Manager Nameinthebaseclassis:Mary
以下是上一个示例中的要点:
派生类中的属性Name隐藏基类中的属性Name。在这种情况下,派生类的属性声明中使用new修饰符:
publicnewstringName
转换(Employee)用于访问基类中的隐藏属性:
((Employee)m1).Name="Mary";
在此例中,Cube和Square这两个类实现抽象类Shape,并重写它的抽象Area属性。注意属性上override修饰符的使用。程序接受输入的边长并计算正方形和立方体的面积。它还接受输入的面积并计算正方形和立方体的相应边长。
abstractclassShape { publicabstractdoubleArea { get; set; } } classSquare:Shape { publicdoubleside; publicSquare(doubles)//constructor { side=s; } publicoverridedoubleArea { get { returnside*side; } set { side=System.Math.Sqrt(value); } } } classCube:Shape { publicdoubleside; publicCube(doubles) { side=s; } publicoverridedoubleArea { get { return6*side*side; } set { side=System.Math.Sqrt(value/6); } } } classTestShapes { staticvoidMain() { //Inputtheside: System.Console.Write("Entertheside:"); doubleside=double.Parse(System.Console.ReadLine()); //Computetheareas: Squares=newSquare(side); Cubec=newCube(side); //Displaytheresults: System.Console.WriteLine("Areaofthesquare={0:F2}",s.Area); System.Console.WriteLine("Areaofthecube={0:F2}",c.Area); System.Console.WriteLine(); //Inputthearea: System.Console.Write("Enterthearea:"); doublearea=double.Parse(System.Console.ReadLine()); //Computethesides: s.Area=area; c.Area=area; //Displaytheresults: System.Console.WriteLine("Sideofthesquare={0:F2}",s.side); System.Console.WriteLine("Sideofthecube={0:F2}",c.side); } }
输出:
Entertheside:4 Areaofthesquare=16.00 Areaofthecube=96.00 Enterthearea:24 Sideofthesquare=4.90 Sideofthecube=2.00