LINQ之路 2:C# 3.0的语言功能(上)

在上一篇的LINQ介绍中,我们已经看到了隐式类型变量var,扩展方法(extension method)和lambda表达式的身影。没错,他们正是LINQ技术的基石,是他们让LINQ的实现成为可能,并且简化了LINQ表达式的书写。在这一篇中,我将和大家一一探讨C#3.0在语言功能上所作的努力,包括:隐式类型局部变量、自动属性和匿名类型。

隐式类型局部变量

C#是强类型语言,意味着我们在声明变量时必须指定变量的具体类型,比如:

        static void DeclareExplicitVars()        {             int myInt = 0;             bool myBool = true;             string myString = "Hello, World!";         }

现在C# 3.0为我们提供了一个新的关键字var,你可以使用它代替正式的数据类型名(如int, bool, string)。在使用var关键字时,编译器会根据用于初始化局部变量的初始值推断出变量的数据类型。例如,上面的变量声明可以改为如下代码:

        static   void DeclareImplicitVars()        {            // 隐式类型局部变量的声明方式: var varName =   defaultValue;            var   myInt = 0;            var   myBool = true;            var   myString = "Hello, World!";        }

上面两种方式是等价的,编译器可以根据初始值推断myInt的类型为System.Int32,myBool的类型为System.Boolean,myString的类型为System.String。

除此之外,我们可以对基类库中的所有类型使用隐式类型,包括数组、泛型、自定义类型。

        public   static void   DeclareImplicitVars()        {            //   declare implicit variables            var   numbers = new int[]   { 2, 4, 6, 8 };            var   persons = new List<Person>();            var   car = new SportsCar();

            //   verify the data type using reflection            Console.WriteLine("numbers is a: {0}",   numbers.GetType().Name);            Console.WriteLine("persons is a: {0}",   persons.GetType().Name);            Console.WriteLine("car is a: {0}", car.GetType().Name);        }

输出结果如下:
       

var在foreach语句中的使用

在foreach循环语句中,我们也可以使用隐式类型。正如你希望的那样,编译器会推断出正确的数据类型:

        static   void VarInForeachLoop()        {            var   numbers = new int[]   { 2, 4, 6, 8 };            foreach   (var item in   numbers)            {                Console.WriteLine("Item value: {0}", item);            }        }

隐式类型变量的限制

需要注意的是,使用var关键字时会存在多种限制。首先,隐式类型只能应用与方法或者属性内局部变量的声明,不能使用var来定义返回值、参数的类型或类型的数据成员。

其次,使用var进行声明的局部变量必须赋初始值,并且不能以null作为初始值。其原因在于编译器必须能够根据初始值推断出该变量的实际类型。

隐式类型数据是强类型数据

隐式类型局部变量最终会产生强类型数据。因此,var关键字与脚本语言(如VBScript或Perl)的Variant数据类型是不一样的,对后者来说,一个变量可以在其生命周期中保存不同类型的值。

其实,类型推断保持了C#语言的强类型特性,并且在编译时只影响变量声明。初始化之后,编译器就已经为隐式类型变量推断出了确切的数据类型。如果把不同类型的值赋给变量会导致编译时错误:

        static   void ImplicitTypingStrongTyping()        {            // 编译器知道 s 是System.String类型            var   s = "This variable can only hold string   data!";            s = "It‘s   OK.";

            // 可以调用任何基础方法            string   upper = s.ToUpper();

            // 错误!不能把数值类型数据赋给String类型变量            s = 100;        }

隐式类型局部变量的作用

看了上面的介绍,你肯定会奇怪这个结构有什么用呢。如果只是为了简单,就不值得了,因为这样做可能会使其他阅读代码的人感到疑惑。但当我们使用LINQ时,var关键字的优势就显现出来了。它可以动态根据查询本身的格式来创建结果集,这样我们就不需要显示定义查询可能返回的类型,而且在很多时候我们并不能一眼就看出LINQ的返回类型。如下例:

        public   static void   QueryOverInts()        {            int[]   numbers = { 10, 20, 30, 40, 1, 2, 3, 5 };            var   subset = from i in   numbers where i < 10 select i;

            Console.Write("values in subset: ");            foreach   (var i in   subset)                Console.Write("{0}  ",   i);            Console.WriteLine();

            Console.WriteLine("subset is a: {0}",   subset.GetType().Name);            Console.WriteLine("subset is defined in: {0}",   subset.GetType().Namespace);        }

输出:
       

其实,我们可以认为只有在定义从LINQ查询返回的数据时才使用var关键字。

自动属性

我们知道.NET语言推荐使用类型属性来封装私有数据字段,而不是 使用GetXXX()和SetXXX()方法。因为.NET基类库总是使用类型属性而不是传统的访问和修改方法,因此使用属性可以获得与.NET平台更好的集成性。需要知道的是,在底层,C#属性会被映射到前缀get_和set_的方法中,即如果定义了Name属性,C#会自动生成get_Name()和set_Name()方法。

考虑如下的C#类型定义:

    class Person    {        private string firstName = string.Empty;        public string FirstName        {            get { return firstName; }            set { firstName = value; }        }

        private string lastName = string.Empty;        public string LastName        {            get { return lastName; }            set { lastName = value; }        }

        private int level = 0;        public int Level        {            get { return level; }            set { level = value; }        }    }

虽然定义属性不难,但如果属性只是赋值和返回值,对次定义字段和属性也很麻烦,特别是在类属性很多的情况下。为了简化这种简单的数据字段封装的过程,C# 3.0提供了自动属性语法。现在,上面的Person可以定义成如下形式:

    class Person    {        public string FirstName { get; set; }        public string LastName { get; set; }        public int Level { get; set; }    }

定义自动属性时,我们只需要指定访问修饰符、数据类型、属性名称和空的get/set作用域。在编译时,会使用自动生成的私有支持字段以及get/set逻辑的正确实现。

需要注意的是,定义自动属性时,必须同时提供get和set关键字,因此不能定义只读或者只写的自动属性。

匿名类型

作为一个面向对象的程序员,我们知道如何定义类来表达一个给定的编程实体。当我需要一个在项目之间重用的类型时,我们通常创建一个C#类,为该类提供必需的一系列属性、方法和事件等。但有时候,我们可能需要定义类来封装一些相关数据,而不需要任何相关联的方法、事件。并且,给类不需要在项目间重用。尽管如此,我们还是得定义一个“临时”类,虽然工作不是很复杂,但是如果需要定义类来封装很多数据成员的话,那么将消耗你大量的劳动时间。我想,大家都不会希望把编程变成一项机械运动吧。

C# 3.0提供的匿名类型正是为了上述任务而生,匿名类型是匿名方法的自然延伸,可以帮助我们轻松的完成上面的工作。

定义一个匿名类型时,使用新的关键字var和之前介绍的对象初始化语法,如下示例:

        static void TestAnonymousType()        {            // 构造一个匿名对象表示一个雇员            var worker = new { FirstName = "Vincent", LastName = "Ke", Level = 2 };

            // 显示并输出            Console.WriteLine("Name: {0}, Level: {1}", worker.FirstName + "" + worker.LastName, worker.Level);        }

使用上述代码来构建匿名对象时,C#编译器会在编译时自动生成名称唯一的类。因为这个类的名字在C#中是不可见的,所以必需使用var关键字来使用隐式类型化。另外,我们需要通过对象初始化语法来定义一系列属性来封装各个数据。

匿名类型的内部表示

所有的匿名类型都自动继承自System.Object,我们可以在隐式类型话的worker上面调用ToString()、GetHashCode()、Equals()、GetType()等方法。

我们可以定义如下方法来查看匿名类型的信息:

        static void ReflectAnonymousType(object obj)        {            Console.WriteLine("Type Name: {0}", obj.GetType().Name);            Console.WriteLine("Base Class: {0}", obj.GetType().BaseType);            Console.WriteLine("obj.ToString() = {0}", obj.ToString());            Console.WriteLine("obj.GetHashCode() = {0}", obj.GetHashCode());        }

        static void TestAnonymousType()        {            // 构造一个匿名对象表示一个雇员            var worker = new { FirstName = "Vincent", LastName = "Ke", Level = 2 };            ReflectAnonymousType(worker);        }

结果如下:

上例中,worker的类型是<>f__AnonymousType0`3(各版本之中可能会有所不同),匿名类型的类型名完全由编译器决定。更重要的是,使用对象初始化语法定义的每一个名称/值对被映射为同名的只读属性以及被封装的私有数据成员。

方法ToString()和GetHashCode()的实现

从上面可以看到,匿名类型直接了System.Object,并且重写了Equals()、GetHashCode()、ToString()方法。其中ToString()根据每一个名称/值对,生成并返回一个字符串,见上图。

GetHashCode()的实现使用每一个匿名类型的成员变量来计算散列值。当且仅当两个匿名类型有相同的属性别且被赋予相同的值时,就会产生相同的散列值,这样,匿名类型就可以很好的和Hashtable容器一起工作。

匿名类型的相等语义

编译器重写的Equals()在判断对象时使用了基于值的语义,但编译器并没有重载(==和!=)相等运算符,因此使用==比较两个匿名对象时,是基于引用的语义!”==”比较引用是对所有类的默认行为。如下例:

        static void AnonymousTypeEqualityTest()        {            // 构建两个匿名类型,拥有相同的名称/值对            var worker1 = new { FirstName = "Harry", SecondName = "Folwer", Level = 2 };            var worker2 = new { FirstName = "Harry", SecondName = "Folwer", Level = 2 };

            // Equals测试            if (worker1.Equals(worker2))                Console.WriteLine("worker1 equals worker2");            else                Console.WriteLine("worker1 not equals worker2");

            // ==测试            if (worker1 == worker2)                Console.WriteLine("worker1 == worker2");            else                Console.WriteLine("worker1 != worker2");

            // Type Name测试            if (worker1.GetType().Name == worker2.GetType().Name)                Console.WriteLine("we are both the same type");            else                Console.WriteLine("we are different types");        }

结果为:



 

时间: 2024-10-18 09:46:59

LINQ之路 2:C# 3.0的语言功能(上)的相关文章

LINQ之路 3:C# 3.0的语言功能(下)

在LINQ介绍一篇中,我们已经看到了隐式类型变量var,扩展方法(Extension method)和Lambda表达式的身影.没错,他们正是LINQ技术的基石,是他们让LINQ的实现成为可能,并且简化了LINQ表达式的书写.在这一篇中,我将和大家一一探讨C#3.0在语言功能上所作的努力,包括:扩展方法.Lambda表达式和对象初始化器. 扩展方法 下一个与LINQ密切相关的C# 3.0语言功能是扩展方法(Extension method).在这之前,一旦一个类型被编译进.NET程序集后,我们便

LINQ之路 4:LINQ方法语法

书写LINQ查询时又两种语法可供选择:方法语法(Fluent Syntax)和查询语法(Query Expression). LINQ方法语法是非常灵活和重要的,我们在这里将描述使用链接查询运算符的方式来创建复杂的查询,方法语法的本质是通过扩展方法和Lambda表达式来创建查询.C# 3.0对于LINQ表达式还引入了声明式的查询语法,通过查询语法写出的查询比较类似于SQL查询.本篇会对LINQ方法语法进行详细的介绍. 当然,.NET公共语言运行库(CLR)并不具有查询语法的概念.所以,编译器会在

LINQ之路 7:子查询、创建策略和数据转换

在前面的系列中,我们已经讨论了LINQ简单查询的大部分特性,了解了LINQ的支持计术和语法形式.至此,我们应该可以创建出大部分相对简单的LINQ查询.在本篇中,除了对前面的知识做个简单的总结,还会介绍几种创建更复杂查询的方式,让我们在面对更复杂的场景时也能轻松面对,包括:子查询.创建策略和数据转换. 子查询 在创建一个复杂的查询时,通常我们需要用到子查询.相信大家都记得SQL查询里的子查询,在创建LINQ查询时也是如此.在LINQ中,对于方法语法,一个子查询包含在另外一个查询的lambda表达式

3Python全栈之路系列之基于socket实现文件上传

Python全栈之路系列之基于socket实现文件上传 发布时间:2017年3月16日 00:04 浏览(106) 评论(0) 分类:Python 前言 此处没有前言 粘包 在实现发送文件功能之前我们先来理解下粘包的问题,下面有两张图,我觉得很清晰的就可以理解到了. 正常情况下发送文件 第一步: 客户端把获取到的文件总大小(size=65426)先放到缓冲区,然后发送给服务端 第二步: 此时客户端接收到的文件总大小就是65426 粘包的问题下发送文件 第一步: 客户端把获取到的文件总大小(siz

[Servlet3.0]Serlvet文件上传

Servlet 3.0的另一个新特性就是提供了处理文件上传的功能,使用Servlet 3.0的内容实现文件上传需要以下几个内容: 在处理文件上传的Servlet上增加@MultipartConfig注解,表示当前Servlet符合MIME类型的multipart/form-data. Optional Element Summary int fileSizeThreshold java.lang.String location long maxFileSize long maxRequestSi

Servlet3.0学习总结——基于Servlet3.0的文件上传

Servlet3.0学习总结(三)——基于Servlet3.0的文件上传 在Servlet2.5中,我们要实现文件上传功能时,一般都需要借助第三方开源组件,例如Apache的commons-fileupload组件,在Servlet3.0中提供了对文件上传的原生支持,我们不需要借助任何第三方上传组件,直接使用Servlet3.0提供的API就能够实现文件上传功能了. 一.使用Servlet3.0提供的API实现文件上传 1.1.编写上传页面 <%@ page language="java&

firefox os 2.0版模拟器上QQ初体验

对于firefox os 爱好者而言,firefox os 手机迟迟没在中国上市会感到些许遗憾,但我们要相信firefox os 登陆中国是迟早的事,腾讯QQ已经登陆firefox os 应用市场,今天我们就从模拟器上感受一番腾讯QQ,想体验的爱好者们可以参考安装模拟器抢先体验一番! 下载安装QQ 安装. 安装完成 登陆界面; 聊天界面 喜欢的盆友快去试试吧!!!   编辑(5狐网)firefox os 2.0版模拟器上QQ初体验,布布扣,bubuko.com

solr5.5.0在CenOS上的安装与配置

solr5.5.0在CenOS上的安装与配置 1. Solr简介 Solr是一个基于Lucene的Java搜索引擎服务器.Solr 提供了层面搜索.命中醒目显示并且支持多种输出格式(包括 XML/XSLT 和 JSON 格式).它易于安装和配置,而且附带了一个基于 HTTP 的管理界面.Solr已经在众多大型的网站中使用,较为成熟和稳定.Solr 包装并扩展了 Lucene,所以Solr的基本上沿用了Lucene的相关术语.更重要的是,Solr 创建的索引与 Lucene 搜索引擎库完全兼容.通

Servlet3.0学习总结(三)——基于Servlet3.0的文件上传

Servlet3.0学习总结(三)——基于Servlet3.0的文件上传 在Servlet2.5中,我们要实现文件上传功能时,一般都需要借助第三方开源组件,例如Apache的commons-fileupload组件,在Servlet3.0中提供了对文件上传的原生支持,我们不需要借助任何第三方上传组件,直接使用Servlet3.0提供的API就能够实现文件上传功能了. 一.使用Servlet3.0提供的API实现文件上传 1.1.编写上传页面 1 <%@ page language="jav