《Effective C#》读书笔记——条目20:保证值类型的常量性和原子性<.NET资源管理>

简介:

  "常量性"指的是:对象自创建后,它的值就保持不变。如果在构造函数中就验证了参数的有效性,那么就能够保证之后该变量值始终是有效的——因为已经不能再改变它的内部状态了。这样做有很多好处:

  • 常量性的类型可以减少必要的错误检查。
  • 常量性的类型也是线程安全的类型,上下文切换中线程读取的数据一致。
  • 常量性的类型可以安全的暴露给外界,因为调用者不能改变对象的内部状态。
  • 常量性的类型在基于散列(hash)的集合中表现良好,因为Object.GetHashCode()方法返回的就是一个不变量。

 

1.应用场景

  我们不可能将所以的类型都设置为常量类型,我们需要对类型使用的场景来分析,这里我们指的是:同时具有常量性和原子性的值类型。我们应该将类型分解成各个能自然形成的单个实体结构。一个对象可能并不具备原子性,但是它的各个组成部分由不同的原子类型组成的(具有原子性的类型都是单一实体)。

 

2.设计常量类型示例

Address类版本1

   下面我们来看一个简单的示例,来了解如何保证值类型的常量性和原子性。这是我们的第一个版本:

View Code

我们运行第一个版本:

复制代码
 1             //示例
 2             Address a1 = new Address();
 3             a1.Line1 = "111 S. Main";
 4             a1.City = "Anytown";
 5             a1.State = "IL";
 6             a1.ZipCode = 51111;
 7             //修改
 8             a1.City = "Ann arbor";
 9             a1.ZipCode = 48103;
10             a1.State = "MI";
11             Console.WriteLine(string.Format("City:{0},ZipCode:{1},State:{2}", a1.City, a1.ZipCode, a1.State));
12             Console.Read();
复制代码

我们可以看到对象的内部状态被改变了,在更改City字段后,a1就处于无效状态——因为更改的State和ZipCode不能匹配;假如这段代码在多线程环境中任何在City更改过程中的上下文切换可能会导致另一个线程看到不一致的数据。同时如果ZipCode值无效,那么将会抛出异常;为了避免出现这种问题我们需要编写更多的内部校验代码,线程的安全也要求我们在每个属性访问器上添加线程同步检查,这导致代码的复杂度的增加。

 

Address类版本2

  这个时候我们需要一个常量类型,在上面第一个版本的基础上我们将所以得实例字段的set访问器修改为private:

View Code

 经过上面的改变,我们只能通过该类的构造函数来对其对象进行初始化工作,并且不能从外部修改一个对象的状态,除非创建一个新的对象,将新对象的引用付给需要修改的对象才可以:

1             //创建一个地址对象
2             Address2 a2 = new Address2("111 S. Main", "", "Anytown", "IL", 5111);
3             //改变,重新创建一个对象
4             a2 = new Address2(a2.Line1, a2.Line2, "Ann arbor", "MI", 48103);

 

Address类版本3

  在版本2中我们的Address类型并不是严格的不可变。隐式属性中包含private setter仍然包含修改内部状态的方法,如果想完全实现不可变类型我们可以这样:将隐式属性改为显式属性,并将该属性的后台存储字段更改为readonly即可:

View Code

   

3.包含引用类型字段的常量类型设计

   在设计常量类型时,要确保没有漏洞会导致其内部状态被外界更改。我们需要格外注意常量类型中的可变引用类型字段,在为这样的类型设计构造函数时,需要对其中可变类型进行防御性的复制。我们来看一个示例:Phone是一个具有常量性的值类型:

复制代码
 1     public struct Phone
 2     {
 3         private readonly int phoneNumber;
 4 
 5         public int PhoneNumber
 6         {
 7             get { return phoneNumber; }
 8         }
 9 
10         public Phone(int phoneNumber):this()
11         {
12             this.phoneNumber = phoneNumber;
13         }
14     }
复制代码

 

下面是PhoneList类:

版本1

View Code

 运行程序:

复制代码
1             //初始化phones
2             Phone[] Phones = new Phone[10];
3             PhoneList p1 = new PhoneList(Phones);
4 
5             //值被改变
6             Phones[5] = new Phone(123456789);
7             Console.WriteLine(p1.Phones.ToArray()[5].PhoneNumber);
复制代码

   我们发现p1的值被改变了,这是由于数组时引用类型,这表示PhoneList结构内部引用的数组和外部的phones数组引用的是同一块内存空间,因此,外部就有机会通过改变phones来修改常量结构PhoneList。如果Phone是引用类型那么着将更容易出现问题。为了避免出现这样的情况,我们需要对数组进行一次防御性的复制。

实时上只要常量类型中存在任何可变的引用类型都应该在该类型的所有构造函数中进行防御性的复制。

 现在我们将PhoneList结构修改如下:

版本2

View Code

   对于一些常量值我们也可以通过创建工厂方法来实现,.NET 中的Color类就是采用这种方式来初始化系统颜色的。对于那些需要多个步骤操作才能完整构造出的常量类型,我们可以创建一个辅助类——String类就是采用这种方式的——借助StringBuilder类。

 

小节

   常量类型使我们的代码更简洁,不需要盲目的为类型中的每个属性都创建get和set访问器,对于那些用于存储数据的类型,应该尽可能的保证其常量性和原子性。在这些常量类型的基础上我们可以更容易的创建更复杂的结构。

 

本文转自gyzhao博客园博客,原文链接:http://www.cnblogs.com/IPrograming/archive/2013/01/13/EffectiveCSharp_20.html ,如需转载请自行联系原作者
相关文章
|
1月前
|
存储 安全 数据库连接
C#深度揭秘:常量的魅力和实践,一文让你从新手到专家
C#深度揭秘:常量的魅力和实践,一文让你从新手到专家
16 0
|
3月前
|
C# 开发者
C# 10.0引入常量插值字符串:编译时确定性的新篇章
【1月更文挑战第22天】在C# 10.0中,微软为开发者带来了一项引人注目的新特性——常量插值字符串。这一功能允许在编译时处理和计算字符串插值表达式,从而得到可以在编译时确定的常量字符串。本文将深入探讨C# 10.0中常量插值字符串的概念、工作原理、使用场景及其对现有字符串处理方式的改进,旨在帮助读者更好地理解和应用这一强大的新特性。
|
9月前
|
C#
【C#视频】常量、枚举、结构体、数组
【C#视频】常量、枚举、结构体、数组
|
10月前
|
C# 索引
C#之常量与变量排错
C#之常量与变量排错
|
10月前
|
存储 编译器 C#
C#变量与常量的区分和总结
C#变量与常量的区分和总结
|
存储 编译器 C#
【C#基础】C# 变量与常量的使用
编程语言 C# 变量和常量的介绍。
114 0
【C#基础】C# 变量与常量的使用
|
人工智能 C# 索引
C# 常量
常量的广义概念是:不变化的量。在C#中,除了那些已经写死的,如:“hello”,4,2.718等字面量以外,我们还可以自定义一个常量。C#与变量的定义十分相似,其中,const为C#中的关键字,表示常量。data_type为数据类型,为常量名,value为初始值。const string myName = "小嗷犬";
84 0
C# 常量
|
存储 C#
C# 变量和常量
C# 变量 一个变量只不过是一个供程序操作的存储区的名字。在 C# 中,每个变量都有一个特定的类型,类型决定了变量的内存大小和布局。范围内的值可以存储在内存中,可以对变量进行一系列操作。 我们已经讨论了各种数据类型。C# 中提供的基本的值类型大致可以分为以下几类: 整数类型 sbyte、byte、short、ushort、int、uint、long、ulong 和 char 浮点型 float 和 double 十进制类型 decimal 布尔类型 true 或 false 值,指定的值 空类型 可为空值的数据类型 C# 中变量定义的语法: <data_type> <variable_l
110 0
|
存储 C#
C#编程基础——常量与变量
C#编程基础——常量与变量
113 0
C#编程基础——常量与变量
|
JavaScript 前端开发 PHP
C#(五)之常量、@控制符、转译符、ASCII编码,Console.Write
对C#的常量,ASCII编码、@控制符、“+”连接符、Console.WriteLine及转译字符的简单应用。
275 0
C#(五)之常量、@控制符、转译符、ASCII编码,Console.Write