首先让我们看一个例子, 假设我们并没有Person类, 并且我们关心的属性只有Name和Age. 下面的代码演示了我们如何在没有声明类型的情况下来构建一个对象的:

1: var tom = new { Name = Tom, Age = 4 };
   2: var holly = new { Name = Holly, Age = 31 };
   3: var jon = new { Name = Jon, Age = 31 };
   4: Console.WriteLine({0} is {1} years old, jon.Name, jon.Age);

可以看到, 初始化一个匿名类与我们之前提到的对象初始化器非常相似——区别仅仅是在new和开始的大括号之间的类型名称没有了. 我们正在使用隐式的局部变量, 因为这是我们的唯一选择——我们没有任何的类型名可以用于声明该变量. 从上面的最后一行代码你可以看到, 类型用于Name和Age属性, 他们可以被读取并且其值就是在隐式类型初始化器中被赋予的值, 因此, 该输出将会是Jon is 31 years old. 属性将会有与初始化器内的表达式一样的类型——Name是string类型, Age是int类型. 和对象初始化器一样的是, 方法和构造器也可以被用于隐式对象初始化器中, 用于做任何你想做的是事情.

现在你知道为什么隐式类型数组为什么这么重要了. 假设我们想创建包含整个家庭成员的数组, 然后迭代算出总的年龄. 以下的代码将会完成这个工作——同时也演示了其他一些有趣的关于匿名类型的特性.

1: var family = new[]
   2: {
   3:     new { Name = Holly, Age = 31 },
   4:     new { Name = Jon, Age = 31 },
   5:     new { Name = Tom, Age = 4 },
   6:     new { Name = Robin, Age = 1 },
   7:     new { Name = William, Age = 1 }
   8: };
   9: int totalAge = 0;
  10: foreach (var person in family)
  11: {
  12:     totalAge += person.Age;
  13: }
  14: Console.WriteLine(Total age: {0}, totalAge);

从前面了解过的隐式类型数组来看, 我们可以推论出一个重要的事情: 所有的家庭成员类型都是一样的. 因为如果他们使用匿名类型初始化器创建的都是一个新的类型, 那么明显数组类型不可能被正确的声明. 在一个给定的Assembly中, 如果两个匿名类型拥有同样数量的属性, 并且他们有相同的名字和类型, 以及相同的出现顺序, 那么编译器将会把他们当成同一个类型. 换句话说, 如果我们把其中一个的Name和Age属性调换一下, 编译器将会生成一个新的匿名类型——同样的,如果我们在新行中引入一个而外的属性, 或者使用long类型代替Age的int类型, 这些统统会导致编译器生成一个新的匿名类型.

实现细节:如果你曾经看过匿名类的IL代码, 你应该知道即使两个匿名对象初始化器拥有同样的属性名和出现出现, 但却使用不同的类型, 那么编译器会生成两个不同的类型, 而它们实际上是通过一个单独的泛型类来生成的. 该泛型类是参数化的, 但其封闭的构造类型将会因为给与不同初始化器的类型参数不同而不同.

我们可以使用foreach表达式应用于上述的数组, 就像我们用于集合中的一样. 类型是由编译器推断的, person的类型也就是数组的类型. 再次提一下, 我们可以将同样的变量用于不同的实例中, 因为它们全部都拥有相同的类型.

上述的代码同样验证了Age属性是强类型的int, 否则我们试图计算age总和将会导致错误. 编译器了解匿名类型, VS2008更是通过tooltip的方式提供更多的信息. 现在我们已经了解了足够的关于匿名类型信息, 接下来让我们看看编译器实际上为我们做的工作.

匿名类型的成员

匿名类型是由编译器创建并其包含在编译后的Assembly当中, 其方式与匿名方法和iterator block的创建方式是一致的. CRL把它们都当成普通的类型, 实际上他们就是普通的类型——如果你将其从匿名类型更改成为一个普通类型, 并且手工编写所有行为的代码, 我们不会看到有任何的改变. 匿名类型包含以下的成员:

一个负责所有初始化值的构造器, 其参数将会是与匿名对象初始化器当中出现的顺序和类型一致, 同样名称也是一样的.
公共只读的属性
私有的只读字段, 用于支持属性
重载了Equals, GetHashCode和ToString
这就是全部了, 没有实现任何借口, 没有克隆和序列化能力——仅仅是一个构造器, 一些属性和几个来自于object的平常的方法.

