C#不可变类型深入解析

 更新时间:2014年08月13日 11:07:06   投稿:shichen2014  
这篇文章主要介绍了C#不可变类型,对于C#程序员深入理解string数据类型有很大的帮助,需要的朋友可以参考下

学过C#的人都知道string类型,但是string作为一种特殊的引用类型还有一个重要的特征就是恒定性,或者叫不可变性,即Immutable。作为不可变类型,最主要的特性表现是:一旦创建,只要修改,就会在托管堆上创建一个新的对象实例,而且和上一个对象实例是相邻的,在托管堆上分配到一块连续的内存空间。

那么为什么需要不可变类型呢?

在多线程情况下,一个线程,由于种种原因(比如异常)只修改了一个变量所代表类型的部分成员的值,这时候,另一个进程进来,也访问这个变量,第二个进程访问到的变量成员,一部分成员还是原来的值,另一部分成员的值是第一个线程修改的值,这样就出现了"数据不一致"。而不可变类型就是为了解决在多线程条件下的"数据不一致"的问题。

当然,字符串的不可变性或恒定性,不仅解决了"数据不一致"的问题,还为字符串的"驻留"提供了前提,这样才可以把不同的字符串以及托管堆上的内存地址以键值对的形式放到全局哈希表中。

一、亲眼目睹"数据不一致":

对Student的Score属性,在赋值的时候加上检测,检测是否是2位数整数。

  public struct Student
  {
    private string name;
    private string score;
 
    public string Name
    {
      get { return name; }
      set { name = value; }
    }
 
    public string Score
    {
      get { return score; }
      set
      {
        CheckScore(value);
        score = value;
      }
    }
 
    //检测分数是否是2位数整数
    private void CheckScore(string value)
    {
      string pattern = @"\d{2}";
      if (!Regex.IsMatch(value, pattern))
      {
        throw new Exception("不是有效分数!");
      }
    }
 
    public override string ToString()
    {
      return String.Format("姓名:{0},分数:{1}", name, score);
    }
  }
 

在主程序中故意制造出一个异常,目的是只对一个变量所代表类型的某些成员赋值。

    static void Main(string[] args)
    {
      Student student = new Student();
      student.Name = "张三";
      student.Score = "80";
      Console.WriteLine(student.ToString());
 
      try
      {
        student.Name = "李四";
        student.Score = "8";
      }
      catch (Exception)
      {
        
        throw;
      }
      Console.WriteLine(student.ToString());
      Console.ReadKey();
    }
 

打断点,运行,发现Student类型的student变量,在第二次赋值的时候,把student的Name属性值改了过来,而student的Score属性,由于发生了异常,没有修改过来。这就是"数据不一致"。

如下图所示:

二、动手设计不可变类型

1.不可变类型的2个特性:

①对象的原子性:要么不改,要改就把所有成员都改,从而创建新的对象。
②对象的常量性:对象一旦创建,就不能改变状态,即不能改变对象的属性,只能创建新的对象。

2.遵循以上不可变类型的2个特征

①在构造函数中对所有字段赋值。
②将属性中的set访问器删除。

  class Program
  {
    static void Main(string[] args)
    {
      Student student = new Student("张三", "90");
      student = new Student("李四","80");
      Console.WriteLine(student.ToString());
      Console.ReadKey();
    }
  }
 
  public struct Student
  {
    private readonly string name;
    private readonly string score;
 
    public Student(string name, string score)
    {
      this.name = name;
      this.score = score;
    }
 
    public string Name
    {
      get { return name; }
    }
 
    public string Score
    {
      get { return score; }
    }
 
    public override string ToString()
    {
      return String.Format("姓名:{0},分数:{1}", name, score);
    }
  }
 

运行结果如下图所示:

由此可见,我们无法修改Student的其中某一个成员,只能通过构造函数创建一个新对象,满足"对象的原子性"。
而且也无法修改Student对象实例的某个属性值,符合"对象的常量性"。

3.如果有引用类型字段和属性,如何做到"不可变性"?

  class Program
  {
    static void Main(string[] args)
    {
      string[] classes = {"语文", "数学"};
      Student student = new Student("张三", "85", classes);
      Console.WriteLine("==修改之前==");
      Console.WriteLine(student.ToString());
 
      string[] tempArray = student.Classes;
      tempArray[0] = "英语";
      Console.WriteLine("==修改之后==");
      Console.WriteLine(student.ToString());
      Console.ReadKey();
    }
  }
 
  public struct Student
  {
    private readonly string name;
    private readonly string score;
    private readonly string[] classes;
 
    public Student(string name, string score, string[] classes)
    {
      this.name = name;
      this.score = score;
      this.classes = classes;
    }
 
    public string Name
    {
      get { return name; }
    }
 
    public string Score
    {
      get { return score; }
    }
 
    public string[] Classes
    {
      get { return classes; }
    }
 
    public override string ToString()
    {
      string temp = string.Empty;
      foreach (string item in classes)
      {
        temp += item + ",";
      }
 
      return String.Format("姓名:{0},总分:{1},参加的课程有:{2}", name, score,temp.Substring(0, temp.Length -1));
    }
  }
 

