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

用智能的编译器来防错

本章的主要内容:

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

自动实现的属性

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

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

自动实现的属性是赋值和取值方法都是共有的,当然你还可以继续使用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 = 18,
                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 = 19
                },
                new
                {
                    name = "pangjianxin",
                    age = 19
                },
                new
                {
                    name = "pangjianxin",
                    age = 19
                },
                new
                {
                    name = "pangjianxin",
                    age = 19
                },
                new
                {
                    name = "pangjianxin",
                    age = 19
                }
            };
            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等操作可以从横向的缩小要查找的范围。

原文地址:https://www.cnblogs.com/pangjianxin/p/8666386.html

时间: 2024-08-08 04:16:42

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

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

扩展方法 扩展方法有以下几个需求: 你想为一个类型添加一些 成员: 你不需要为类型的实例添加任何更多的数据: 你不能改变类型本身, 因为是别人的代码. 对于C#1和C#2中的静态方法,扩展方法是一种更优雅的解决方案. 语法 并不是任何方法都能作为扩展方法使用-- 它必须具有以下特征: 它必须在一个非嵌套的. 非泛型的静态类中( 所以必须是一 个静态方法): 它至少要有 一个参数: 第一个参数必须附加 this 关键字作为前缀: 第一个参数不能有其他任何修饰 符(比如out或ref): 第一个参数

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

Lambda表达式和表达式树 先放一张委托转换的进化图 看一看到lambda简化了委托的使用. lambda可以隐式的转换成委托或者表达式树.转换成委托的话如下面的代码: Func<string, int> getLength = s => s.Length; 转换成表达式树的话是下面的代码: Expression<Func<string, int>> getLength = s => s.Length; 委托方面的东西前面都做了详细的介绍.我们主要学习表达

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

查询表达式和LINQ to object(下) 接下来我们要研究的大部分都会涉及到透明标识符 let子句和透明标识符 let子句不过是引入了一个新的范围变量.他的值是基于其他范围变量的.let 标识符=表达式; 首先展示一个不适用let操作符来使用的按用户名称长度来排序: ... var queryWithoutLet = from user in SampleData.AllUsers orderby user.Name.Length select user; foreach (User us

写出优美代码的方式,两个习惯:一步到位VS迭代优化

最近把手头这个安卓APP的所有事务性方法都写完了,有了以下体会,新手体会,老鸟轻拍 想写成优美代码的人一般都会有这样的想法: 一定要在写每一句代码,写每一个方法,构造每一个类的时候,都要记得优化:解耦以复用,拆分方法以复用,使用循环减少冗余,限制循环次数减少无效操作,等等.. 这个想法一定没有错,但很多时候往往会是这样的情况: 当功能一复杂,比如你已经分解了几个方法,比如你已经使用了几层循环(有点过分...),比如在多线程中 你经常无法一步到位地完成那么多优化 这往往造成你写一句代码会思考很久

程序员如何像写代码一样找女朋友

在程序员的世界里,妹子是稀有动物,女神就更是凤毛麟角了,大部分程序员由于经常面对电脑,缺乏与人的沟通交流,加上软件行业的工作特殊性,因此找女朋友更是难上加难.那么,程序员如何用自己的方法去追求心仪的女生呢?有这个冲动的朋友请继续看下去. 1.需求分析 根据自己的性格特点.经济实力,合理定位:适合自己的女性范围,也就是软件工程里常谈到:需求分析.自己最想找什么样的女孩,譬如:身高在什么范 围,年龄在什么范围,学历在什么范围,相貌有什么要求,对性格有什么偏好,喜静还是偏活泼.你越能更多了解自己,知道

安卓开发复习笔记——Fragment+FragmentTabHost组件(实现新浪微博底部菜单)

记得之前写过2篇关于底部菜单的实现,由于使用的是过时的TabHost类,虽然一样可以实现我们想要的效果,但作为学习,还是需要来了解下这个新引入类FragmentTabHost 之前2篇文章的链接: 安卓开发复习笔记——TabHost组件(一)(实现底部菜单导航) 安卓开发复习笔记——TabHost组件(二)(实现底部菜单导航) 关于Fragment类在之前的安卓开发复习笔记——Fragment+ViewPager组件(高仿微信界面)也介绍过,这里就不再重复阐述了. 国际惯例,先来张效果图: 下面

安卓开发复习笔记——Fragment+ViewPager组件(高仿微信界面)

什么是ViewPager? 关于ViewPager的介绍和使用,在之前我写过一篇相关的文章<安卓开发复习笔记——ViewPager组件(仿微信引导界面)>,不清楚的朋友可以看看,这里就不再重复. 什么是Fragment? Fragment是Android3.0后新增的概念,Fragment名为碎片,不过却和Activity十分相似,具有自己的生命周期,它是用来描述一些行为或一部分用户界面在一个Activity中,我们可以合并多个Fragment在一个单独的activity中建立多个UI面板,或

[Java基础] Java线程复习笔记

先说说线程和进程,现代操作系统几乎无一例外地采用进程的概念,进程之间基本上可以认为是相互独立的,共享的资源非常少.线程可以认为是轻量级的进 程,充分地利用线程可以使得同一个进程中执行多种任务.Java是第一个在语言层面就支持线程操作的主流编程语言.和进程类似,线程也是各自独立的,有自 己的栈,自己的局部变量,自己的程序执行并行路径,但线程的独立性又没有进程那么强,它们共享内存,文件资源,以及其他进程层面的状态等.同一个进程内的 多个线程共享同样的内存空间,这也就意味着这些线程可以访问同样的变量和

oracle从入门到精通复习笔记续集之PL/SQL(轻量版)

复习内容: PL/SQL的基本语法.记录类型.流程控制.游标的使用. 异常处理机制.存储函数/存储过程.触发器. 为方便大家跟着我的笔记练习,为此提供数据库表文件给大家下载:点我下载 为了要有输出的结果,在写PL/SQL程序前都在先运行这一句:set serveroutput on结构:declare--声明变量.类型.游标begin--程序的执行部分(类似于java里的main()方法)exception--针对begin块中出现的异常,提供处理的机制--when...then...--whe