构造器和属性完成都是一些显而易见的事情. 两个来自于相同匿名类型的实施是否相等, 通过轮流比较所有属性类型的Equals方法来决定.hash代码生成也是一类似的, 通过调用每个属性的GetHashCode然后再合并结果. 将多个hash code合并成为一个组合, 其方法并没有被指定, 因此你不能在你的代码中依靠它——你唯一有信心的就是, 两个相等的对象实例必然返回相同的hash值, 而两个不等的实例可能会返回不等的hash值. 当然所有这些也只有当属性类型实现的GetHashCode和Equals也遵循常规规则的时候才有效.

注意因为属性是只读的, 因此匿名类是不可变的, 因此属性的类型也是不可变的. 由于这个不可变性, 你不用担心属性在赋值后会被改变, 跨线程自然也没有问题.

发散性初始化器(projection initializers)

目前我们看到的匿名对象初始化器中我们一直使用简单的name / value对——Name=Jon, Age=31, 虽然有时候我们确实是这么做, 但是更多时候在真实的编程中, 我们可能会从一个已有的对象中来拷贝属性值. 有时我们会通过一些方式来读取值, 不过更经常的是拷贝就足够了.

没有LINQ, 要给出令人信服的例子有点困难, 不过让我们回到我们的Person类, 假设我们有一个很好的理由我们想将一个Person实例的集合转换到另外一个类似的集合, 其元素包含有一个name和一个flag指示该person是否是一个成年人. 给定一个合适的变量, 我们可以使用下面的代码:

1: new { Name = person.Name, IsAdult = (person.Age >= 18) }

该代码当然能够工作, 如果仅仅是设置一个单一的name属性使用此方法当然还不算笨拙——但如果你要拷贝很多的属性, 你应该考虑其他的尝试. C# 3提供了一个快捷方式: 如果你没有指定属性名称, 而仅仅使用表达式去对值进行评估, 那么编译器将会用表达式的最后一部分作为属性名. 这被称为发散性初始化器. 这意味着我们可以将上面的代码改写为:

1: new { person.Name, IsAdult = (person.Age >= 18) }

将一个匿名对象初始化器变为一个发散性初始化器是很常见的——通常当你想从一个对象拷贝一些属性到另外一个对象的时候发生, 而经常作为join操作的一部分. 一下显示了完整的代码, 使用了List.ConvertAll方法和匿名代理:

1: List<Person> family = new List<Person>
   2: {
   3:     new Person {Name=Holly, Age=31},
   4:     new Person {Name=Jon, Age=31},
   5:     new Person {Name=Tom, Age=4},
   6:     new Person {Name=Robin, Age=1},
   7:     new Person {Name=William, Age=1}
   8: };
   9: var converted = family.ConvertAll(delegate(Person person)
  10: {
  11:     return new { person.Name, IsAdult = (person.Age >= 18) }; }
  12: );
  13: foreach (var person in converted)
  14: {
  15:     Console.WriteLine({0} is an adult? {1},
  16:     person.Name, person.IsAdult);
  17: }

上述的代码初始了使用了发散性初始化器, 我们还展示了匿名方法和代理类型推论的价值, 没有它们, 我们无法保持转换后的类型依然是强类型的. 因为我们无法指定TOutput参数在转换器当中的类型. 在完成转换之后, 我们可以使用一个迭代来遍历整个List并访问Name和IsAdult属性, 不过我们已经在使用另外一个类型了.

