zoukankan      html  css  js  c++  java
  • 理解隐式类型、对象初始化程序和匿名类型

    在C# 3.0中,几乎每个新特性都是为LINQ服务的。所以,本文将介绍下面几个在C# 3.0中引入的新特性:

    • 自动实现的属性
    • 隐式类型的局部变量
    • 对象和集合初始化程序
    • 隐式类型的数组
    • 匿名类型

    其实这几个特性都是比较容易理解的,对于这几个特性,编译器帮我们做了更多的事情(想想匿名方法和迭代器块),从而简化我们的代码。

    自动实现的属性

    在C# 3.0以前,当我们定义属性的时候,一般使用下面的代码

    public class Book
    {
        private int _id;
        private string _title;
    
        public int Id
        {
            get { return _id; }
            set { _id = value; }
        }
    
        public string Title
        {
            get { return _title; }
            set { _title = value; }
        }
    }

    在C# 3.0中引入了自动实现的属性,编译器会帮我们做更多的转换,所以我们可以把上面的属性实现代码转换为:

    public class Book
    {
        public int Id { get; set; }
        public string Title { get; set; }
    }

    在使用了自动实现的属性之后,代码变短了,我们也没有必要再定义私有的字段。

    其实,当查看过IL代码之后就会发现这里编译器帮我们定义了私有字段,实现了get/set方法。

    注意,当使用结构的时候,如果要使用自动属性,会有一个小问题:所有的构造函数都需要显式地调用无参数的构造函数this(),只有这样,编译器才知道所有的字段都被明确的赋值了。

    例如下面代码中,当我们删除":this()"后,编译器就会报错。

    public struct Student
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Gender { get; set; }
    
        //在结构中使用自动属性一定要显式地调用无参数的构造函数this()
        public Student(string name):this()
        {
            this.Name = name;
        }
    }

    隐式类型的局部变量

    C# 1.0和C# 2.0中的类型系统是静态、显示和安全的。

    在C# 3.0中我们可以使用var关键字定义隐式类型的变量,但是变量仍然是静态类型,只是编译器可以帮助我们推断变量的类型

    下面看一段代码,使用隐式类型的语句跟注释掉的语句的IL代码是相同的:

    static void Main(string[] args)
    {
        var str = "hello world";//string str = "hello world";
        var num = 9;// int num = 9;
    
        Console.WriteLine(str.GetType());
        Console.WriteLine(num.GetType());
        Console.Read();
    }

    通过代码的输出可以看到,每个隐式类型的变量都是静态类型(同样我们也可以通过VS单步调试来查看隐式类型变量的类型),在这里是编译器帮我们做了类型推断。

    为了验证这一点,但我们给str变量赋一个整型的值时,就会得到一个"Cannot implicitly convert type 'int' to 'string'"的错误。

    static void Main(string[] args)
    {
        var str = "hello world";//string str = "hello world";
        str = 9;
        Console.Read();
    }

    隐式类型的限制

    使用隐式类型的时候,会有一些限制,不是所有变量都能使用隐式类型:

    • 被声明的变量是一个局部变量,不能是静态字段和实例字段
    • 变量在声明时必须被初始化
      • 编译器需要根据变量的值来推断变量的类型,否则就会出现编译时错误
    • 初始化表达式不能为一个方法组,也不能为一个匿名函数(不进行强制类型转化)
      • var enter = delegate { Console.WriteLine(); };//编译错误
      • var enter = (Action)delegate { Console.WriteLine(); };//正常,因为编译器可以进行类型推断
    • 初始化表达式不是null
      • 因为null可以隐式转化为任何引用类型或可空类型,所以编译器不能进行类型推断
    • 语句中只能声明一个变量
      • "var a = 2, b = 3;"会得到编译错误

    隐式类型的优缺点

    有些时候使用隐式类型可以减少代码长度,通过不影响代码可读性,反而使我们把注意力放在了更有用的代码上;但是,有时候隐式类型会是代码可读性变差。所以要自己衡量什么时候使用隐式类型的变量。下面看一个简单的例子

    static void Main(string[] args)
    {
        //简化了代码,没有牺牲可读性
        var wordCount = new Dictionary<string, int>();
        foreach (var dict in wordCount)
        {
            Console.WriteLine("number of {0} is {1}", dict.Key, dict.Value);
        }
    
        //可读性变差,不容易从代码中直接看出变量类型
        var numA = 2147483647;
        var numB = 2147483648;
        var numC = 4294967295;
    
        Console.WriteLine(numA.GetType());
        Console.WriteLine(numB.GetType());
        Console.WriteLine(numC.GetType());
        
        Console.Read();
    }

    隐式类型的局部变量对象和集合初始化程序

    在C# 3.0中,我们有了新的对象和集合初始化的方法。

    对象初始化程序(object initializers)

    当我们有了对象初始化程序的时候,对象初始化的代码就变得更加直观、简单。看一个例子:

    public class Book
    {
        public int Id { get; set; }
        public string Title { get; set; }
    
        //如果没有默认的构造函数,使用对象初始化时就会报错
        public Book()
        {
        }
    
        public Book(string title)
        {
            this.Title = title;
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            //C# 3.0之前初始化对象的方法
            Book b1 = new Book();
            b1.Id = 1;
            b1.Title = "C# step by step";
    
            //使用对象初始化程序
            Book b2 = new Book(){Id = 2, Title = "C# in depth"};
            Book b3 = new Book { Id = 3, Title = "C# in depth" };
            Book b4 = new Book("C# in depth") { Id = 1};
    
        }
    }

    当我们查看IL代码会发现,b1、b2和b3的IL代码完全一样。

    集合初始化列表(collection initializers)

    在C# 3.0中还提出来集合初始化列表,我们可以轻松的实现集合的初始化工作。

    接着上面的例子,我们可以创建图书列表:

    //C# 3.0之前初始化集合的方法
    List<Book> bookList1 = new List<Book>();
    bookList1.Add(b1);
    bookList1.Add(b2);
    bookList1.Add(b3);
    bookList1.Add(b4);
    
    //使用集合初始化程序
    List<Book> bookList2 = new List<Book> { b1, b2, b3, b4 };
    
    List<Book> bookList3 = new List<Book>
    {
        new Book{Id = 5, Title = "Java in depth"},
        new Book{Id = 6, Title = "Python in depth"},
    };

    可以看到,当使用了集合初始化列表之后,代码变得更加简洁了。

    通过查看IL代码我们可以发现,使用集合初始化列表的时候,其实编译器帮我们调用了List的Add方法进行了元素的添加。

    隐式类型的数组

    在C# 1.0和C# 2.0中,当我们使用数组的时候,必须要指定涉及的具体数组类型。

    在C# 3.0中,可以使用"new[]"来声明并初始化一个隐式类型的数组。

    看一个简单的例子:

    string[] names = { "Wilber", "Will", "July" };
    PrintName(names);
    PrintName(new string[] { "Wilber", "Will" });
    
    //C# 3.0中使用匿名类型数组,编译器负责推断数组类型
    PrintName(new[] { "Wilber", "Will" });
    var nameArray = new[] { "Wilber", "Will", "July" };
    PrintName(nameArray);
    
    //无法进行类型推断,编译器报错
    //var array = new[] { 1, "hello world" };
    ……
    private static void PrintName(string []names)
    {
        foreach (var name in names)
        {
            Console.WriteLine(name);
        }
    }

    匿名类型

    在C# 3.0之前,当我们创建对象的时候,我们需要一个类型。在C# 3.0中出现了匿名类型的概念(类似于匿名方法,编译器帮我们完成了很多工作),我们可以直接通过new关键字为对象定义了属性,并且为这些属性赋值。

    看一个例子:

    static void Main(string[] args)
    {
        //创建匿名对象
        //通过匿名对象初始化程序为属性赋值
        var student = new { Name = "Wilber", Age = 28, Gender = "Male" };
        Console.WriteLine("{0} is {1} years old", student.Name, student.Age);
    
        //使用隐式类型的数组初始化列表
        var school = new[] {
            //使用同一个匿名类型创建实例
            new {Name = "Wilber", Gender = "Male", Age = 28 },
            new {Name = "Will", Gender = "Male", Age = 27 },
            new {Name = "Lily", Gender = "Female", Age = 26 },
        };
    
        int totalAge = 0;
        foreach (var stu in school)
        {
            totalAge += stu.Age;
        }
    
        Console.WriteLine(totalAge);
        Console.Read();
    }

    在上面的例子中,我们可以通过IL代码看看编译器帮我们做了什么?

    通过ILSpy我们可以看到,对于C#代码中的匿名类型,编译器帮我们们生成了特定的匿名类型"<>f__AnonymousType0`3"和"<>f__AnonymousType1`3"。

    注意,虽然实例化student变量的匿名类型<Name, Age, Gender>,跟实例化school数组元素的匿名类型<Name, Gender, Age>只是属性的顺序不一样,编译器会把他们当作两种不同的类型。

    总结

    本文中介绍了一些C# 3.0中提出的特性,虽然这些特性都是为了LINQ最准备,但是这些特性也可以被单独使用来简化我们的代码。

  • 相关阅读:
    HBase 高性能加入数据
    Please do not register multiple Pages in undefined.js 小程序报错的几种解决方案
    小程序跳转时传多个参数及获取
    vue项目 调用百度地图 BMap is not defined
    vue生命周期小笔记
    解决小程序背景图片在真机上不能查看的问题
    vue项目 菜单侧边栏随着右侧内容盒子的高度实时变化
    vue项目 一行js代码搞定点击图片放大缩小
    微信小程序进行地图导航使用地图功能
    小程序报错Do not have xx handler in current page的解决方法
  • 原文地址:https://www.cnblogs.com/wilber2013/p/4300296.html
Copyright © 2011-2022 走看看