C# 9.0新特性——只初始化设置器

自C#1.0版本以来,我们要定义一个不可变数据类型的基本做法就是:先声明字段为readonly,再声明只包含get访问器的属性。例子如下:

1、背景与动机

自C#1.0版本以来,我们要定义一个不可变数据类型的基本做法就是:先声明字段为readonly,再声明只包含get访问器的属性。例子如下:

struct Point
{
  public int X { get; }
  public int Y { get; }

  public Point(int x, int y)
  {
    this.X = x;
    this.Y = y;
  }
}

这种方式虽然很有效,但是它是以添加大量代码为代价的,并且类型越大,属性就越多,工作量就大,也就意味着更低的生产效率。

为了节省工作量,我们也用对象初始化方式来解决。对于创建对象来说,对象初始化方式是一种非常灵活和可读的方式,特别对一口气创建含有嵌套结构的树状对象来说更有用。下面是一个简单的对象初始化的例子:

var person = new Person{ FirstName = "Mads", LastName = "Torgersen" };

从这个例子,可以看出,要进行对象初始化,我们不得不先要在需要初始化的属性中添加set访问器,然后在对象初始化器中,通过给属性或者索引器赋值来实现。

public class Person
{
  public string? FirstName { get; set; }
  public string? LastName { get; set; }
}

这种方式最大的局限就是,对于初始化来说,属性必须是可变的,也就是说,set访问器对于初始化来说是必须的,而其他情况下又不需要set,而且我们需要的是不可变对象类型,因此这个setter明显在这里就不合适。既然有这种常见的需要和局限性,那么我为何不引入一个只能用来初始化的Setter呢?于是只用来初始化的init设置访问器就出现了。这时,上面的Point结构定义就可以简化成下面的样子:

struct Point
{
  public int X { get; init; }
  public int Y { get; init; }
}

那么现在,我们使用对象初始化器来创建一个实例:

var p = new Point() { X = 54, Y = 717 };

第二例子Person类型中,将set访问器换为init就成了不可变类型了。同时,使用对象初始化器方式没有变化,依然如前面所写。

public class Person
{
  public string? FirstName { get; init; }
  public string? LastName { get; init; }
}

通过采用init访问器,编码量减少,满足了只读需求,代码简洁易懂。

2. 定义和要求

只初始化属性或索引器访问器是一个只在对象构造阶段进行初始化时用来赋值的set访问器的变体,它通过在set访问器的位置来使用init来实现的。init有着如下限制:

  • init访问器只能用在实例属性或索引器中,静态属性或索引器中不可用。
  • 属性或索引器不能同时包含init和set两个访问器
  • 如果基类的属性有init,那么属性或索引器的所有相关重写,都必须有init。接口也一样。

2.1 init访问器可设置值的时机

除过在局部方法和lambda表达式中,带有init访问器的属性和索引器在下面情况是被认为可设置的。这几个可以进行设置的时机,在这里统称为对象的构造阶段。除过这个构造阶段之外,其他的后续赋值操作是不允许的。

  • 在对象初始化器工作期间
  • 在with表达式初始化器工作期间
  • 在所处或者派生的类型的实例构造函数中,在this或者base使用上
  • 在任意属性init访问器里面,在this或者base使用上
  • 在带有命名参数的attribute使用中

根据这些时机,这意味着Person类可以按如下方式使用。在下面代码中第一行初始化赋值正确,第二行再次赋值就不被允许了。这说明,一旦初始化完成之后,只初始化属性或索引就保护着对象的状态免于改变。

var person = new Person { FirstName = "Mads", LastName = "Nielsen" }; // OK
person.LastName = "Torgersen"; // 错误!

2.2 init属性访问器和只读字段

因为init访问器只能在初始化时被调用,所以在init属性访问器中可以改变封闭类的只读字段。需要注意的是,从init访问器中来给readonly字段赋值仅限于跟init访问器处于同一类型中定义的字段,通过它是不能给父类中定义的readonly字段赋值的,关于这继承有关的示例,我们会在2.4类型间的层级传递中看到。

public class Person
{
  private readonly string firstName = "<unknown>";
  private readonly string lastName = "<unknown>";

  public string FirstName
  {
    get => firstName;
    init => firstName = (value ?? throw new ArgumentNullException(nameof(FirstName)));
  }
  public string LastName
  {
    get => lastName;
    init => lastName = (value ?? throw new ArgumentNullException(nameof(LastName)));
  }
}

2.3 类型层级间的传递

我们知道只包含get访问器的属性或索引器只能在所处类的自身构造函数中被初始化,但init访问器可以进行设置的规则是可以跨类型层级传递的。带有init访问器的成员只要是可访问的,对象实例并能在构造阶段被知晓,那这个成员就是可设置的。

class Person
{
  public Person()
  {
    //下面这段都是允许的
    Name = "Unknown";
    Age = 0;
  }
  public string Name { get; init; }
  public int Age { get; }

}

class Adult : Person
{
  public Adult()
  {
    // 只有get访问器的属性会出错,但是带有init是允许的
    Name = "Unknown Adult"; //正确
    Age = 18; //错误
  }
}

class Consumption
{
  void Example()
  {
    var d = new Adult()
    {
      Name = "Jack", //正确
      Age = 23 //错误,因为是只读,只有get
    };
  }
}

从init访问器能被调用这一方面来看,对象实例在开放的构造阶段就可以被知晓。因此除过正常set可以做之外,init访问器的下列行为也是被允许的。

  • 通过this或者base调用其他可用的init访问器
  • 在同一类型中定义的readonly字段,是可以通过this给赋值的
class Example
{
  public Example()
  {
    Prop1 = 1;
  }

  readonly int Field1;
  string Field2;
  int Prop1 { get; init; }
  public bool Prop2
  {
    get => false;
    init
    {
      Field1 = 500; // 正确
      Field2 = "hello"; // 正确
      Prop1 = 50; // 正确
    }
  }
}

前面2.2节中提到,init中是不能更改父类中的readonly字段的,只能更改本类中readonly字段。示例代码如下:

class BaseClass
{
  protected readonly int Field;
  public int Prop
  {
    get => Field;
    init => Field = value; // 正确
  }

  internal int OtherProp { get; init; }
}

class DerivedClass : BaseClass
{
  protected readonly int DerivedField;
  internal int DerivedProp
  {
    get => DerivedField;
    init
    {
      DerivedField = 89; // 正确
      Prop = 0;    // 正确
      Field = 35;   // 出错,试图修改基类中的readonly字段Field
    }
  }

  public DerivedClass()
  {
    Prop = 23; // 正确
    Field = 45;   // 出错,试图修改基类中的readonly字段Field
  }
}

如果init被用于virtual修饰的属性或者索引器,那么所有的覆盖重写都必须被标记为init,是不能用set的。同样地,我们不可能用init来覆盖重写一个set的。

class Person
{
  public virtual int Age { get; init; }
  public virtual string Name { get; set; }
}

class Adult : Person
{
  public override int Age { get; init; }
  public override string Name { get; set; }
}

class Minor : Person
{
  // 错误: 属性必须有init来重写Person.Age
  public override int Age { get; set; }
  // 错误: 属性必须有set来重写Person.Name
  public override string Name { get; init; }
}

2.4 init和接口

一个接口中的默认实现,也是可以采用init进行初始化,下面就是一个应用模式示例。

interface IPerson
{
  string Name { get; init; }
}

class Initializer
{
  void NewPerson<T>() where T : IPerson, new()
  {
    var person = new T()
    {
      Name = "Jerry"
    };
    person.Name = "Jerry"; // 错误
  }
}

2.5 init和readonly struct

init访问器是允许在readonly struct中的属性中使用的,init和readonly的目标都是一致的,就是只读。示例代码如下:

readonly struct Point
{
  public int X { get; init; }
  public int Y { get; init; }
}

但是要注意的是:

不管是readonly结构还是非readonly结构,不管是手工定义属性还是自动生成属性,init都是可以使用的。
init访问器本身是不能标记为readonly的。但是所在属性或索引器可以被标记为readonly

struct Point
{
  public readonly int X { get; init; } // 正确
  public int Y { get; readonly init; } // 错误
}

作者:MarkKang
出处:https://www.cnblogs.com/markkang/

以上就是C# 9.0新特性——只初始化设置器的详细内容,更多关于C# 9.0新特性的资料请关注我们其它相关文章!

(0)

相关推荐

  • 浅谈C#9.0新特性之参数非空检查简化

    参数非空检查是缩写类库很常见的操作,在一个方法中要求参数不能为空,否则抛出相应的异常.比如: public static string HashPassword(string password) { if(password is null) { throw new ArgumentNullException(nameof(password)); } ... } 当异常发生时,调用者很容易知道是什么问题.如果不加这个检查,可能就会由系统抛出未将对象引用为实例之类的错误,这不利于调用者诊断错误. 由

  • C# 9.0 新特性之模式匹配简化的实现

    记得在 MS Build 2020 大会上,C# 语言开发项目经理 Mads Torgersen 宣称 C# 9.0 将会随着 .NET 5 在今年 11 月份正式发布.目前 .NET 5 已经到了 Preview 5 阶段了,C# 9.0 也已经初具规模.忍不住激动的心情,暂停更新<C#.NET 拾遗补漏>系列几天,先要和大家分享一下我了解到的 C# 9.0 的新特性.由于新特性比较多,所以会分成几篇来讲.这是第一篇,专讲模式匹配这个特性的简化. 模式匹配(Pattern Matching)

  • C#9.0 新特性简介

    CandidateFeaturesForCSharp9 看到标题,是不是认为我把标题写错了?是的,C# 8.0还未正式发布,在官网它的最新版本还是Preview 5,通往C#9的漫长道路却已经开始.前写天收到了活跃在C#一线的BASSAM ALUGILI给我分享C# 9.0新特性,我在他文章的基础上进行翻译,希望能对大家有所帮助. 这是世界上第一篇关于C#9候选功能的文章.阅读完本文后,你将会为未来可能遇到的C# 9.0新特性做好更充分的准备. 这篇文章基于, C# 9.0候选新特性 原生大小的

  • c# 9.0新特性nint和Pattern matching的使用方法

    一:背景 1. 讲故事 上一篇跟大家聊到了Target-typed new 和 Lambda discard parameters,看博客园和公号里的阅读量都达到了新高,甚是欣慰,不管大家对新特性是多头还是空头,起码还是对它抱有一种极为关注的态度,所以我的这个系列还得跟,那就继续开撸吧,今天继续带来两个新特性,更多新特性列表,请大家关注:新特性预览 二:新特性研究 1. Native ints 从字面上看貌似是什么原生类型ints,有点莫名其妙,还是看一看Issues上举得例子吧: Summar

  • 浅谈C# 9.0 新特性之只读属性和记录

    大家好,这是 C# 9.0 新特性系列的第 4 篇文章. 熟悉函数式编程的童鞋一定对"只读"这个词不陌生.为了保证代码块自身的"纯洁",函数式编程是不能随便"弄脏"外来事物(参数.变量等)的,所以"只读"对函数式编程非常重要. 为了丰富 C# 对函数式编程支持,较新的 C# 版本引入了一些很有用的新特性.比如 C# 8 中就对 struct 类型的方法增加了 readonly 修饰符支持,被 readonly 修饰的方法是不能

  • 浅析C# 9.0 新特性之 Lambda 弃元参数

    大家好,这是 C# 9.0 新特性短系列的第 5 篇文章. 弃元(Discards) 是在 C# 7.0 的时候开始支持的,它是一种人为丢弃不使用的临时虚拟变量.语法上它是用来赋值的,但它却不被分配存储空间,即没有值,所以不能从中读取值.弃元用 _(下划线) 表示,下划线是一个关键字,只能赋值,不能读取,例如: 在 C# 7.0 中,弃元的使用场景主要有下面四种: 元组和对象的解构 使用 is 和 switch 的模式匹配 对具有 out 参数的方法的调用 作用域内独立使用场景 针对这几个场景,

  • C# 9.0新特性——只初始化设置器

    自C#1.0版本以来,我们要定义一个不可变数据类型的基本做法就是:先声明字段为readonly,再声明只包含get访问器的属性.例子如下: 1.背景与动机 自C#1.0版本以来,我们要定义一个不可变数据类型的基本做法就是:先声明字段为readonly,再声明只包含get访问器的属性.例子如下: struct Point { public int X { get; } public int Y { get; } public Point(int x, int y) { this.X = x; th

  • c# 9.0新特性——模块初始化器

    作者:MarkKang 出处:https://www.cnblogs.com/markkang/ 1 背景动机 关于模块或者程序集初始化工作一直是C#的一个痛点,微软内部外部都有大量的报告反应很多客户一直被这个问题困扰,这还不算没有统计上的客户.那么解决这个问题,还有基于什么样的考虑呢? 在库加载的时候,能以最小的开销.无需用户显式调用任何接口,使客户做一些期望的和一次性的初始化. 当前静态构造函数方法的一个最大的问题是运行时会对带有静态构造函数的类型做一些额外的检查.这是因为要决定静态构造函数

  • PHP 5昨天隆重推出--PHP 5/Zend Engine 2.0新特性

    前言 今天突然想到PHP官方网站上一转,一眼就看到PHP5推出的通告.虽然以前看到过PHP5的预告,但还是仔细看了PHP 5/Zend Engine 2.0新特性一文,一股JAVA气息扑面而来...   特将该文试译出来,首发于CSDN网站,以飨读者. PHP 5/Zend Engine 2.0新特性徐唤春 译 sfwebsite@hotmail.comhttp://www.php.net/zend-engine-2.php 全新的对象模型PHP中的对象处理部分已完全重写,具有更佳的性能和更多的

  • Android5.0新特性详解之全新的动画

    在Material Design设计中,为用户与app交互反馈他们的动作行为和提供了视觉上的连贯性.Material主题为控件和Activity的过渡提供了一些默认的动画,在android L上,允许自定义这些动画: Touch feedback 触摸反馈 Circular Reveal 圆形展示 Curved motion 曲线运动 View state changes 视图状态变化 Vector Drawables 矢量图动画 Activity transitions 活动转场 触摸反馈 触

  • C# 8.0新特性介绍

    C# 语言是在2000发布的,至今已正式发布了7个版本,每个版本都包含了许多令人兴奋的新特性和功能更新.同时,C# 每个版本的发布都与同时期的 Visual Studio 以及 .NET 运行时版本高度耦合,这也有助于开发者更好的学习掌握 C#,并将其与 Visual Studio 以及 .NET 的使用结合起来. 加快 C# 版本的发布速度 在被称为"新微软"的推动下,微软创新的步伐也加快了.为了做到加快步伐,微软开发部门将一些过去集成在一起的技术现在都分离了出来. Visual S

  • AngularJS 2.0新特性有哪些

    AngularJS已然成为Web应用开发世界里最受欢迎的开源JavaScript框架.自成立以来,见证其成功的是惊人的经济增长以及团体的支持与采用--包括个人开发者.企业.社区. Angular已经变成一个构建复杂单页面应用的客户端MVW框架(Model-View-Whatever).它在应用测试和应用编写方面都扮演重要角色,同时简化了开发过程. Angular目前的版本为1.3,该版本稳定,并被谷歌(框架维护者)用于支持众多应用(据估计,在谷歌有超过1600个应用运行于Angular1.2或1

  • C# 6.0 新特性汇总

    1. 静态using(static using) 静态using声明允许不使用类名直接调用静态方法. The static using declaration allows invoking static methods without the class name. In C# 5 using System; Console.WriteLine("Hello, World!"); In C# 6 using static System.Console; WriteLine("

  • Android Studio3.0新特性及安装图文教程

    Android Studio是Android的官方IDE.它是专为Android而打造,可以加快您的开发速度,帮助您为每款Android设备构建最优应用. 它提供专为Android开发者量身定制的工具,其中包括丰富的代码编辑.调试.测试和性能分析工具. 一.Android Studio3.0新特性 (1).核心IDE更改 我们将基础IDE从IntelliJ 2016.2升级到2017.1.2,在2016.3和 2017.1中增加了许多新功能, 包括参数提示,语义突出显示,搜索中的即时结果等等.

随机推荐