[转]c#匿名类的更多相关文章

  1. 浅谈Java的匿名类

    在实际的项目中看到一个很奇怪的现象,Java可以直接new一个接口,然后在new里面粗暴的加入实现代码.就像下面这样.那么问题来了,new出来的对象没有实际的类作为载体,这不是很奇怪吗? 思考以下代码 ...

  2. Second Day: 关于Button监听事件的三种方法(匿名类、外部类、继承接口)

    第一种:通过匿名类实现对Button事件的监听 首先在XML文件中拖入一个Button按钮,并设好ID,其次在主文件.java中进行控件初始化(Private声明),随后通过SetOnClickLis ...

  3. 学习笔记:因为java匿名类学习到接口的一些小用法

    在看CometD的示例代码时发现了许多有意思的代码,但说实话看别人的代码确实是件很累的事情,所以就看到这个知识点做一下记录吧.   先看一段代码: 代码1   这段代码中有一个new的操作,而且是在方 ...

  4. .NET中那些所谓的新语法之二:匿名类、匿名方法与扩展方法

    开篇:在上一篇中,我们了解了自动属性.隐式类型.自动初始化器等所谓的新语法,这一篇我们继续征程,看看匿名类.匿名方法以及常用的扩展方法.虽然,都是很常见的东西,但是未必我们都明白其中蕴含的奥妙.所以, ...

  5. HttpCookie加匿名类实现多语言

    突然想做一个多语言网站,确不知道怎么实现好,突然想到了HttpCookie,然后页面后台用匿名类实现语言的储存. string lan = Request["str_lan"]; ...

  6. java匿名类和匿名对象及this的其他用法

    /* 匿名内部类:就是内部类的简写格式. 必须前提:内部类必须继承或者实现一个类或者接口. 匿名内部类其实就是一个匿名 子类对象. 格式:new 父类对象 or 接口(){ 子类内容:(覆盖父类的, ...

  7. c#匿名类,匿名对象

    何谓匿名类,其实本质和普通定义的类一样,只不过是由系统的编译器来完成的,首先举个例子. 一般情况 //声明一个类,包含贴别多的字段 public class Person() { public str ...

  8. java:使用匿名类直接new接口

    java中的匿名类有一个倍儿神奇的用法,见下面代码示例: package contract; public interface ISay { void sayHello(); } 上面是一个简单的接口 ...

  9. java内部类以及匿名类

    内部类 一个类内部定义的类称为内部类. 内部类允许把逻辑相关的类组织在一起,并控制内部代码的可视性. 内部类与外部类的结构层次如下. 顶层类:最外层的类 外部类:内部类所在的类 内部类:类内部定义的类 ...

  10. java匿名类

    一般情况下,我们需要声明一个类去继承一个接口,然后再new这个类,赋值给接口.但有时后这个类只会被调用一次,为了调用方便,那么就可以用匿名类来简化这个步骤. interface IKey{ void ...

随机推荐

  1. 写RestApi需要注意些什么?

    PS1="\n[\e[32;1m]([\e[37;1m]\u[\e[32;1m])-([\e[37;1m]jobs:\j[\e[32;1m])-([\e[37;1m]\w[\e[32;1m] ...

  2. 容器编排之Kubernetes1.10.2安装与配置

    k8s 1.10.2 https搭建文档 1.下载k8s镜像 方式一:docker hub + github,需要创建一个docker hub账户,连接指定的github账户,docker hub会从 ...

  3. (vue.js)import "mint-ui/lib/stylecss"失败

    在vue2.0中引入了mint-ui后总是报一个css的错误 但是package.json中已经配置了css-loader style-loader ,webpack.config中也已经配置了css ...

  4. kuangbin专题十二 HDU1069 Monkey and Banana (dp)

    Monkey and Banana Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others ...

  5. centos上安装docker

    一 docker安装: 1 首先需要检查linux内核的版本,docker要求linux内核是在3.10之上的, uname -r 2 更新yum源,注意这步应该是管理员权限,如果当前不是管理员,切换 ...

  6. FPGA实战操作(1) -- SDRAM(操作说明)

    SDRAM是做嵌入式系统中,常用是的缓存数据的器件.基本概念如下(注意区分几个主要常见存储器之间的差异): SDRAM(Synchronous Dynamic Random Access Memory ...

  7. 理解Javascript_01_理解内存分配

    理解Javascript_01_理解内存分配 转载自:http://www.cnblogs.com/fool/archive/2010/10/07/1845226.html   在正式开始之前,我想先 ...

  8. Nginx 基本 安装..

    ubuntu 下 Nginx是高度自由化的Web服务器,它的功能是由许多模块来支持.如果使用了某个模块,这个模块使用了一些类似zlib或OpenSSL等的第三方库,那么就必须先安装这些软件.Ubunt ...

  9. poj2253青蛙(可到达路径的单次跳跃最短距离)

    Frogger Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 55388   Accepted: 17455 Descrip ...

  10. css雪碧图制作

    使用css背景合并工具cssSprite 工具下载链接: http://download.csdn.net/download/wx247919365/8741243 1.选择文件 2.生成雪碧图 3. ...