《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 ,如需转载请自行联系原作者
相关文章
|
6月前
|
存储 安全 数据库连接
C#深度揭秘:常量的魅力和实践,一文让你从新手到专家
C#深度揭秘:常量的魅力和实践,一文让你从新手到专家
49 0
|
6月前
|
C# 开发者
C# 10.0引入常量插值字符串:编译时确定性的新篇章
【1月更文挑战第22天】在C# 10.0中,微软为开发者带来了一项引人注目的新特性——常量插值字符串。这一功能允许在编译时处理和计算字符串插值表达式,从而得到可以在编译时确定的常量字符串。本文将深入探讨C# 10.0中常量插值字符串的概念、工作原理、使用场景及其对现有字符串处理方式的改进,旨在帮助读者更好地理解和应用这一强大的新特性。
|
5月前
|
存储 Java C#
C# 中的值类型与引用类型:内存大小解析
C# 中的值类型与引用类型:内存大小解析
|
5月前
|
存储 安全 Java
程序与技术分享:C#值类型和引用类型的区别
程序与技术分享:C#值类型和引用类型的区别
41 0
|
2月前
|
存储 Java C#
C# 中的值类型与引用类型
在 C# 编程中,值类型和引用类型的区别至关重要,直接影响内存管理、性能优化及编程模式选择。值类型直接存储数据(如 `int`、`float`),而引用类型存储数据的引用地址(如 `class`、`string`)。值类型的赋值涉及数据复制,适合小数据量;引用类型仅复制引用,适合大数据量处理但需关注垃圾回收。本文通过具体代码示例详细解析二者的定义、存储方式及性能影响,并提供实战案例分析及易错点避免方法,帮助读者更好地理解和应用。
52 2
|
5月前
|
开发框架 .NET 编译器
程序与技术分享:C#基础知识梳理系列三:C#类成员:常量、字段、属性
程序与技术分享:C#基础知识梳理系列三:C#类成员:常量、字段、属性
36 2
|
5月前
|
存储 C# 开发者
C# 编程基础:注释、变量、常量、数据类型和自定义类型
C# 编程基础:注释、变量、常量、数据类型和自定义类型
|
存储 C# 图形学
代码解析 C# 引用类型还是值类型
代码解析 C# 引用类型还是值类型
|
存储 C# C语言
C# OOP之五 深入理解值类型和引用类型
C# OOP之五 深入理解值类型和引用类型
41 0
|
11月前
|
存储 C#
C# “值类型“和“引用类型“在内存的分配
C# “值类型“和“引用类型“在内存的分配