# [C#基础知识]专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
**引言**
经过前面专题的介绍,大家应该对C# 1和C# 2中的特性有了进一步的理解了吧,现在终于迎来我们期待已久的C# 3中特性,C# 中Lambda表达式和Linq的提出相当于彻底改变我们之前的编码风格了,刚开始接触它们,一些初学者肯定会觉得很难理解,但是我相信,只要多多研究下并且弄明白之后你肯定会爱上C# 3中的所有特性的,因为我自己就是这么过来的,在去年的这个时候,我看到Lambda表达式和Linq的时候觉得很难理解,而且觉得很奇怪的(因为之前都是用C# 3之前的特性去写代码的,虽然C# 3中的特性已经出来很久了,但是自己却写的很少,也没有怎么去研究,所以就觉得很奇怪,有一种感觉就是——怎么还可以这样写的吗?),经过这段时间对C# 语言系统的学习之后,才发现新的特性都是建立在以前特性的基础上的,只是现在编译器去帮助我们解析C# 3中提出的特性,所以对于编译器而言,用C# 3.0中的特性编写的代码和C# 2.0中编写的代码是一样的。从这个专题开始,将会为大家介绍C# 3 中的特性,本专题就介绍下C# 3中提出来的一些基础特性,这些特性也是Lambda表达式和Linq的基础。
**一、自动实现的属性**
当我们在类中定义的属性不需要一些额外的验证时,此时我们可以使用自动实现的属性使属性的定义更加简洁,对于C# 3中自动实现的属性,编译器编译时会创建一个私有的匿名的字段,该字段只能通过属性的get和set访问器进行访问。下面就看一个C#3中自动实现的属性的例子:
```
/// <summary>
/// 自定义类
/// </summary>
public class Person
{
// C# 3之前我们定义属性时,一般会像下面这样去定义
// 首先会先定义私有字段,再定义属性来对字段进行访问
//private string _name;
//public string Name
//{
// get { return _name; }
// set { _name = value; }
//}
// C# 3之后有自动实现的属性之后
// 对于不需要额外验证的属性,就可以用自动实现的属性对属性的定义进行简化
// 不再需要额外定义一个私有字段了,
// 不定义私有字段并不是此时没有了私有字段,只是编译器帮我们生成一个匿名的私有字段,不需要我们在代码中写出
// 减少我们书写的代码
// 下面就是用自动实现的属性来定义的一个属性,其效果等效于上面属性的定义,不过比之前更加简洁了
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 年龄
/// </summary>
public int Age { get; private set; }
/// <summary>
/// 自定义构造函数
/// </summary>
/// <param name="name"></param>
public Person(string name)
{
Name = name;
}
}
```
有些人会问——你怎么知道编译器会帮我们生成一个匿名的私有字段的呢?对于这点当然通过反射工具来查看经过编译器编译之后的代码了,下面是用Reflector工具查看的一张截图:
![](https://box.kancloud.cn/2016-01-23_56a2eb2c50b83.png)
如果在结构体中使用自动属性时,则所有构造函数都需要显式地调用无参构造函数this(),否则,就会出现编译时错误,因为只有显式调用无参构造函数this(),编译器才知道所有字段都被赋值了。下面是一段测试代码:
```
/// <summary>
/// 在结构体使用自动属性
/// </summary>
public struct TestPerson
{
// 自动属性
public string Name { get; set; }
// 在结构中所有构造函数都需要显示地调用无参数构造函数this(),
// 否则会出现编译错误
// 只有调用了无参数构造函数,编译器才知道所有字段都被赋值了
public TestPerson(string name)
//: this()
{
this.Name = name;
}
}
```
把this()注释掉后就会出现编译时错误,如下图:
![](https://box.kancloud.cn/2016-01-23_56a2eb2c6cd47.png)
**二、隐式类型**
用关键字var定义的变量则该变量就是为隐式类型,var 关键字告诉编译器根据变量的值类推断变量的类型。所以对于编译器而言,隐式类型同样也是显式的,同样具有一个显式的类型。
**2.1 隐式类型的局部变量**
用var 关键字来声明局部变量,下面一段演示代码:
为什么说用var定义的变量对于编译器来说还是具有显式类型呢?在Visual studio中,将鼠标放在var部分的时候就可以看到编译器为变量推断的类型。并且**变量仍然是静态类型,只是我们在代码中没有写出类型的名称而已,这个工作交给编译器根据变量的值去推断出变量的类型**,为了证明变量时静态类型,当我们把2赋给变量stringvariable时就会出现编译时错误,然而在其他动态语言中,这样的赋值是可以编译通过,所以用**var声明的变量仍然还是静态类型**,只是我们在代码中没有写出来而已。下面是证明上面两点的截图:
![](https://box.kancloud.cn/2016-01-23_56a2eb2c7e780.png)
然而使用隐式类型时有一些限制,具体限制有:
* **被声明的变量是一个局部变量,不能为字段(包括静态字段和实例字段)**
* **变量在声明时必须被初始化(因为编译器要根据变量的赋值来推断变量的类型,如果没有被初始化则编译器就无法推断出变量类型了, 然而C#是静态语言则必须在定义变量时指定变量的类型,所以此时变量不知道什么类型,就会出现编译时错误)**
* **变量的初始化不能初始化为一个方法组,也不能为一个匿名函数(前提是不进行强制类型转化的匿名函数)**
* **变量不能初始化为null(因为null可以隐式转化为任何引用类型或可空类型,所以编译器不能推断出该变量到底应该为什么类型)**
* **不能用一个正在声明的变量来初始化隐式类型 (如不能这样来声明隐式类型**
**)**
* **不能用var来声明方法中的参数类型**
同时使用隐式类型有优点也有缺点,下面的一段示例代码完全诠释了:
```
// 隐式类型的优点
// 对于复杂类型,减少打字量
// 使用隐式类型,此时就不需要再赋值的左右两侧都指定Dictionary<string,string>
var dictionary = new Dictionary<string, string>();
// 在foreach中使用隐式类型
foreach (var item in dictionary)
{
//
}
// 隐式类型的缺点
// 下面代码使用隐式类型就会使得开发人员很难知道变量的具体类型
// 所以对于什么情况下使用隐式类型,完全取决个人情况,自己感觉是否使用了隐式类型会使代码看起来更整洁和容易理解
var a = 2147483649;
var b = 928888888888;
var c = 2147483644;
Console.WriteLine( "变量a的类型为:{0}",a.GetType());
Console.WriteLine("变量b的类型为:{0}", b.GetType());
Console.WriteLine("变量c的类型为:{0}", c.GetType());
Console.Read();
```
**2.2 隐式类型的数组**
var不仅可以创建隐式类型的局部变量,还可以创建数组,下面是一段演示代码:
```
// 隐式类型数组演示
// 编译器推断为int[]类型
var intarray = new[] { 1,2,3,4};
// 编译器推断为string[] 类型
var stringarray = new[] { "hello", "learning hard" };
// 隐式类型数组出错的情况
var errorarray = new[] { "hello", 3 };
```
使用隐式类型的数组时,编译器必须推断出使用什么类型的数组,编译器首先会构造一个包含大括号里面的所有表达式(如上面代码中的 1,2,3,4和"hello","learning hard")的编译时类型的集合,在这个集合中如果所有类型都能隐式转换为卫衣的一种类型,则该类型就成为数组的类型,否则,就会出现编译时错误,如代码中隐式类型数组出错的情况, 因为"hello"转化为string,而3却转化为int,此时编译器就不能确定数组的类型到底为什么,所以就会出现编译错误,错误信息为:"**找不到隐式类型数组的最佳类型**"
**三、对象集合初始化**
**3.1 对象初始化**
有了对象初始化特性之后,我们就不需要考虑定义参数不同的构造函数来应付不同情况的初始化了,就减少了在我们实体类中定义的构造函数代码,这样使代码更加简洁,下面就具体看下C# 3中的对象初始化的使用和注意事项:
```
namespace 对象集合初始化器Demo
{
class Program
{
static void Main(string[] args)
{
#region 对象初始化演示
// 在C# 3.0之前,我们可能会使用下面方式来初始化对象
Person person1 = new Person();
person1.Name = "learning hard";
person1.Age = 25;
Person person2 = new Person("learning hard");
person2.Age = 25;
// 如果类没有无参的构造函数就会出现编译时错误
// 因为下面的语句是调用无参构造函数来对类中的字段进行初始化的
// 大括号部分就是对象初始化程序
Person person3 = new Person { Name = "learning hard", Age = 25 };
// 下面代码和上面代码是等价的,只不过上面省略了构造函数的圆括号而已
Person person4 = new Person() { Name = "learning hard", Age = 25 };
Person person5 = new Person("learning hard") { Age = 25 };
#endregion
}
}
/// <summary>
/// 自定义类
/// </summary>
public class Person
{
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 年龄
/// </summary>
public int Age { get; set; }
/// <summary>
/// 定义无参的构造函数
/// 如果类中自定义了带参数的构造函数,则编译不会生成默认的构造函数
/// 如果没有默认的构造函数,则使用对象初始化时就会报错说没有实现无参的构造函数
/// </summary>
public Person()
{
}
/// <summary>
/// 自定义构造函数
/// </summary>
/// <param name="name"></param>
public Person(string name)
{
Name = name;
}
}
}
```
上面代码中我用红色标注出使用对象初始化时需要注意的地方,大家也可以通过反射工具查看编译器是如何去解析对象初始化代码的。
**3.2 集合初始化**
C# 3中还提出了集合初始化特性来对集合初始化进行了优化,下面是一段集合初始化的使用演示代码:
```
namespace 对象集合初始化器Demo
{
class Program
{
static void Main(string[] args)
{
#region 集合初始化演示
// C# 3.0之前初始化集合使用的代码
List<string> names = new List<string>();
names.Add("learning hard1");
names.Add("learning hard2");
names.Add("learning hard3");
// 有了C# 3.0中集合初始化特性之后,就可以简化代码
// 同时下面也使用了隐式类型(使用了var关键字)
var newnames = new List<string>
{
"learning hard1","learning hard2", "learning hard3"
};
#endregion
}
}
/// <summary>
/// 自定义类
/// </summary>
public class Person
{
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 年龄
/// </summary>
public int Age { get; set; }
/// <summary>
/// 定义无参的构造函数
/// 如果类中自定义了带参数的构造函数,则编译不会生成默认的构造函数
/// 如果没有默认的构造函数,则使用对象初始化时就会报错说没有实现无参的构造函数
/// </summary>
public Person()
{
}
/// <summary>
/// 自定义构造函数
/// </summary>
/// <param name="name"></param>
public Person(string name)
{
Name = name;
}
}
}
```
集合初始化同样是编译器自动帮我们调用List的无参构造函数,然后调用Add()方法一个一个地添加进去,对于编译器而言,C# 3中使用集合初始化的代码和C#3之前写的代码是一样.然而对于开发人员来说,有了C#3的集合初始化之后,这个过程就不需要我们自己去编码,而是交给编译器帮我们做就好了, 为了证明编译器帮我们所做得事情,下面看看用反射工具来查看编译器到底是怎样帮我们来翻译集合初始化的:
```
List<string> names = new List<string>();
names.Add("learning hard1");
names.Add("learning hard2");
names.Add("learning hard3");
List<string> <>g__initLocal3 = new List<string>();
<>g__initLocal3.Add("learning hard1");
<>g__initLocal3.Add("learning hard2");
<>g__initLocal3.Add("learning hard3");
List<string> newnames = <>g__initLocal3;
```
从上面反射出来的代码可以看出,编译器确实是一位大好人,帮我们做了那么多的事情。 可能大家会有这样的疑问——对象集合初始化只不过是一个语法糖而已,就是简单地让我们少写点代码而已啊,也没有其他什么用啊?下面部分的介绍将会解决你们的疑问。
**四、匿名类型**
看到匿名类型可能大家会联想到前面介绍的匿名方法,编译器对匿名类型和匿名方法都采用同样的处理方式,该方式为编译器为匿名类型生成类型名,我们在代码中不需要显式自定义一个类型,下面就看看匿名类型的使用:
```
namespace 匿名类型Demo
{
class Program
{
static void Main(string[] args)
{
#region 匿名类型的使用Demo
// 定义匿名类型
// 因为这里不知道初始化的类型是什么,所以这里就必须使用隐式类型
// 此时隐式类型就发挥出了功不可没的作用,从而说明隐式类型的提出是为了服务于匿名类型的
// 而匿名类型的提出又是服务于Linq,一步步都是在微软团队的计划当中
Console.WriteLine("进入匿名类型使用演示:");
var person1 = new { Name = "learning hard", Age = 25 };
Console.WriteLine("{0} 年龄为: {1}", person1.Name, person1.Age);
Console.Read();
Console.WriteLine("按下Enter键进入匿名类型数组演示:");
Console.WriteLine();
#endregion
#region 匿名类型数组演示
// 定义匿名类型数组
var personcollection = new[]
{
new {Name ="Tom",Age=30},
new {Name ="Lily", Age=22},
new {Name ="Jerry",Age =32},
// 如果加入下面一句就会出现编译时错误
// 因为此时编译器就不能推断出要转换为什么类型
// new {Name ="learning hard"}
};
int totalAge = 0;
foreach (var person in personcollection)
{
// 下面代码证明Age属性是强类型的int类型
totalAge += person.Age;
}
Console.WriteLine("所有人的年龄总和为: {0}", totalAge);
Console.ReadKey();
#endregion
}
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb2c8c77b.png)
上面匿名类型的演示中使用了前面几部分介绍的所有特性——隐式类型,对象集合初始化,所以对于前面说对象集合初始化也没有其他方面的用处的疑问也可以得到答案了,如果没有对象集合初始化,要写出这样的代码(指的是 var person1 = new { Name = "learning hard", Age = 25 };)还可能吗?所以前面的隐式类型和对象集合初始化另外的一个用处就是服务于匿名类型的, 然而匿名类型又是服务于Linq的,对于Linq的好处当时是多的数不胜数了, 后面专题中会为大家介绍Linq。
上面还指出虽然我们在代码中没有为匿名类型指定类型名,而编译器会为我们生成一个类型,为了证明这点我们同样反射工具Reflector查看下编译器最后为我们生成的代码到底是怎样的?截图如下:
![](https://box.kancloud.cn/2016-01-23_56a2eb2c9d10a.png)
从上面截图中可以看出编译器确实为我们生成了一个匿名类型)**[<>f__AnonymousType0](http://www.aisto.com/roeder/dotnet/Default.aspx?Target=code://匿名类型Demo:1.0.0.0/<>f__AnonymousType0<,>)**<**<Name>j__TPar**, **<Age>j__TPar**>(其中代码相当于我们上面中定义的Person类),编译器为我们生成的这个类型是直接继承自System.Object的,并且是internal sealed(指的是该类型只在程序集内可见,并且不能被继承)。
**五、总结**
到这里,本专题的介绍也就结束了, 本专题就介绍了C# 3中几个基础的特性——自动实现的属性、隐式类型、对象集合初始化和匿名类型,这些类型的提出都是服务于后面更复杂的特性Linq的,所以只有掌握好这些基础特性之后,才能更好更快地掌握好Linq。在后面一个专题将和大家聊下C#3中的Lambda表达式。
该专题中的演示源码:[http://files.cnblogs.com/zhili/%E5%9F%BA%E7%A1%80%E7%89%B9%E6%80%A7Demo.zip](http://files.cnblogs.com/zhili/%E5%9F%BA%E7%A1%80%E7%89%B9%E6%80%A7Demo.zip)
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结