Artech

Develop every application as an art using the most suitable technologies!

常用链接

统计

积分与排名

网上邻居

我的博文系列

最新评论

[原创]深入理解C# 3.x的新特性(1): Anonymous Type

在C#3.0中,引入了一个新的Feature:Anonymous Method,允许我们已Inline的方式来定义Delegate,为Developer在Coding的时候带来了很大的便利。在C#3.0中,我们又有了另一个相似的Feature:Anonymous Type。Anonymous Type允许我们已Inline的方式的创建一个基于未知类型、具有所需数据结构的对象。

一、Anonymous Type Overview
 

在传统的编程模式中,对象依赖于一个既定的Type,我们只能在Type的基础上创建相应的Instance。比如如果我们需要创建一个Employee Instance,前提是我们已经有了一个相应的Emplyee Type的定义。比如:

public class Employee
    
{
        
private Guid _id;
        
private string _name;

        
public Guid ID
        
{
            
get return _id; }
            
set { _id = value; }
        }


        
public string Name
        
{
            
get return _name; }
            
set { _name = value; }
        }

}

有了这样一个Employee Type,我们才可以创建相应的Employee Instance。

var v = new Employee{ ID = Guid.NewGuid(), Name = "Zhang San" };

注:在上面的Code中,实际上使用到了另外两个C# 3.0的new feature: Implicitly typed local variable & Object Initializer. 

这样基于一个预先定义的Type的对象创建方式的一个最大的限制就是:对于我们需要创建的每一个对象,我们必先定于该对象对应的Type。Anonymous Type有效地解决了这个问题。我认为Anonymous Type主要是基于下面的目的而设计:

一个Type是对一个现实中实体的State(Data)和Behavior(Method)的抽象。对于一些仅仅只包含State(Data)的Type(这样对象通常作为Data Package在Application各个Layer之间、以及一个分布式环境中各个Application之间进行数据的传递),我们关心的仅仅是这个由这些数据成员组成结构Type由哪些数据成员构成,它们的名称是什么,具有怎样的数据类型。换句话说,这样的Data-based Type定义了一个Data Structure,相应地,我们可以说一个固定的Data Structure对应着一个特定的Type。而C# 3.0 的Anonymous Type就提供了这样的实现:Compiler通过我们在Source Code定义的数据成员的具体结构为我们创建相应的Type

比如我们现在需要一个在上面定义的Employee对象,实际上我们不是需要的一个Type Name叫做Employee的对象,而是需要一个具有如下特征的对象:该对象具有两个数据成员: ID & Name他们的数据类型分别为GUID和string。在Source Code中,我们通过以下各结构指定这种特征:

var v = new{ID = Guid.NewGuid(), Name= "Zhang San" };

我们仔细分析上面这段代码,实际上它包含两部分的信息的:

  • 为Compiler Type的创建定义一个数据结构。{}中的内容指明了:包含两个数据成员,第一个是名称为ID,第二个为Name(成员的顺序也是一个决定因素,也就是说{ID = Name= "Zhang San" ,Guid.NewGuid()}和{ID = Guid.NewGuid(), Name= "Zhang San" }对于的Anonymous Type将是不同的。我不太清楚这样的设计到底处于一个什么样的目的);和Implicitly typed local variable一样,成员的类型由指定的数据或者表达式计算结果的数据类型决定。
  •  为在运行时对象的创建提供数据,就像Constructor的参数一样。 

二、CLR 眼中的Anonymous Type

我们说Anonymous Type仅仅是C# 3.0的新的特性,而没有说Anonymous Type是.NET Framework 3.5的新特性。这是因为Anonymous Type仅仅是.NET Programming Language和相应的Compiler的新引入的特征。而对于.NET Framework 3.5来说,它看不到这和原来有什么不同,换句话说,对于Anonymous Type和一般的Named Type,对于CLR来说他们之间没有什么本质的区别

对于下面这样的一段简单的代码:

var v = new{ID = Guid.NewGuid(), Name= "Zhang San" };

通过编译,Compiler将会创建一个名为<>f__AnonymousType0<<>j__AnonymousTypeTypeParameter1, <>j__AnonymousTypeTypeParameter2>的Class。该Class的结构如下:

public sealed class <>f__AnonymousType0<<>j__AnonymousTypeTypeParameter1, <>j__AnonymousTypeTypeParameter2>

{

// Properties

public <>j__AnonymousTypeTypeParameter1IDgetset; }

public j__AnonymousTypeTypeParameter2 Namegetset; }

// Fields

private j__AnonymousTypeTypeParameter1 <>i__AnonymousTypeField3;

private j__AnonymousTypeTypeParameter2 <>i__AnonymousTypeField4;

}

