Linq之隐式类型、自动属性、初始化器、匿名类
目录
写在前面
上篇文章是本系列的小插曲,也是在项目中遇到,觉得有必要总结一下,就顺手写在了博客中,也希望能帮到一些朋友。本文将继续介绍linq系列的基础知识,隐式类型,自动属性,初始化器,匿名类的相关概念,这些内容也许与linq相关也许不相关,但还是放一起总结吧,也算是复习了。部分内容通过反编译的方式一探究竟。
系列文章
隐式类型
先看看Msdn上对隐式类型的简单定义
从 Visual C# 3.0 开始,在方法范围中声明的变量可以具有隐式类型 var。 隐式类型的本地变量是强类型变量(就好像您已经声明该类型一样),但由编译器确定类型。
一个例子
1 static void Main(string[] args) 2 { 3 //隐式类型 implicitly typed 4 var i = 1; 5 //显示类型 explicitly typed 6 int j = 1; 7 Console.WriteLine(i.ToString()); 8 Console.WriteLine(j.ToString()); 9 Console.ReadKey(); 10 }
从上面msdn上的解释,隐式类型本地变量是强类型的,由编译器确定类型,那么我们编译一下该项目,然后使用ILspy反编译查看该程序集,看看是不是将i定义为了int类型,以解心中的迷惑。
你会发现,在编译之后,变量该是什么类型就是什么类型,感觉好牛逼的样子。
那到底那些情况下可以用var?在不知道返回的到底是什么类型的时候,var的用处非常大,还有就是如果返回值类型非常长,写var也是比较轻松的,但有的时候你想真真切切看到到底是什么类型的时候,这个时候就不要用var了。再看一个例子:
//该方式返回一个匿名类,这个时候不知道用什么类型来接受,就用var var custQuery = from cust in customers where cust.City == "Phoenix" select new { cust.Name, cust.Phone };
如果要遍历上面的结果,此时也必须是var,也就有了类似下面的代码
1 foreach(var item in custQuery) 2 { 3 //逻辑代码 4 }
自动属性
首先看MSDN的说法
在 C# 3.0 和更高版本中,当属性的访问器中不需要其他逻辑时,自动实现的属性可使属性声明更加简洁。 客户端代码还可通过这些属性创建对象。
这就话有点意思,“当属性的访问器中不需要其他逻辑时”,那这就话另外一个意思是,如果访问器中需要其他逻辑时,就不使用自动属性。
一个例子
如果有这样一个Person类,对年龄加了限制,年龄不得小于0,不得大于100,此时如果使用自动属性,逻辑无法添加了。如:
1 class Person 2 { 3 private int age; 4 public int Age 5 { 6 set 7 { 8 if (age > 0 && age <= 100) 9 { 10 age = value; 11 } 12 else 13 { 14 throw new Exception("年龄必须在1~100范围内"); 15 } 16 } 17 get { return age; } 18 } 19 public string Name { set; get; } 20 }
那么定义的自动属性,编译器又替咱们做了那些工作呢?反编译一下看个究竟
看来编译器为咱们做了很多工作。
初始化器
最常用到的初始化器有对象初始化器和集合初始化器,数组初始化器。还以上面的Person类为例:
c#3.0之前的做法是
1 Person p = new Person(); 2 p.Name = "wolfy"; 3 p.Age = 23;
现在又多了一种新的玩法,对象初始化器,你现在可以这样写:
在写完大括号后,只需要一个空格,所有的属性就会智能提示出来,而且编写一个就会少一个属性,避免了多个属性的混淆。
相比之前的做法
如果一个类的字段很多,由于基类object的属性及方法的干扰,上下键的去找属性,实在是太麻烦了。通过对象初始化器,你写一个少一个,也避免出现了少为属性赋值的情况了。
那么对象初始化器,编译器又为我们做了什么?同样还是反编译一下看一个究竟
代码:
1 Person p = new Person() { Name="wolfy", Age=23 };
反编译查看的结果:
集合初始化器
代码
1 List<Person> persons = new List<Person>() 2 { 3 new Person(){ Age=1, Name="张三"}, 4 new Person(){Age=2,Name="李四"} 5 };
反编译查看的结果:
的确够智能了,不能忍了,编译器替你一个一个去Add了。
数组呢?
代码
1 Person[] persons = new Person[]{ 2 new Person(){ Name="张三", Age=1}, 3 new Person(){ Name="李四" , Age=2} 4 };
看来内部并没什么变化。
匿名类
同样查看MSDN的定义
匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。 类型名由编译器生成,并且不能在源代码级使用。 每个属性的类型由编译器推断。
匿名类型通常用在查询表达式的 select 子句中,以便返回源序列中每个对象的属性子集。
匿名类型包含一个或多个公共只读属性。 包含其他种类的类成员(如方法或事件)为无效。 用来初始化属性的表达式不能为 null、匿名函数或指针类型。
有了匿名类类型可以这样写代码
1 var v = new { age = 13, name = "wolfy" }; 2 Console.WriteLine(v.age + " " + v.name);
反编译看一下IL
通过上面IL,也可以说明为什么不能赋值为null,这样编译器就无法推断它到底是什么类型了。
匿名类的属性是只读的,无法修改
如果不为匿名类中的属性指定名称,则采用初始化这些属性时所采用的属性的名称作为匿名类的属性名称,看一个例子
通常,当你使用匿名类型来初始化变量时,可以通过使用 var 将变量作为隐式键入的本地变来变量进行声明。 类型名称无法在变量声明中给出,因为只有编译器能访问匿名类型的基础名称。
一个例子
1 //可通过将隐式键入的本地变量与隐式键入的数组相结合创建匿名键入的元素的数组 2 var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }};
匿名类型是直接从对象派生的类类型,并且其无法强制转换为除对象外的任意类型。 虽然你的应用程序不能访问它,编译器还是提供了每一个匿名类型的名称。 从公共语言运行时的角度来看,匿名类型与任何其他引用类型没有什么不同。
如果程序集中的两个或多个匿名对象初始值指定了属性序列,这些属性采用相同顺序且具有相同的名称和类型,则编译器将对象视为相同类型的实例。 它们共享同一编译器生成的类型信息。
无法将字段、属性、时间或方法的返回类型声明为具有匿名类型。 同样,你不能将方法、属性、构造函数或索引器的形参声明为具有匿名类型。 要将匿名类型或包含匿名类型的集合作为参数传递给某一方法,可将参数作为类型对象进行声明。 但是,这样做会使强类型化作用无效。 如果必须存储查询结果或者必须将查询结果传递到方法边界外部,请考虑使用普通的命名结构或类而不是匿名类型。
由于匿名类型上的 Equals 和 GetHashCode 方法是根据方法属性的 Equals 和 GetHashCode 定义的,因此仅当同一匿名类型的两个实例的所有属性都相等时,这两个实例才相等。
总结
本文通过反编译的方式,学习了隐式类型,自动属性,初始化器一级匿名类型的相关概念。常见的c#语法糖,在一起总结了。
参考文章
http://msdn.microsoft.com/zh-cn/library/bb383973.aspx
http://msdn.microsoft.com/zh-cn/library/bb384054.aspx
http://msdn.microsoft.com/zh-cn/library/bb397696.aspx
-
博客地址:http://www.cnblogs.com/wolf-sun/
博客版权:如果文中有不妥或者错误的地方还望高手的你指出,以免误人子弟。如果觉得本文对你有所帮助不如【推荐】一下!如果你有更好的建议,不如留言一起讨论,共同进步! 再次感谢您耐心的读完本篇文章。