• 理解隐式类型、对象初始化程序和匿名类型


    在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最准备,但是这些特性也可以被单独使用来简化我们的代码。

  • 相关阅读:
    Android 微信分享信息
    微信朋友圈如何同时分享(图片+文字)
    Android应用加入微信分享
    讨论IT选定的技术招聘企业几点
    MSSQL发现第五到数据的第十
    STL 源代码分析 算法 stl_algo.h -- binary_search
    加快XCode编译链接速度(200%+)—XCode编译慢液
    linux复制文件命令scp
    写出高性能的多核并行编程
    SSH—Struts(三)—跑步者(Action)
  • 原文地址:https://www.cnblogs.com/wilber2013/p/4300296.html
Copyright © 2020-2023  润新知