<>j__AnonymousTypeTypeParameter1<>j__AnonymousTypeTypeParameter2这两个Generic Type代表我在 {} 中制定ID和Name的类型。通过这个结构,我们发现其定义和一般的Generic Type并没有什么区别。

为了进一步了解生成什么样的Anonymous Type,我们使用IL DASM在IL级别看看生成的Anonymous Type的大体结构:

为了做一个对比,下面是我们最开始定义的Named Employee Type在IL DASM中的结构:

如果想更清楚了解Anonymous Type的本质,建议读者亲自使用IL DASM看看的每个成员具体的IL。

三、Anonymous Type is Bound to Assembly

在上面一个部分中我们说了对于CLR来说,Anonymous Type和一般的Named Type并没有本质的区别。但是话不能太绝对,他们之间还是有一点小小的差异。到底是什么样差异,我在这里先卖一个关子。在具体介绍这个差异的时候,我们先来看看一个Sample:

在这个Sample中,我定义了两个Project:

  • Console Application:Artech.NewFeatureInCSharp.ConsoleApp
  • Class Libray:Artech.NewFeatureInCSharp.Library



Artech.NewFeatureInCSharp.Library中定一个Employee Type:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Artech.NewFeatureInCSharp.Library
{
    
public class Employee
    
{
        
private Guid _id;
        
private string _name;

        
public Guid ID
        
{
            
get return _id; }
            
set { _id = value; }
        }


        
public string Name
        
{
            
get return _name; }
            
set { _name = value; }
        }

    }

}

和一个Static的Utility Class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Artech.NewFeatureInCSharp.Library
{
    
public static  class Utility
    
{
        
public static object Anonymous_GetEmployee(Guid id, string name)
        
{
            
return new { ID = id, Name = name };
        }


        
public static Employee GetEmployee(Guid id, string name)
        
{
            
return new Employee { ID = id, Name = name };
        }

    }

}

在Utility中定义了两个GetEmployee方法,分别返回以Anonymous Type形式和Named Type形式的Employee对象。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Artech.NewFeatureInCSharp.Library;

namespace Artech.NewFeatureInCSharp.ConsoleApp
{
    
class Program
    
{
        
static void Main(string[] args)
        
{
            var v1 
= new{ID = Guid.NewGuid(), Name= "Zhang San" };
            var v2 
= new { ID = Guid.NewGuid(), Name = "Li Si"};
            var v3 
= Utility.Anonymous_GetEmployee(Guid.NewGuid(), "Wang Wu");
            Console.WriteLine(
"var v1 = new{ID = Guid.NewGuid(), Name= \"Zhang San\" };");
            Console.WriteLine(
"var v2 = new{ID = Guid.NewGuid(), Name= \"Li Si\" };");
            Console.WriteLine(
"var v3 = Utility.Anonymous_GetEmployee(Guid.NewGuid(), \"Wang Wu\");");

            Console.WriteLine(
"\nv1.GetType() = {0}", v1.GetType());
            Console.WriteLine(
"v2.GetType() = {0}", v2.GetType());
            Console.WriteLine(
"v3.GetType() = {0}", v3.GetType());

            Console.WriteLine(
"\nobject.ReferenceEquals(v1.GetType(),v2.GetType()) = {0}",object.ReferenceEquals(v1.GetType(),v2.GetType()));
            Console.WriteLine(
"object.ReferenceEquals(v1.GetType(),v3.GetType()) = {0}"object.ReferenceEquals(v1.GetType(), v3.GetType()));
           
            Console.WriteLine(
"\n\n");

            var v4 
= new Employee { ID = Guid.NewGuid(), Name = "Zhang San" };
            var v5 
= new Employee { ID = Guid.NewGuid(), Name = "Li Si" };
            var v6 
= Utility.GetEmployee(Guid.NewGuid(), "Wang Wu");
            Console.WriteLine(
"var v4 = new Employee{ID = Guid.NewGuid(), Name= \"Zhang San\" };");
            Console.WriteLine(
"var v5 = new Employee{ID = Guid.NewGuid(), Name= \"Li Si\" };");
            Console.WriteLine(
"var v6 = Utility.GetEmployee(Guid.NewGuid(), \"Wang Wu\");");

            Console.WriteLine(
"\nv4.GetType() = {0}", v4.GetType());
            Console.WriteLine(
"v5.GetType() = {0}", v5.GetType());
            Console.WriteLine(
"v6.GetType() = {0}", v6.GetType());

            Console.WriteLine(
"\nobject.ReferenceEquals(v4.GetType(),v5.GetType()) = {0}"object.ReferenceEquals(v4.GetType(), v5.GetType()));
            Console.WriteLine(
"object.ReferenceEquals(v4.GetType(),v6.GetType()) = {0}"object.ReferenceEquals(v4.GetType(), v6.GetType()));
        }
       
    }

}

