.NET中那些所谓的新语法之一:自动属性、隐式类型、命名参数与自动初始化器

开篇:在平常的.NET开发学习中,咱们每每会接触到一些较新的语法,它们相对之前的老语法相比,作了不少的改进,简化了不少繁杂的代码格式,也大大减小了咱们这些菜鸟码农的代码量。可是,在开心欢乐之余,咱们也不由地对编译器内部到底为咱们作了哪些事儿而感到好奇?因而,咱们就借助反编译神器,去看看编译器到底作了啥事!其实本篇中不少都不算新语法,对于不少人来讲可能都是接触了好久了,这里主要是针对.NET的老版原本说,是一个“相对”的新语法。html

/* 新语法索引 */

1.自动属性 Auto-Implemented Properties
2.隐式类型 var
3.参数默认值 和 命名参数
4.对象初始化器 与 集合初始化器 { }

1、自动属性探秘:[ C# 3.0/.Net 3.x 新增特性 ]

1.1 之前的作法:先写私有变量,再写公有属性

    public class Student
    {
        private Int32 _id;

        public Int32 Id
        {
            get { return _id; }
            set { _id = value; }
        }
        private string _name;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        private Int16 _age;

        public Int16 Age
        {
            get { return _age; }
            set { _age = value; }
        }
    }

1.2 如今的作法:声明空属性

    public class Person
    {
        public Int32 ID { get; set; }
        public string Name { get; set; }
        public Int16 Age { get; set; }
    }

PS:如今看来,是否是少些不少代码?直接声明一个空属性,编译器就能够帮咱们完成之前的私有成员字段和get、set方法,因而,咱们能够经过Reflector反编译工具去看看,究竟是怎么完成这个操做的。小程序

1.3 伟大的“乡村基”—CSC(C Sharp Compiler):C#编译器

PS:这里为什么会提到乡村基,一是由于乡村基的简称就是CSC,二是由于本人比较喜欢吃乡村基的中式快餐,因此,么么嗒!(感受像是给乡村基打广告似的,不过我仍是蛮喜欢乡村基的,固然是抛开价格来讲)函数

  (1)首先咱们来编译一下上面这个小程序,而后将编译后的exe/dll拖到反编译神器Reflector(或者ILSpy也是赞赞哒)工具

  (2)找到Person类,能够看到编译后的结果:CSC帮咱们自动生成了与共有属性对应的私有字段学习

  咱们能够从图中看出,自动生成的字段与之前的字段有一些区别:优化

  ①在每一个字段上方都加上了一个[CompilerGenerated]的特性(Attribute),顾名思义:表示其是由编译器生成的;spa

  ②每一个字段的变量名称是有必定格式的,好比<Age>k__BackingField,那么能够看出格式为:<属性名>k_BackingField;(BackingField顾名思义就是背后的字段)3d

  (3)看完了自动生成的字段,再来看看属性是怎么定义的:调试

  ①和自动生成的字段同样,属性也加上了[CompilerGenerated]的特性以示区别code

  ②众所周知,属性就是一个get和一个set的两个方法的封装,那么咱们以前写的空get/set方法又是怎么被编译生成的呢

  因而,咱们能够看到,在get和set方法中,也加上了[CompilerGenerated]的特性以示区别,另外还帮咱们自动对应了自动生成的私有字段,这就跟咱们本身手动写的私有字段+共有属性的方法保持了一致。因此,自动属性是一个实用的语法糖,帮咱们作了两件事:自动生成私有字段,自动在get/set方法中匹配私有字段。

2、隐式类型—关键字:var [ C# 3.0/.Net 3.x 新增特性 ]

2.1 犹抱琵琶半遮面—你能猜出我是谁?

   之前,咱们在定义每一个变量时都须要明确指出它是哪一个类型。可是,当有了var以后,一切变得那么和谐,咱们能够用一个var定义全部的类型。

    var age = 100;
    age += 150;

    var name = "";
    name = "edisonchou";

    Console.WriteLine("age={0}", age);
    Console.WriteLine("name={0}", name);

  点击调试,发现编译器自动帮咱们匹配上了正确的类型并成功显示出来:

  那么,咱们又好奇地想知道编译器究竟是否识别出来了指定的类型,因而咱们再次经过反编译工具来一看究竟:

  能够看出,咱们可爱的CSC正确地帮咱们推断出了正确的类型,不禁得想给它点32个赞了!

  可是,变量类型不可更改,由于声明的时候已经肯定类型了,例如咱们在刚刚的代码中给变量赋予不一样于定义时的类型,会出现错误。

2.2 好刀用在刀刃上—隐式类型应用场景

  在数据型业务开发中,咱们会对一个数据集合进行LINQ查询,而这个LINQ查询的结果多是ObjectQuery<>或IQueryable<>类型的对象。所以,在目标具体类型不明确的状况下,咱们能够用var关键来声明:

List<UserInfo> userList = roleService.LoadRoles(param);
var data = from u in userList
           where u.IsDel == 0
           select u;

2.3 但“爱”就是克制—隐式类型使用限制

  (1)被声明的变量是一个局部变量,而不是静态或实例字段;

  (2)变量必须在声明的同时被初始化,编译器要根据初始化值推断类型;

  (3)初始化不是一个匿名函数,同时初始化表达式也不能是 null

  (4)语句中只声明一次变量,声明后不能更改类型;(详见上面的例子)

  (5)赋值的数据类型必须是能够在编译时肯定的类型

3、参数默认值和命名参数:[ C# 4.0/.NET 4.0 新增特性 ]

3.1 带默认值的方法

        static void Main(string[] args)
        {
            // 01.带默认值参数函数
            FuncWithDefaultPara();
            // 02.省略一个默认参数调用
            FuncWithDefaultPara(10086);

            Console.ReadKey();
        }

        static void FuncWithDefaultPara(int id = 10010, bool gender = true)
        {
            Console.WriteLine("Id:{0},Gender:{1}", id,
                gender ? "Man" : "Woman");
        }

  点击调试,显示结果以下:

3.2 编译后的方法调用

  一样,为了一探带参数默认值方法调用的细节,咱们仍是借助反编译神器查看其中的玄妙:

  (1)首先,咱们来看看带默认值参数的方法被编译后是怎么的:

  能够看到,在.NET Framework中大量采用了基于Attribute的开发方式,这里为参数添加了表示默认值的特性DefaultParameterValue

  (2)其次,再来看看Main函数中的调用过程是怎么被编译的:

  

  能够看出,编译器帮咱们在方法调用的括号中帮咱们填充了默认值。这里,咱们不由好奇,若是在调用中,不指定ID(即便用ID默认值10010)而仅仅指定Gender为false是否能够编译经过?咱们来试一下:

        static void Main(string[] args)
        {
            // 01.带默认值参数函数
            FuncWithDefaultPara();
            // 02.省略一个默认参数调用
            FuncWithDefaultPara(10086);
            // 错误调用:
            FuncWithDefaultPara(false);

            Console.ReadKey();
        }

  这时,出现了如下错误:

  因而,咱们知道,CSC也尚未那么智能,没法理解咱们高深的“意图”。那么,有木有一种方法来解决这种需求呢,因而命名参数横空出世了。

3.3 使用命名参数

  在新语法中为方法调用引入了命名参数,格式为 参数名:参数值

        static void Main(string[] args)
        {
            // 01.带默认值参数函数
            FuncWithDefaultPara();
            // 02.省略一个默认参数调用
            FuncWithDefaultPara(10086);
            // 错误调用:
            //FuncWithDefaultPara(false);
            // 03.使用命名参数调用
            FuncWithDefaultPara(gender: false);

            Console.ReadKey();
        }    

  经过调试,能够获得以下结果:

  经过前面的分析,咱们能够分析出,使用命名参数被编译以后仍是会生成指定参数值的调用:

4、自动初始化器:[ C# 3.0/.NET 3.x 新增特性 ]

4.1 属性初始化器

  (1)在开发中,咱们常常会这些为new出来的对象设置属性:

        static void InitialPropertyFunc()
        {
            Person p = new Person() { Name = "小强", Age = 18 };
            Console.WriteLine("Name:{0}-Age:{1}", p.Name, p.Age);
        }    

  (2)可是,通过编译后咱们发现原来仍是之前的方式:先new出来,而后一个属性一个属性地赋值。这里,编译器首先生成了一个临时对象g_initLocal0,而后为其属性赋值,最后将g_initLocal0这个对象的地址传给要使用的对象p。

4.2 集合初始化器

  (1)在开发中,咱们常常在一个集合的实例化中,就为其初始化:

        static void InitialCollectionFunc()
        {
            List<Person> personList = new List<Person>()
            {
                new Person(){Name="小强",Age=10},
                new Person(){Name="小王",Age=15},
                new Person(){Name="小李",Age=18}
            };

            foreach(Person person in personList)
            {
                Console.WriteLine("Name:{0}-Age:{1}", 
                    person.Name, person.Age);
            }
        }

  (2)通过上面的集合初始化器咱们了解到编译器仍是会编译成原来的方式,即先new出来,为其分配了内存空间以后,再一个一个地为其属性赋值。那么,在集合的初始化中咱们也能够大胆地猜想,编译器也是作了以上的优化工做:即先将每一个对象new出来,而后一个一个地为属性赋值,最后调用集合的Add方法将其添加到集合中。因而,咱们仍是反编译一下,一探究竟:

附件下载

  (1)反编译神器之Reflector: http://pan.baidu.com/s/1evCJG

  (2)所谓的新语法Demo:http://pan.baidu.com/s/1ntwqdAT

 

相关文章
相关标签/搜索