程序与技术分享: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共同所有。

相关文章
|
2天前
|
存储 C# 开发者
C# 编程基础:注释、变量、常量、数据类型和自定义类型
C# 编程基础:注释、变量、常量、数据类型和自定义类型
8 1
|
19天前
|
C#
C# 版本的 计时器类 精确到微秒 秒后保留一位小数 支持年月日时分秒带单位的输出
这篇2010年的文章是从别处搬运过来的,主要包含一个C#类`TimeCount`,该类有多个方法用于处理时间相关的计算。例如,`GetMaxYearCount`计算以毫秒为单位的最大年数,`GetCurrentTimeByMiliSec`将当前时间转换为毫秒,还有`SecondsToYYMMDDhhmmss`将秒数转换为年月日时分秒的字符串。此外,类中还包括一些辅助方法,如处理小数点后保留一位数字的`RemainOneFigureAfterDot`。
|
2天前
|
存储 安全 C#
C# 类的深入指南
C# 类的深入指南
7 0
|
1月前
|
C#
C#的类和对象的概念学习案例刨析
【5月更文挑战第17天】C#是一种面向对象的语言,以类和对象为核心。类作为对象的模板,定义了属性(如Name, Age)和行为(如Greet)。对象是类的实例,可设置属性值。封装通过访问修饰符隐藏实现细节,如Customer类的私有name字段通过Name属性访问。继承允许新类(如Employee)从现有类(Person)继承并扩展。多态让不同对象(如Circle, Square)共享相同接口(Shape),实现抽象方法Area,提供灵活的代码设计。
47 1
|
1月前
|
开发框架 前端开发 .NET
C#编程与Web开发
【4月更文挑战第21天】本文探讨了C#在Web开发中的应用,包括使用ASP.NET框架、MVC模式、Web API和Entity Framework。C#作为.NET框架的主要语言,结合这些工具,能创建动态、高效的Web应用。实际案例涉及企业级应用、电子商务和社交媒体平台。尽管面临竞争和挑战,但C#在Web开发领域的前景将持续拓展。
|
1月前
|
SQL 开发框架 安全
C#编程与多线程处理
【4月更文挑战第21天】探索C#多线程处理,提升程序性能与响应性。了解C#中的Thread、Task类及Async/Await关键字,掌握线程同步与安全,实践并发计算、网络服务及UI优化。跟随未来发展趋势,利用C#打造高效应用。
|
1月前
|
开发框架 .NET Java
探索 C#编程的奥秘与魅力
【4月更文挑战第20天】C#是微软开发的现代、面向对象的编程语言,以其简洁语法、强大功能和跨平台支持脱颖而出。它支持自动垃圾回收、泛型、委托、LINQ,并广泛应用于桌面、Web、移动和游戏开发。C#拥有活跃的开发者社区和丰富的资源,是Unity游戏开发的首选语言。随着.NET Core,C#可在多个操作系统上运行,持续创新,未来发展潜力巨大。
|
1月前
|
存储 安全 网络安全
C#编程的安全性与加密技术
【4月更文挑战第21天】C#在.NET框架支持下,以其面向对象和高级特性成为安全软件开发的利器。本文探讨C#在安全加密领域的应用,包括使用System.Security.Cryptography库实现加密算法,利用SSL/TLS保障网络传输安全,进行身份验证,并强调编写安全代码的重要性。实际案例涵盖在线支付、企业应用和文件加密,展示了C#在应对安全挑战的同时,不断拓展其在该领域的潜力和未来前景。
|
1月前
|
人工智能 C# 云计算
C#编程的未来发展趋向
【4月更文挑战第21天】C#编程未来将深化跨平台支持,强化云计算与容器技术集成,如.NET Core、Docker。在AI和ML领域,C#将提供更丰富框架,与AI芯片集成。语言和工具将持续创新,优化异步编程,如Task、async和await,提升多核性能。开源生态的壮大将吸引更多开发者,共创更多机遇。
|
1月前
|
程序员 C#
C#编程中的面向对象编程思想
【4月更文挑战第21天】本文探讨了C#中的面向对象编程,包括类、对象、封装、继承和多态。类是对象的抽象,定义属性和行为;对象是类的实例。封装隐藏内部细节,只暴露必要接口。继承允许类复用和扩展属性与行为,而多态使不同类的对象能通过相同接口调用方法。C#通过访问修饰符实现封装,使用虚方法和抽象方法实现多态。理解并应用这些概念,能提升代码的清晰度和可扩展性,助你成为更好的C#程序员。