您的位置:首页 > 编程语言 > C#

C#基础知识梳理系列三:C#类成员:常量、字段、属性

2012-07-24 12:17 666 查看
摘 要

类 就像自然界的事物一样,拥有反应其自身状态特性的一系列数据。类状态数据是由常量、字段、属性等一些基础成员组成,且有静态和实例之分。它们之间有什么区别呢?可以互相替代吗?常量与静态只读字段有什么区别呢?属性是用方法实现,那么实现它的方法可以有参数吗?本章将解释这些奥秘。

第一节 常量
常量是一个符号,是在编译时已经存在且在程序生命周期内不会发生改变的值,它被保存在程序集的元数据中,只能使用C#内置的数据类型(基元类型)定义,如:int、uint、long 等,当然不包括System.Object。既然是内置类型定义,它必然是在声明时同时已初始化。常量使用const定义,C#编译器总是默认为static成员,且不可明文指定其访问修饰符为static。常量的可访问修饰符为:public、private、protected、internal 或 protected internal。如下代码:

public class Code_03
{
public const double PAI = 3.14;

public void Test()
{
double area = PAI * Math.Sqrt(20);
}
}


IL如图:

View Code

public class Code_03
{
public const double PAI = 3.14;
double radius = 20;
static int a = 10;
readonly int b = 0;
static readonly int c = 30;

static Code_03()
{
a = 100;
c = 1000;
//错误 非静态的字段、方法或属性“ConsoleApp.Example03.Code_03.c”要求对象引用
//radius = 1;
//b = -1;
}
public Code_03()
{
radius = 1;
a = -1;
b = -1;
//错误 无法对静态只读字段赋值(静态构造函数或变量初始值中除外)
//c = -1;
}
public void MyMethod()
{
radius = 1;
a = -1;
//错误 无法对静态只读字段赋值(静态构造函数或变量初始值中除外)
//b = -1;
//c = -1;
}
}


字段通常保存着类或对象本身的状态,我们当然可以将其公开为public让外界对其进行读、写修改。从某种意义上来讲,我们更希望在类本身内部对自己的状态进行维护,并不希望外界对自己的状态进行直接更改,以防止破坏这些数据,所幸的是还有一个数据成员可供使用,它就是属性。

第二节 属性
如果在外部要访问某一个类的内部成员(私有字段),可以使用方法来达到目的,但如果对每一个字段都去编写一个方法来进行读写操作似乎又麻烦了些。属性以灵活的方式实现了对私有字段的访问,它是一种“访问器”方法,包括get方法和set方法,更明确地说,属性就是方法的精简写法的实现,隐藏了实现和验证的代码。它有两个访问器:

get访问器用于获取属性的值。

set访问器用于设定属性的值。既然它是方法,且是要在方法内对私有字段用新值进行更改替换,那么它就是可以(或者说是应该)接收参数的, value 关键字就是用于定义由 set 取值函数分配的值。假如有如下一个属性的定义:

public class Code_03_2
{
string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}


这个定义是通过属性Name对私有字段_name进行访问,我们来看一下编译器对IL做了哪些处理:



编译器自动生成了get_和set_方法。其中方法get_name()的IL如下:

IL:

.method public hidebysig specialname instance string
get_Name() cil managed
{
// 代码大小       12 (0xc)
.maxstack  1
.locals init ([0] string CS$1$0000)
IL_0000:  nop
IL_0001:  ldarg.0
IL_0002:  ldfld      string ConsoleApp.Example03.Code_03_2::_name
IL_0007:  stloc.0
IL_0008:  br.s       IL_000a
IL_000a:  ldloc.0
IL_000b:  ret
} // end of method Code_03_2::get_Name


它是在方法内部读取字段_name然后返回。

set_Name(string)方法如下:

IL:

.method public hidebysig specialname instance void
set_Name(string 'value') cil managed
{
// 代码大小       9 (0x9)
.maxstack  8
IL_0000:  nop
IL_0001:  ldarg.0
IL_0002:  ldarg.1
IL_0003:  stfld      string ConsoleApp.Example03.Code_03_2::_name
IL_0008:  ret
} // end of method Code_03_2::set_Name


它是在方法内部对字段_name进行值修改。

我们已经看出来,属性是方法的实现,既然是方法,那方法就可以被访问修饰符定义,如public、private等,来限定方法的可访问性,很显然,我们也可以对属性的可访问性进行限定,这里是对访问器set和get进行访问限定的。比如:

string _name;
public string Name
{
get { return _name; }
private set { _name = value; }
}


如此一来,则此属性Name是只读属性,如果愿意,也可以将其定义为只写属性,但这样做好像没什么意思。

另外,属性还有一种更简洁的写法如下:

public int Age { get; set; }
public string Address { get; set; }


在编译的时候,编译器会自动生成对应的私有字段_age和_address,同样也会生成相应的get_方法和set_方法。

还有一种数据结构与属性很类似,称为索引器,它同样有get和set访问器,只是它的get方法接受大于或等于一个参数,它的set方法接受大于或等于两个参数。通常它在类内部维护一个集合,如Array、List等。如下代码:

public class Code_03_3
{
string[] _nameList = new string[100];
public string this[int i]
{
get
{
return _nameList[i];
}
set
{
_nameList[i] = value;
}
}
}


this关键字用于定义索引器,value 关键字用于定义由 set 索引器分配的值。再来看一下编译器都干了什么事?



编译器自动生成了两个方法get_Item(int32)和set_Item(int32,string),它们接受了大于等于一个参数。最后要说明一点的是:索引器不必根据整数值进行索引,也可以用其他类型进行索引。如下的定义是用Guid进行索引:

Dictionary<Guid, string> data = new Dictionary<Guid, string>();
public string this[Guid key]
{
get
{
return data[key];
}
set
{
data[key] = value;
}
}


我们一直在讨论属性是封装了对类内部私有字段成员的访问,它提供了一种书写更简便、访问控制更安全实现方式。根据建议,我们应该尽量避免在属性的访问器内进行过多的逻辑运算,如果确实有复杂的逻辑运行,请考虑使用方法,我们将在下一章讨论方法的方方面面。

小 结
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: