Effective C# 原则7: 选择恒定的原子值类型数据

Effective C# 7 选择恒定的原子值类型数据

恒定(immutable types)简单,就是一但它建,它()就是固定的。如果你验证一些准用于建一个象的参数,你知道它在验证从前面的点上看。你不能修改一个象的内部状使之成无效的。在一个象被建后,你必自己小心翼翼的保护对象,否你不得不做错误验证来禁止改任何状。恒定型天生就具有线程完全性的特点:多访问者可同时访问相同的内容。如果内部状不能修改,那就不能不同的线程提供看不一致的数据视图的机会。恒定型可以从你的上安全的暴露出来。用者不能修改象的内部状。恒定型可以很好的在基于哈希代的集合上工作。以Object.GetHashCode()方法返回的同一个例是必相同的(10),而正是恒定能成功的地方。

并不是所有的型都能成恒定型的。如果它可以,你需要克隆一个象用于修改任何程序的状了。就是推荐使用恒定型和原子型数据了。把你的象分解自然的构。一个Address型就是的,它就是一个简单的事,由多个相的字段成。改其中一个字段就很可能意味着修改了其它字段。一个客户类型不是一个原子型,一个客户类型可能包含很多小的信息:地址,名字,一个或者多个电话。任何一个互不关联的信息都可以改。一个客可能会在不搬家的情况下改变电话。而另一个客可能在搬了家的情况下保留原来的电话有可能,一个客了他()的名字,而没有搬家也没有改电话。一个客户类型就不是原子型;它是由多个不同的恒定的成部份构成的:地址,名字,以及一个成电话集合。原子型是体:你很自然的用原子型来取代体内容。一例外会改它其中的一个成字段。

下面就是一个典型的可地址实现
// Mutable Address structure.
public struct Address
{
  private string  _line1;
  private string _line2;
  private string  _city;
  private string _state;
  private int    _zipCode;

  // Rely on the default system-generated
  // constructor.

  public string Line1
  {
    get { return _line1; }
    set { _line1 = value; }
  }
  public string Line2
  {
    get { return _line2; }
    set { _line2 = value; }
  }
  public string City
  {
    get { return _city; }
    set { _city= value; }
  }
  public string State
  {
    get { return _state; }
    set
    {
      ValidateState(value);
      _state = value;
    }
  }
  public int ZipCode
  {
    get { return _zipCode; }
    set
    {
      ValidateZip( value );
      _zipCode = value;
    }
  }
  // other details omitted.
}

// Example usage:
Address a1 = new Address( );
a1.Line1 = "111 S. Main";
a1.City = "Anytown";
a1.State = "IL";
a1.ZipCode = 61111 ;
// Modify:
a1.City = "Ann Arbor"; // Zip, State invalid now.
a1.ZipCode = 48103; // State still invalid now.
a1.State = "MI"; // Now fine.

内部状的改意味着它很可能反了象的不性,至少是临时的。当你改City个字段后,你就使a1于无效状。城市的改使得它与洲字段及以区字段不再匹配。代的有害性看上去不足以致命,但这对于多线程程序来只是一小部份。在城市化以后,洲化以前的任何内容转变,都会潜在的使另一个线程看到一份矛盾的数据视图

Okay,所以你不准去写多线程程序。你仍然于困境当中。想象这样问题,区代是无效的,并且置抛出了一个异常。你只是完成了一些你想做的事,可你却使系统处于一个无效的状当中。了修正问题,你要在地址里面添加一个相当大的内部验证码验证码应该须要相当大的空,并且很复杂了完全实现期望的安全性,当你修改多个字段,你要在你的代码块围创建一个被的数据COPY线程安全性可能要求添加一个明确的线程同用于检测每一个属性访问器,包括setget而言之,将是一个意重大的行--并且很可能在你添加新功能分的展。

取而代之,把address构做一个恒定型。始把所有的字段都改成只的吧:
public struct Address
{
  private readonly string  _line1;
  private readonly string  _line2;
  private readonly string  _city;
  private readonly string  _state;
  private readonly int    _zipCode;

  // remaining details elided
}

要移除所有的属性置功能:
public struct Address
{
  // ...
  public string Line1
  {
    get { return _line1; }
  }
  public string Line2
  {
    get { return _line2; }
  }
  public string City
  {
    get { return _city; }
  }
  public string State
  {
    get { return _state; }
  }
  public int ZipCode
  {
    get { return _zipCode; }
  }
}

在,你就有了一个恒定型。它有效的工作,你必添加一个构造函数来完全初始化address构。address构只外的添加一个构造函数,来验证每一个字段。一个拷构造函数不是必的,因为赋值运算符算高效。住,默的构造函数仍然是可访问的。是一个默所有字符串nullZIP码为0的地址构:
public struct Address
{
  private readonly string  _line1;
  private readonly string  _line2;
  private readonly string  _city;
  private readonly string  _state;
  private readonly int    _zipCode;

  public Address( string line1,
    string line2,
    string city,
    string state,
    int zipCode)
  {
    _line1 = line1;
    _line2 = line2;
    _city = city;
    _state = state;
    _zipCode = zipCode;
    ValidateState( state );
    ValidateZip( zipCode );
  }

  // etc.
}

在使用个恒定数据,要求直接用不同的用来一的修改它的状。你更宁愿建一个新的象而不是去修改某个例:
// Create an address:
Address a1 = new Address( "111 S. Main",
  "", "Anytown", "IL", 61111 );

// To change, re-initialize:
a1 = new Address( a1.Line1,
  a1.Line2, "Ann Arbor", "MI", 48103 );

a1是两者之一:它的原始位置Anytown,或者是后来更新后的位置Ann Arbor。你再不用像前面的例子那了修改已存在的地址而使临时无效状里只有一些在构造函数才存在的临时,而在构造函数外是无法访问内部状的。很快,一个新的地址象很快就生了,它的就一直固定了。正是期望的安全性:a1是默的原始,要是新的。如果在构造时发生了异常,那a1保持原来的默认值

(注:在构造时发生异常不会影响a1呢?因只要构造函数没有正确返回,a1都只保持原来的。因是那是一个赋值语句。也就是要用构造函数来实现对象更新,而不是另外添加一个函数来更新象,因就算用一个函数来更新象,也有可能更新到一半生异常,也会使得于不正确的状当中。大家可以参考一下.Net里的日期时间结构,它就是一个典型的恒定常量例子。它没有提供任何的对单独年,月,日或者星期行修改的方法。因为单独修改其中一个,可能致整个日期于不正确的状:例如你把日期独的修改31号,但很可能那个月没有31号,而且星期也可能不同。它同也是没提供任何方法来同时设置所以参数,了条原后就明白了吧。参考一下DateTime构,可以更好的理解要使用恒定型。注:有些immutable type译为变类型。)

建一个恒定型,你要确保你的用没有任何机会来修改内部状值类型不支持派生,所以你不必定担心派生来修改它的内部状。但你要注意任何在恒定型内的可的引用型字段。当你为这实现了构造函数后,你要被的把可的引用COPY一遍(注:被COPYdefensive copy,文中应该是指了保数据,在数据赋值时不得不行的一个COPY,所以被认为防守,我译为:被,表示拷不是自的,而是不得以而之的)
所有些例子,都是假Phone是一个恒定的值类型,因及到值类型的恒定性:

// Almost immutable: there are holes that would
// allow state changes.
public struct PhoneList
{
  private readonly Phone[] _phones;

  public PhoneList( Phone[] ph )
  {
    _phones = ph;
  }

  public IEnumerator Phones
  {
    get
    {
      return _phones.GetEnumerator();
    }
  }
}

Phone[] phones = new Phone[10];
// initialize phones
PhoneList pl = new PhoneList( phones );

// Modify the phone list:
// also modifies the internals of the (supposedly)
// immutable object.
phones[5] = Phone.GeneratePhoneNumber( );

个数是一个引用型。PhoneList内部引用的数,引用了分配在象外的数上。开发可以通另一个引用到个存上的象来修改你的恒定构。了避免这种可能,你对这个数做一个被。前面的例子示了可集合的弊端。如果电话类型是一个可的引用型,它会有更多危害存在的可能。客可以修改它在集合里的,即使个集合是保,不任何人修改。个被的拷贝应该个构造函数里被实现,而不管你的恒定型里是否存在引用象:

// Immutable: A copy is made at construction.
public struct PhoneList
{
  private readonly Phone[] _phones;

  public PhoneList( Phone[] ph )
  {
     _phones = new Phone[ ph.Length ];
     // Copies values because Phone is a value type.
     ph.CopyTo( _phones, 0 );
  }

  public IEnumerator Phones
  {
    get
    {
      return _phones.GetEnumerator();
    }
  }
}

Phone[] phones = new Phone[10];
// initialize phones
PhoneList pl = new PhoneList( phones );

// Modify the phone list:
// Does not modify the copy in pl.
phones[5] = Phone.GeneratePhoneNumber( );

当你返回一个可变类型的引用,也应该遵守一原。如果你添加了一个属性用于从PhoneList构中取得整个数表,访问器也必须实现一个被情参23

复杂型表明了三个策略,是你在初始化你的恒定时应该使用的。Address构定了一个构造函数,你的客可以初始化一个地址,定合理的构造函数通常是最容易达到的。

你同可以建一个工厂方法来实现一个构。工厂使得建一个通用的型数据得更容易。.Net框架的Color型就是遵从一策略来初始化系统颜色的。个静的方法Color.FromKnownColor()Color.FromName()从当前示的色中拷一个定的系统颜色,返回

第三,你可以那些需要多作才能完成构造函数的恒定型添加一个伴随.Net框架里的字符串就遵从一策略,它利用了伴随System.Text.StringBuilter。你是使用StringBuliter类经过操作来建一个字符串。在完成了所有必须步骤生成一个字符串后,你从StringBuilter取得了一个恒定的字符串。
(
注:.net里的string是一但初始化,就不能再修改,它的任何改都会生成新的字符串。因此多次操作一个string多的垃圾内存碎片,你可以用StringBuliter来平衡问题)

恒定型是更简单,更容易维护的。不要盲目的你的一个象的属性getset访问器。你对这型的第一选择是把些数存储为恒定型,原子型。从体中,你可以可以容易的建更多复杂

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值