C#不可变类型深入解析

 更新时间:2020年6月25日 11:35  点击:1837

学过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),就会报错:

[!--infotagslink--]

相关文章

  • C#实现简单的登录界面

    我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望大家能够喜欢。...2020-06-25
  • 浅谈C# 字段和属性

    这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
  • C#中截取字符串的的基本方法详解

    这篇文章主要介绍了C#中截取字符串的的基本方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-03
  • C#连接SQL数据库和查询数据功能的操作技巧

    本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
  • C#实现简单的Http请求实例

    这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
  • C#中new的几种用法详解

    本文主要介绍了C#中new的几种用法,具有很好的参考价值,下面跟着小编一起来看下吧...2020-06-25
  • 使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序)

    这篇文章主要介绍了使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#开发Windows窗体应用程序的简单操作步骤

    这篇文章主要介绍了C#开发Windows窗体应用程序的简单操作步骤,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
  • C#从数据库读取图片并保存的两种方法

    这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
  • C#和JavaScript实现交互的方法

    最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
  • 经典实例讲解C#递归算法

    这篇文章主要用实例讲解C#递归算法的概念以及用法,文中代码非常详细,帮助大家更好的参考和学习,感兴趣的朋友可以了解下...2020-06-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • 轻松学习C#的基础入门

    轻松学习C#的基础入门,了解C#最基本的知识点,C#是一种简洁的,类型安全的一种完全面向对象的开发语言,是Microsoft专门基于.NET Framework平台开发的而量身定做的高级程序设计语言,需要的朋友可以参考下...2020-06-25
  • C#变量命名规则小结

    本文主要介绍了C#变量命名规则小结,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-09
  • c#中(&&,||)与(&,|)的区别详解

    这篇文章主要介绍了c#中(&&,||)与(&,|)的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
  • C#绘制曲线图的方法

    这篇文章主要介绍了C#绘制曲线图的方法,以完整实例形式较为详细的分析了C#进行曲线绘制的具体步骤与相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • C# 中如何取绝对值函数

    本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
  • c#自带缓存使用方法 c#移除清理缓存

    这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
  • C#学习笔记- 随机函数Random()的用法详解

    下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#中list用法实例

    这篇文章主要介绍了C#中list用法,结合实例形式分析了C#中list排序、运算、转换等常见操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25