代码不复杂,我在这里简单介绍一下整体的结构。这个结构分两部分,第一部分是基于Anonymous Type的,另一部分是基于Named Employee Type的。在第一部分中,我首先创建了3个Anonymous Type的Instance:v1、v2和v3(v3是通过调用定义在Artech.NewFeatureInCSharp.Library中的Utility获得,其余两个则直接通过Inline的方式创建),第二部分也具有相同的代码结构。

var v1 = new{ID = Guid.NewGuid(), Name= "Zhang San" };
var v2 
= new { ID = Guid.NewGuid(), Name = "Li Si"};
var v3 
= Utility.Anonymous_GetEmployee(Guid.NewGuid(), "Wang Wu");

然后现实他们对应的Type的Full name.

Console.WriteLine("\nv1.GetType() = {0}", v1.GetType());
Console.WriteLine(
"v2.GetType() = {0}", v2.GetType());
Console.WriteLine(
"v3.GetType() = {0}", v3.GetType());

最后调用object.ReferenceEquals对这3个Type进行比较。

Console.WriteLine("\nobject.ReferenceEquals(v1.GetType(),v2.GetType()) = {0}",object.ReferenceEquals(v1.GetType(),v2.GetType()));
Console.WriteLine(
"object.ReferenceEquals(v1.GetType(),v3.GetType()) = {0}"object.ReferenceEquals(v1.GetType(), v3.GetType()));

大家先想想到底运行后将会出现什么样的结果,看看你的想法和真实的结果是否一致:

对于第二部分基于Named Type的输出,结果很明显,没有什么好说的。我们重点来看基于Anonymous Type的输出结果:

