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

相关文章

  • UnityUI中绘制线状统计图

    UnityUI中绘制线状统计图

    这篇文章介绍了UnityUI中绘制线状统计图的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-04-04
  • 使用GetInvalidFileNameCharts生成文件名

    使用GetInvalidFileNameCharts生成文件名

    这篇文章主要介绍了一个很实用的函数Path.GetInvalidFileNameCharts(),他可以很方便的生成一个有效的文件名称
    2014-01-01
  • C#使用密封类实现密封用户信息的示例详解

    C#使用密封类实现密封用户信息的示例详解

    在C#中,密封类(sealed class)是一种不能被其他类继承的类,它用于防止其他类继承它的功能和属性, 下面我们就来看看如何使用密封类密封用户的信息吧
    2024-02-02
  • 基于WPF实现拟物音量控件

    基于WPF实现拟物音量控件

    这篇文章主要为大家详细介绍了如何基于WPF实现简单的拟物音量控件,文中的示例代码讲解详细,对我们学习或工作有一定帮助,感兴趣的小伙伴可以了解一下
    2023-05-05
  • C#操作注册表的方法

    C#操作注册表的方法

    以下从‘读’‘写’‘删除’‘判断’四个事例实现对注册表的简单操作
    2007-03-03
  • C#使用IronPython调用Python的实现

    C#使用IronPython调用Python的实现

    本文主要介绍了C#使用IronPython调用Python的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • C#位运算符的基本用法介绍

    C#位运算符的基本用法介绍

    这篇文章介绍了C#位运算符的基本用法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08
  • C#简单发送email的方法

    C#简单发送email的方法

    这篇文章主要介绍了C#简单发送email的方法,涉及C#发送Email的相关技巧,非常具有实用价值,需要的朋友可以参考下
    2015-04-04
  • C#使用GZipStream解压缩数据文件的方法

    C#使用GZipStream解压缩数据文件的方法

    这篇文章主要介绍了C#使用GZipStream解压缩数据文件的方法,实例分析了C#中GZipStream方法的原理与使用技巧,需要的朋友可以参考下
    2015-04-04
  • C#中在WebClient中使用post发送数据实现方法

    C#中在WebClient中使用post发送数据实现方法

    这篇文章主要介绍了C#中在WebClient中使用post发送数据实现方法,需要的朋友可以参考下
    2014-08-08

最新评论