程序与技术分享:C#基础知识梳理系列三:C#类成员:常量、字段、属性

简介: 程序与技术分享:C#基础知识梳理系列三:C#类成员:常量、字段、属性

摘 要


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


第一节 常量


常量是一个符号,是在编译时已经存在且在程序生命周期内不会发生改变的值,它被保存在程序集的元数据中,只能使用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如图:


通过IL我们可以看到,常量中定义的PAI值3.14(3.1400000000000001)实际是直接被编译到目标代码的元数据中的,而不是对PAI的引用。


如此一来,就引出一个问题:常量PI定义在程序集A中,如果程序集B在使用A中的PAI时,经过编译其真实值是直接被编译到B的IL中,当下一次我们要对PAI的值进行更改时(比如把3.14改为3.1415926),如果只编译程序集A而不编译程序集B,则会导致程序集B得不到更新,还保留原来的3.14,必须对程序集B重新编译才能获取新值3.1415926。要想解决此问题,可以使用接下来我们要讨论的字段,给字段加上readonly修饰符不仅可以达到与常量同样的目的:程序运行期间其值不可更改,而且可以避免每次重新编译程序集B。


第二节 字段


字段是构成类结构的一种元素,它不仅可以用C#内置类型进行声明,也可以用任意的自定义类型进行声明,很明显,它不仅可以保存一个值类型的实例,也可以保存一个引用类型的地址引用。字段可以直接定义在类或结构中。相比常量,它就多了一些特性,它不仅可以是类的状态数据,也可以是实例的状态数据,它默认并不是static,而是对象级的成员,除非明确指定其修饰符为static。字段可以使用的修饰符为: public、private、protected、internal 或 protected internal。另外,readonly也可以用于字段,如果再加上satic,此时它就相当于常量了,只不过对象级的字段初始化是在构造函数中进行的,类级的字段初始化是在静态构造函数中进行的。如下代码:


public class Code_03


{


public const double PAI = 3.14;


double radius = 20;


static int a = 10;


static readonly int b = 30;


}


可以看到,编译器自动生成了一个静态构造函数,并在其内对a和b进行初始化,另外在实例级构造函数内对radius进行初始化。需要说明一点的是:如果人为的要在构造函数中对常量PAI进行更改,在编译器检查语法过程中将会报错,编译器不允许这样操作。


常量是在编译时计算,字段是在运行时字段,并且常量与static readonly字段有着相同之处,那它们又有区别与联系呢?


const常量在声明处进行初始化,编译时直接将值编译进元数据,运行时不能进行值更改(如下面代码中的PAI)。


实例字段可在定义处和构造函数内进行初始化。可在任意处进行更改,如果其可访问性允许(如下面代码中的radius)。


static 字段声明为类级的字段,它属于类的状态数据。可在任意处进行更改,如果其可访问性允许(如下面代码中的a)。


readonly 字段声明只读字段,只能在构造函数内对其进行更改(如下面代码中的b)。


static readonly 字段声明静态只读字段,它属于类级且只读。只能在静态构造函数内对其更改(如下面代码中的c)。


示例代码:


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 //代码效果参考:http://www.jhylw.com.cn/014027403.html

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 //代码效果参考:http://www.jhylw.com.cn/335332803.html

_nameList【i】;

}


set


{


_nameList【i】 = value;


}


}


}


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


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


Dictionary

public string this【Guid key】


{


get


{


return data【key】;


}


set


{


data【key】 = value;


}


}


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


小 结


本博客文章版权归博客园和solan3000共同所有。

相关文章
|
4月前
|
开发框架 .NET C#
C#|.net core 基础 - 删除字符串最后一个字符的七大类N种实现方式
【10月更文挑战第9天】在 C#/.NET Core 中,有多种方法可以删除字符串的最后一个字符,包括使用 `Substring` 方法、`Remove` 方法、`ToCharArray` 与 `Array.Copy`、`StringBuilder`、正则表达式、循环遍历字符数组以及使用 LINQ 的 `SkipLast` 方法。
127 8
|
4月前
|
缓存 C# Windows
C#程序如何编译成Native代码
【10月更文挑战第15天】在C#中,可以通过.NET Native和第三方工具(如Ngen.exe)将程序编译成Native代码,以提升性能和启动速度。.NET Native适用于UWP应用,而Ngen.exe则通过预编译托管程序集为本地机器代码来加速启动。不过,这些方法也可能增加编译时间和部署复杂度。
276 2
|
2月前
|
存储 安全 编译器
学懂C#编程:属性(Property)的概念定义及使用详解
通过深入理解和使用C#的属性,可以编写更清晰、简洁和高效的代码,为开发高质量的应用程序奠定基础。
116 12
|
2月前
|
算法 Java 测试技术
Benchmark.NET:让 C# 测试程序性能变得既酷又简单
Benchmark.NET是一款专为 .NET 平台设计的性能基准测试框架,它可以帮助你测量代码的执行时间、内存使用情况等性能指标。它就像是你代码的 "健身教练",帮助你找到瓶颈,优化性能,让你的应用跑得更快、更稳!希望这个小教程能让你在追求高性能的路上越走越远,享受编程带来的无限乐趣!
155 13
|
4月前
|
设计模式 程序员 C#
C# 使用 WinForm MDI 模式管理多个子窗体程序的详细步骤
WinForm MDI 模式就像是有超能力一般,让多个子窗体井然有序地排列在一个主窗体之下,既美观又实用。不过,也要小心管理好子窗体们的生命周期哦,否则一不小心就会出现一些意想不到的小bug
370 0
|
5月前
|
安全 C# 索引
C#一分钟浅谈:属性与索引器的定义
本文深入浅出地介绍了C#编程中的属性和索引器。属性让字段更安全,通过访问器方法在读写时执行额外操作,如验证数据有效性;索引器则赋予类数组般的访问方式,支持基于索引的数据访问模式。文章通过示例代码展示了如何定义及使用这两种特性,并提供了常见问题及其解决方案,帮助读者写出更健壮、易维护的代码。希望读者能从中学习到如何有效利用属性和索引器增强C#类的功能性。
131 12
|
5月前
|
C# 容器
C#中的命名空间与程序集管理
在C#编程中,`命名空间`和`程序集`是组织代码的关键概念,有助于提高代码的可维护性和复用性。本文从基础入手,详细解释了命名空间的逻辑组织方式及其基本语法,展示了如何使用`using`指令访问其他命名空间中的类型,并提供了常见问题的解决方案。接着介绍了程序集这一.NET框架的基本单位,包括其创建、引用及高级特性如强名称和延迟加载等。通过具体示例,展示了如何创建和使用自定义程序集,并提出了针对版本不匹配和性能问题的有效策略。理解并善用这些概念,能显著提升开发效率和代码质量。
218 4
|
4月前
|
XML 存储 安全
C#开发的程序如何良好的防止反编译被破解?ConfuserEx .NET混淆工具使用介绍
C#开发的程序如何良好的防止反编译被破解?ConfuserEx .NET混淆工具使用介绍
217 0
|
4月前
|
Java 程序员 C#
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
21 0
|
4月前
|
安全 API C#
C# 如何让程序后台进程不被Windows任务管理器强制结束
C# 如何让程序后台进程不被Windows任务管理器强制结束
112 0