我们通过Inline的方式创建了v1和v2,通过调用定义在另一个Assembly中定义的Utility class创建了v3。虽然我们创建对象的方式不同,但是这3个Instance的结构完全相同,我们可以想象他们对应的Type应该相似。但是,他们到底是不是就是同一个Type呢?通过输出的Type的Full Name:<>f__AnonymousType0`2[System.Guid,System.String]来看,他们“貌似”同一个Type。但是Full name相同并不意味着他们就是同一个Type。确定两个Type的同一性的方法就是确定他们具有相同的Reference。于是我们使用了object.ReferenceEquals方法。两个调用的结果完全不同:v1和v2对应的Type是一样的,而v1和v3则不是同一个。关于Type在Managed Heap的体现,请参阅我的文章: 《[原创]What is "Type" in managed heap?》。

我们来讨论问什么会出现上面的运行结果。原因很简单:Compiler在生成Anonymous Type的时候,并不是为每个形如这样{M1=?, M2 =? , …}的结构生成一个不同的Type,它只会为不同的参数列表的结构:参数的名称,参数的数据类型,参数的相互顺序定义不同的Type。而具有相同的参数列表的{M1=?, M2 =? ,  …}会共享同一个Type。但是这种机制仅限于在同一个Assembly中。也就是在一个Assembly创建的Anonymous Type仅仅限于在本Assembly中使用,不同被另一个Assembly共享。所以我们通过Inline的方式创建了v1和v2是同一个Type的两个Instance,而我们通过跨Assembly创建的v3却属于不同的Type,尽管他们的Type定义可能完全一样。

C# 3.x相关内容:
[原创]深入理解C# 3.x的新特性(1):Anonymous Type
[原创]深入理解C# 3.x的新特性(2):Extension Method - Part I
[原创]深入理解C# 3.x的新特性(2):Extension Method - Part II
[原创]深入理解C# 3.x的新特性(3):从Delegate、Anonymous Method到Lambda Expression
[原创]深入理解C# 3.x的新特性(4):Automatically Implemented Property
[原创]深入理解C# 3.x的新特性(5):Object Initializer 和 Collection Initializer

posted on 2007-07-15 21:50 Artech 阅读(5642) 评论(29)  编辑 收藏 网摘 所属分类: E. LINQ

评论

#1楼[楼主] 2007-07-15 22:00 Artech      

时间仓促,写的有点乱,有什么问题可以通过Comments提出来。谢谢!   回复  引用  查看    

#2楼 2007-07-15 22:12 idior      

条理清晰,不过个人感觉这个feature不值得花这么大功夫去写。

下面这句话足以。 呵呵

它只会为不同的参数列表的结构:参数的名称,参数的数据类型,参数的相互顺序定义不同的Type。而具有相同的参数列表的{M1=?, M2 =? , …}会共享同一个Type。但是这种机制仅限于在同一个Assembly中。
  回复  引用  查看    

#3楼[楼主] 2007-07-15 22:17 Artech      

@idior
呵呵,让我想起了,高中作文时凑字数:)
  回复  引用  查看    

#4楼 2007-07-15 23:08 Me[未注册用户]

ScottGu已经有一系列关于New Orcas Features的文章了!
不过还是很期待你的……
你的WCF系列质量可是相当高哦~~~~
关注着
:)
  回复  引用    

#5楼[楼主] 2007-07-15 23:19 Artech      

@Me
给个链接,资源共享一下。
谢谢你的关注:)
  回复  引用  查看    

#6楼 2007-07-15 23:25 Anytao      

@idior
@Artech
分析和评论都很精彩。
不过还是很喜欢这种深入浅出的技术论述,Artech驾驭技术分析的功夫,越发炉火纯青了,每个系列都很出彩。
  回复  引用  查看    

#7楼[楼主] 2007-07-15 23:48 Artech      

@Anytao
其实在在看“Anonymous Type”的时候,也确实觉得Idior说的这点才是真正值得一写的,但是文章不能太单薄,所以我说我在“凑字数”,呵呵!

不过你的夸奖太过了,有点脸红了:)
  回复  引用  查看    

#8楼 2007-07-15 23:55 宋国安      

楼主的文章好有深度。看样子是个系列哦。期待对其他特性的分析。
包括, Lambda Expressions, Extension Methods,Implicitly Typed Local Variables, Object Initializers

个人感觉,C# 3.0的强大,在于其编译器的强大。像这些特性,均是为Linq服务的。
对于Anonymous Type,个人认为,也就是做临时变量类型在linq中使用或在一个函数内部使用,不适合用作函数返回类型,更不是跨Assembly的。
  回复  引用  查看    

#9楼[楼主] 2007-07-16 00:05 Artech      

@宋国安
你说的没错,一种编程语言和它的编译器应该是一体的。C#3.0主要的目的引入了对LINQ的支持,使我们可以一种OO的方式对原来不能使用OO的方式进行操作的数据的操作。为了有效支持LINQ,C#和与之对应的编译器引入了一系列的新特性。不过有些特性在一些非LINQ领域也有很好的应用。

这些新的特性大都限于Programing Language层面,而对于底层.NET Framework,CLR却没有本质的变化。
  回复  引用  查看    

#10楼 2007-07-16 10:13 一醉解千愁      

乱,比较期待Extension Method的分析   回复  引用  查看    

#11楼[楼主] 2007-07-16 10:17 Artech      

@一醉解千愁
正在准备:)
  回复  引用  查看    

#12楼 2007-07-16 12:25 Join miao      

内容不算很新了   回复  引用  查看    

#13楼 2007-07-16 12:56 Anytao      

@Artech
呵呵,也有同样的感受。
有意成立一个技术团队,邀请你参加,请查看留言信息,期待加盟。
  回复  引用  查看    

#14楼[楼主] 2007-07-16 13:03 Artech      

@Anytao
好呀,很荣幸:)
  回复  引用  查看    

#15楼 2007-07-16 13:09 Anytao      

@Artech
呵呵,异常的感谢,向dudu申请成功后再进一步通知你,谢了。
  回复  引用  查看    

#16楼 2007-07-16 13:20 Anytao      

@Artech
1、团队英文名称: .NET CLR Research
2、团队中文名称: CLR基础研究团队
3、团队简介: 研究,学习.NET CLR底层基础、架构和应用
4、团队成员的Blog帐号: anytao,artech
5、团队Blog是否公开:是

团队建设和相关步骤随后以邮件的形式和你进一步沟通,不会花太多时间,就是想提供一个更专注的平台来发挥博客园的技术优势,提高对CLR的理解和认知,我的邮箱是taotalk@126.com
  回复  引用  查看    

#17楼[楼主] 2007-07-16 13:57 Artech      

@Anytao
:)
  回复  引用  查看    

#18楼 2008-01-18 22:11 共同学习,共同进步      

写得太好了,只是我太落后了。谢谢。。。   回复  引用  查看    

#19楼[楼主] 2008-01-21 12:20 Artech      

@共同学习,共同进步
谢谢对本系列的关注!
  回复  引用  查看    

#20楼 2009-03-25 08:58 一人独钓      

讲的太好了。有了匿名类型结构体还有存在的必要吗,在性能方面结构体是不是优于匿名类型?   回复  引用  查看    

#21楼[楼主] 2009-03-26 12:17 Artech      

--引用--------------------------------------------------
一人独钓: 讲的太好了。有了匿名类型结构体还有存在的必要吗,在性能方面结构体是不是优于匿名类型?
--------------------------------------------------------
匿名类型仅仅是编译器的特性,对于运行时,它和一般类型是一样的,所以不存在任何性能差别!
  回复  引用  查看    

#22楼 2009-03-26 13:56 一人独钓      

@Artech
明白您的意思了,匿名类型由新的编译器支持,而CLR还是2.0。
  回复  引用  查看    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 818980




相关文章:

相关链接: