用智能的编译器来防错

本章的主要内容:

  • 自动实现的属性:编写由字段直接支持的简单属性, 不再显得臃肿不堪;
  • 隐式类型的局部变量:根据初始值推断类型,简化局部变量的声明;
  • 对象和集合初始化程序:用一个表达式就能创建和初始化对象;
  • 隐式类型的数组:根据内容推断数组的类型,从而简化数组的创建过程;
  • 匿名类型:允许创建新的临时类型来包含简单的属性;

自动实现的属性

这个特性简单的我都不想描述,但是为了保持内容的完整性,放一张图:

和匿名方法还有迭代器一样,它在编译器的帮助下会生成一个后备字段。

自动实现的属性是赋值和取值方法都是共有的,当然你还可以继续使用C#2私有赋值方法。

在实现自己的结构(struct)时,所有构造函数都必须显式的调用一下无参的构造函数,只有这样,编译器才知道所有的字段都被明确赋值了。因为这里有个类型初始化的顺序:

类或结构在初始化时的执行顺序,依次如下:

1: 子类静态变量

2: 子类静态构造函数

3: 子类非静态变量

4: 父类静态变量

5: 父类静态构造函数

6: 父类非静态变量

7: 父类构造函数

8: 子类构造函数

可以看到除了构造函数以外其他东西都是先初始化本身在初始化基类的。

隐式类型的局部变量

首先需要说明一点的时隐式类型只能用于局部变量,不能用于字段变量。

第二点是,C#仍然是一门静态的语言,只是要求编译器为你来推断变量的类型,在编译时仍然是类型静态的。

用var关键字来声明一个隐式类型的局部变量。

小小的总结:不是在所有情况下都能为所有变量使用隐式类型, 只有在以下情况下才能用它:

  • 被声明的变量是一个局部变量, 而不是静态字段和实例字段;
  • 变量在声明的同时被初始化;
  • 初始化表达式不是方法组, 也不是匿名函数( 如果不进行强制类型转换);
  • 初始化表达式不是 null;
  • 语句中只声明了一个变量;
  • 你希望变量拥有的类型是初始化表达式的编译时类型;
  • 初始化表达式不包含正在声明的变量 。

隐式类型的局部变量也有一些不好的地方,有的时候你不得不仔细的判断它的类型。例如:

  • var a = 2147483647;
  • var b = 2147483648;
  • var c = 4294967295;
  • var d = 4294967296;
  • var e = 9223372036854775807;
  • var f = 9223372036854775808;

上面的这些变量的类型都不好在第一时间就判断出来。但是,有的时候你不得不用,比如要返回一个匿名的类型,只能这样写:var a=new {name="pangjianxin",age=10};这样的表达式你要用什么类型来引用呢?

简化的初始化

直接上代码。

public class Person
{
public string Name { get; }
public int Age { get; set; }
public List<Person> Persons { get; } = new List<Person>();
public Location Location { get; } = new Location();
public Person()
{ }
public Person(string name)
{
this.Name = name;
}
} public class Location
{
public string City { get; set; }
public string Street { get; set; }
}

上面定义两个类,一个Person类,一个Location类。Person类中维护两个自动属性Name和Age,另外,还维护了两个只读属性Persons和Location。还有一个无参的构造函数和一个有参数的构造函数。

前面已经说过,类会在静态的和非静态的字段初始化后才会执行构造函数,属性本质上来说是一对get/set方法,不存在初始化。

看一下调用情况:

 static void Main(string[] args)
{
Person p = new Person("pangianxin")
{
Age = ,
Location = { City = "baotou", Street = "gangtiedajie" }
};
Console.WriteLine(p.Location.City);
//p.Location=new Location();无法对Location进行初始化,因为他是只读的。

p.Location.City = "baotou ";
              p.Location.Street = "gangtiedajie ";

            Console.ReadKey();
}

上面使用了对象初始化程序来对对象进行初始化。

首先注意到的是p.Location是一个只读的属性。我们不能直接该给属性进行赋值,但是可以在取到这个属性的引用后,再对其进行赋值,在C#语言规范里面,这个叫做“设置一个嵌入对象的属性”。就是设置属性的属性。这样却没有了限制。

第二点是Location = { City = "baotou", Street = "gangtiedajie" }这句。编译器发现等号右侧的是另一个对象初始化程序, 所以会适当地将属性应用到嵌入对象。

集合初始化程序

var names = new List { "Holly", "Jon", "Tom", "Robin", "William" };

就是这样。

同样是编译器在后台调用add方法来将元素add进集合。

集合初始化程序必须要遵循以下两点:

  • 实现IEnumerale
  • 具有add方法

对于第一点来说,要求实现IEnumerable是合理的,因为编译器必须得知道是某种意义上的集合。对于第二点,因为编译器会在后台调用add方法来存放元素,所以,你初始化的这个集合必须得保证有这个add方法。

隐式类型的数组

string[] names = {"Holly", "Jon", "Tom", "Robin", "William"};

这种方式看起来很简洁,但是不能将大括号里面的东西直接传递给方法:MyMethod({" Holly", "Jon", "Tom", "Robin", "William"});这样会报错。要这样:MyMethod( new string[] {"Holly", "Jon", "Tom", "Robin", "William"});

匿名类型

这个玩意儿才是今天的重点。

匿名类型在与更高级的特性结合起来才会更有用。

用这个东西初始化的类型也只能用var来承接了:var myInfo=new {name="pangjianxin“,age=19};当然。除了object以外。

如果两个匿名对象初始化程序包含相同数量的属性, 且这些属性具有相同的名称和类型, 而且以相同的顺序出现, 就认为它们是同一个类型。

static void Main(string[] args)
{
var persons = new[]
{
new
{
name = "pangjianxin",
age =
},
new
{
name = "pangjianxin",
age =
},
new
{
name = "pangjianxin",
age =
},
new
{
name = "pangjianxin",
age =
},
new
{
name = "pangjianxin",
age =
}
};
Console.ReadKey();
}

如果上面的匿名类型的类型不一致,比如吧其中一个的属性的顺序颠倒,额外增加一个属性等等,编译器会报错:”找不到隐式类型数组的最佳类型“。

编译器在后台为匿名类型生成了一个泛型的类型来帮助匿名类型进行初始化,这个泛型类被放到了一个单独的程序集中。它在后台生成的类名超级变态:

它将匿名类型中的name和age的类型作为泛型类型的类型参数,然后,main方法中变成了这样:

编译器厉害吧?

返回来看一下匿名类型具有哪些成员:

首先它是继承了object。废话

它有后备字段和只读的属性

有获取所有初始值的构造函数。

重写了object的Equals、GetHashCode、ToString

由于所有属性都是只读的,所以只要这些属性是不易变的, 那么匿名类型就是不易变的。 这就为你提供了“ 不易变” 这一特性所具有全部常规性的优势—— 能放心向方法传递值, 不用害怕这些值被改变; 能在线程之间共享数据, 等等。

投影初始化程序

var person = new {name = "pangjianxin", age = 19};
var anotherPerson = new {name = person.name, isAdult = (person.age > 18)};

anotherPerson利用person的属性形成了一个新的匿名类型。这就是投影初始化程序。

不过匿名类型最大的用处在于linq中。利用select或selectmany等操作可以从横向的缩小要查找的范围。

C#复习笔记(4)--C#3:革新写代码的方式(用智能的编译器来防错)的更多相关文章

  1. C#复习笔记(4)--C#3:革新写代码的方式(Lambda表达式和表达式树)

    Lambda表达式和表达式树 先放一张委托转换的进化图 看一看到lambda简化了委托的使用. lambda可以隐式的转换成委托或者表达式树.转换成委托的话如下面的代码: Func<string ...

  2. C#复习笔记(4)--C#3:革新写代码的方式(查询表达式和LINQ to object(下))

    查询表达式和LINQ to object(下) 接下来我们要研究的大部分都会涉及到透明标识符 let子句和透明标识符 let子句不过是引入了一个新的范围变量.他的值是基于其他范围变量的.let 标识符 ...

  3. C#复习笔记(4)--C#3:革新写代码的方式(扩展方法)

    扩展方法 扩展方法有以下几个需求: 你想为一个类型添加一些 成员: 你不需要为类型的实例添加任何更多的数据: 你不能改变类型本身, 因为是别人的代码. 对于C#1和C#2中的静态方法,扩展方法是一种更 ...

  4. Java 10 的 10 个新特性,将彻底改变你写代码的方式!

    Java 9才发布几个月,很多玩意都没整明白,现在Java 10又快要来了.. 这时候我真尼玛想说:线上用的JDK 7 甚至JDK 6,JDK 8 还没用熟,JDK 9 才发布不久不知道啥玩意,JDK ...

  5. Java 10的10个新特性,将彻底改变你写代码的方式!

    Java 9才发布几个月,很多玩意都没整明白,现在Java 10又快要来了.. 这时候我真尼玛想说:线上用的JDK 7 甚至JDK 6,JDK 8 还没用熟,JDK 9 才发布不久不知道啥玩意,JDK ...

  6. Java 8 到 Java 14,改变了哪些你写代码的方式?

    前几天,JDK 14 正式发布了,这次发布的新版本一共包含了16个新的特性. 其实,从Java8 到 Java14 ,真正的改变了程序员写代码的方式的特性并不多,我们这篇文章就来看一下都有哪些. La ...

  7. Java基础复习笔记系列 八 多线程编程

    Java基础复习笔记系列之 多线程编程 参考地址: http://blog.csdn.net/xuweilinjijis/article/details/8878649 今天的故事,让我们从上面这个图 ...

  8. Java二次复习笔记(1)

    Java二次复习笔记(1) Java采用的默认字符编码集是Unicode. byte=byte+byte报错,值为int,而byte+=byte不报错.同样short = short+short报错, ...

  9. 斜率优化DP复习笔记

    前言 复习笔记2nd. Warning:鉴于摆渡车是普及组题目,本文的难度定位在普及+至省选-. 参照洛谷的题目难度评分(不过感觉部分有虚高,提高组建议全部掌握,普及组可以选择性阅读.) 引用部分(如 ...

随机推荐

  1. GraphQL 是什么

    我的理解,GraphQL 是一种以Json为载体实现:操作数据和获取结果的需求的查询语言!简言:以Json换Json.

  2. JavaScript的基本包装类型概述与基本包装类型_Number类型

    JavaScript的基本包装类型示例 为了便于操作基本类型值,javaScript 提供了 3 个特殊的引用类型:Boolean.Number和 String. 这些类型与其他引用类型相似,但同时也 ...

  3. 虚拟化技术QEMU-KVM入门

    一.QEMU.KVM.QEMU-KVM QEMU提供一系列的硬件模拟设备(CPU,网卡,磁盘等),客户机指令都需要QEMU翻译,因而性能较差.KVM是linux内核提供的虚拟化,可以用来进行vCPU的 ...

  4. SQL BETWEEN 操作符

    BETWEEN 操作符在 WHERE 子句中使用,作用是选取介于两个值之间的数据范围. BETWEEN 操作符 操作符 BETWEEN ... AND 会选取介于两个值之间的数据范围.这些值可以是数值 ...

  5. nginx-redirect配置

    转载一篇非常好的文章,大赞!!!!! http://blog.csdn.net/u010391029/article/details/50395680 nginx的配置文件解读 http://blog ...

  6. [Python] Python 100例

    题目1:有四个数字:1.2.3.4,能组成多少个互不相同且无重复数字的三位数?各是多少? 程序分析:可填在百位.十位.个位的数字都是1.2.3.4.组成所有的排列后再去 掉不满足条件的排列. #程序源 ...

  7. 【转】宽带路由器应用(三)—ARP欺骗防护功能的使用

    在局域网中,通信前必须通过ARP协议来完成IP地址转换为第二层物理地址(即MAC地址).ARP协议对网络安全具有重要的意义.通过伪造IP地址和MAC地址实现ARP欺骗,对网络的正常传输和安全都是一个很 ...

  8. ②---Java开发工具Eclipse安装配置

    Java开发工具Eclipse安装及配置 以下将为大家介绍Java开发工具Eclipse安装及配置. 一.下载Eclipse安装文件 正所谓工欲善其事必先利其器,我们在开发java语言过程中同样需要依 ...

  9. [ASP.NET]ScriptManager控件使用 转载

    目录 概述 局部刷新 错误处理 类型系统扩展 注册定制脚本 注册 Web 服务 在客户端脚本中使用认证和个性化服务 ScriptManagerProxy 类 添加 ScriptManager 控件 客 ...

  10. robotframework连接mysql数据库

    1.安装databaselibrary.pymysql 通过cmd命令执行: pip install robotframework-databaselibrary pip install pymysq ...