结果如下图所示:

由此可见,还是可以对对象的属性间接修改赋值,不满足不可变类型的"常量性"特点。

4.通过在构造函数和属性的get访问器中复制的方式来满足不可变性

  class Program
  {
    static void Main(string[] args)
    {
      string[] classes = {"语文", "数学"};
      Student student = new Student("张三", "85", classes);
      Console.WriteLine("==修改之前==");
      Console.WriteLine(student.ToString());
 
      string[] tempArray = student.Classes;
      tempArray[0] = "英语";
      Console.WriteLine("==修改之后==");
      Console.WriteLine(student.ToString());
      Console.ReadKey();
    }
  }
 
  public struct Student
  {
    private readonly string name;
    private readonly string score;
    private readonly string[] classes;
 
    public Student(string name, string score, string[] classes)
    {
      this.name = name;
      this.score = score;
      this.classes = new string[classes.Length];
      classes.CopyTo(this.classes, 0);
      CheckScore(score);
    }
 
    public string Name
    {
      get { return name; }
    }
 
    public string Score
    {
      get { return score; }
    }
 
    public string[] Classes
    {
      get
      {
        string[] result = new string[classes.Length];
        classes.CopyTo(result,0);
        return result;
      }
    }
 
    //检测分数是否是2位数整数
    private void CheckScore(string value)
    {
      string pattern = @"\d{2}";
      if (!Regex.IsMatch(value, pattern))
      {
        throw new Exception("不是有效分数!");
      }
    }
 
    public override string ToString()
    {
      string temp = string.Empty;
      foreach (string item in classes)
      {
        temp += item + ",";
      }
 
      return String.Format("姓名:{0},总分:{1},参加的课程有:{2}", name, score,temp.Substring(0, temp.Length -1));
    }
  }
 

运行结果如下图所示:

此外,如果让分数不满足条件,Student student = new Student("张三", "8", classes),就会报错:

相关文章

  • WPF实现数据绑定的几种方法

    WPF实现数据绑定的几种方法

    Windows Presentation Foundation (WPF) 是微软开发的一套用于构建用户界面的框架,在 WPF 中,数据绑定是一个非常重要的概念,它使得 UI 和数据源之间的同步变得简单和高效,本文将详细分析 WPF 中实现数据绑定的几种方法,需要的朋友可以参考下
    2024-12-12
  • C# 的析构以及垃圾回收实例分析

    C# 的析构以及垃圾回收实例分析

    这篇文章主要介绍了C# 的析构以及垃圾回收实例分析的相关资料,需要的朋友可以参考下
    2017-06-06
  • C#图片处理3种高级应用

    C#图片处理3种高级应用

    本文介绍C#图片处理高级应用,这些功能并无多大技术含量。全部基于.Net Framework类库完成,代码中包含了C#图片处理的一些基础知识,与大家分享,个人能力有限,不足之处还请及时指正。
    2015-10-10
  • c#对XML文档的创建与增删改查的示例代码

    c#对XML文档的创建与增删改查的示例代码

    这篇文章主要介绍了c#对XML文档的创建与增删改查的示例代码,文中讲解非常细致,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • C#遍历操作系统下所有驱动器的方法

    C#遍历操作系统下所有驱动器的方法

    这篇文章主要介绍了C#遍历操作系统下所有驱动器的方法,涉及C#中DriveInfo类GetDrivers方法的使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-04-04
  • c#中Empty()和DefalutIfEmpty()用法分析

    c#中Empty()和DefalutIfEmpty()用法分析

    这篇文章主要介绍了c#中Empty()和DefalutIfEmpty()用法,以实例形式分析了针对不同情况下Empty()和DefalutIfEmpty()用法区别,需要的朋友可以参考下
    2014-11-11
  • Unity3D移动端实现摇一摇功能

    Unity3D移动端实现摇一摇功能

    这篇文章主要为大家详细介绍了基于Unity3D移动端实现摇一摇功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-10-10
  • C#不可变类型深入解析

    C#不可变类型深入解析

    这篇文章主要介绍了C#不可变类型,对于C#程序员深入理解string数据类型有很大的帮助,需要的朋友可以参考下
    2014-08-08
  • C#清除字符串内空格的方法

    C#清除字符串内空格的方法

    这篇文章主要介绍了C#清除字符串内空格的方法,是C#操作字符串非常实用的技巧,需要的朋友可以参考下
    2014-10-10
  • WinForm天猫双11自动抢红包源码分享

    WinForm天猫双11自动抢红包源码分享

    这篇文章主要为大家分享了WinForm天猫双11自动抢红包源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-10-10

最新评论