理想与现实之间

学习的最好方法就是blog

博客园 首页 新随笔 联系 订阅 管理
  68 Posts :: 2 Stories :: 426 Comments :: 12 Trackbacks

2008年11月22日 #

按:这文章算是上星期与装配脑袋一起讨论到的一些东西的总结。我试图用更多一点的代码把协变和反变解释得更浅显一点。大家也可以参考Ninputer同学的文章:

http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html

 

为什么要有协变

首先来说明一下为什么会要协变。协变其实是一个相当简单的概念。我们知道在OO的语言中,可以把一个子类的实例赋值给一个基类的引用。就像这样:

FileStream fs = new FileStream();
Stream s 
= fs;

 

当引入了泛型之后,很容易我们会想到,像下面这样一个赋值是不是可以呢:

interface ISample<T> {}
class ConcreteFileStream : ISample<FileStream>
{}

ISample
<FileStream> iFs = new ConcreteFileStream();
ISample
<Stream> iS = iFs; //这里的赋值是可以的吗?

 

直观上来看FileStream是Stream的子类,那么这个赋值当然应该是可行的。如果是可行的,那么我们称ISample<T>的泛型参数T,支持协变。

然而事实上,情况远没有这么简单。协变并不是像我们主观上认为的那样,总是可以的。在某些情况,泛型参数不能进行协变,但可以进行反变。所谓的反变,就是指对于这个泛型参数,我们可以做如下的赋值:

class ConcreteStream : ISample<Stream>
{}

ISample
<Stream> iS = new ConcreteStream();
ISample
<FileStream> iFs = iS; //如果这样的赋值可以进行,则称这个泛型参数支持反变

 

这样的赋值竟然是可以的?是不是有违我们的直觉?接下来我们讨论一下什么情况下可以协变,什么情况下可以反变,而什么情况下两者都不可以。

这里需要说明的一点是,我们这里说“一个赋值可以进行”,意思是指这个赋值不会引发类型不安全的形为。不会因此,导致类型相关的异常。在实际使用中,C#编译器会检测到“这样一个赋值是不可以进行的”,从而会引发问题的代码不能通过编译。而泛型参数也要显示的声明成是否可以协变与反变。

 

什么时候可以协变与反变?

之前提到了,要进行协变,远比我们的直觉要复杂的多。一个泛型参数是不是可以协变,取决于接口所定义的方法是如何使用这个泛型参数的。让我们来看下面的例子:

interface ISample<T>
{
    T foo();
}

 

此时,T是被用作返回值的类型。而在这种情况下,T是支持协变的。请看以下的代码:

ISample<FileStream> iFs = new ConcreteFileStream();

ISample
<Stream> iS = iFs;
Stream s 
= iS.foo(); //iS.foo()返回FileStream对象,可以隐式转化为Stream类型,没有问题!!

 

当我们使用ISample<Stream>的时候,因为类型参数为Stream,所以代码中我们指望foo方法会返回一个Stream对象。而当iS实际指向一个ISample<FileStream>的对象时,foo方法会返回一个FileStream对象。而因为FileStream是Stream的子类,因此也是一个Stream对象。所以,在这里这个赋值不会引发任何问题。

在C# 4.0当中,如果一个泛型参数可以进行协变,我们要显示地进行声明,最通常的,当T被传出的时候,可以进行协变(这在有高阶函数的时候不成立,但我们稍后再讨论),所以我们要用一个out关键字来修饰T,说明它可以协变,像这样:

interface ISample<out T>
{}

 

接下来让我们看一个不能协变的例子:

interface ISample<T>
{
    
void foo(T t);
}

ISample
<FileStream> iFs = new ConcreteFileStream();

ISample
<Stream> iS = iFs; //如果可以,会引发以下的情况,导致类型不安全

Stream s 
= new Stream();
iS.foo(s); 
//不能把s转化为FileStream!!!

 

 

 我们可以看到当使用iS的时候,我们认为类型参数是Stream,因此调用foo的时候,我们可能会把一个Stream类型的对象当作参数传递给foo。而当iS实际指向一个ISample<FileStream>的时候,foo函数要求的是一个FileStream对象。而Stream是不能转化为FileStream的。所以如果允许这样的赋值,在运行时会有一个InvalidCastException。实际情况是,C#编译器会检测到这段代码的问题,不会让代码通过编译。而在这种情况下,iS不可以指向ISample<FileStream>,也就是说T不支持协变。比较有趣的是,在这种情况下,T可以进行反变:

ISample<Stream> iS = new ConcreteStream();

ISample
<FileStream> iFs = iS;
FileStream fs 
= new FileStream();
iFs.foo(fs);
//可以将fs转化为Stream类型,所以不会有类型不安全!

 

 

在这里,使用iFs的时候,因为T的类型被指定为FileStream,foo的名义签名为 void foo(FileStream t),所以我们有可能将一个FileStream类型的对象传递给foo函数。而当iFs实际指向一个ISample<Stream>时,foo的实际签名为void foo(Stream t),而当我们把fs传递给foo的时候,会发生一个从FileStream到Stream的转换。FileStream是子类,所以这个转换完全可行。在这里,我们把一个ISample<Stream>赋值给一个ISample<FileStream>,而不会引发类型不安全。这种情况,我们称T支持反变。我们要用in关键字来显示地说明这一点:

interface ISample<in T>
{
    
void foo(T t);
}

 

所以,协变与反变的一般定义如下:

class Base {}
class Derived : Base {}
interface ISample<T> {}

ISample
<Base> iB;
ISample
<Derived> iD;

iB 
= iD; //如果这个赋值是类型安全的,那么T可以协变
iD = IB; //如果这个赋值是类型安全的,那么T可以反变

 

写到这里,你大概会觉得,当T用在参数的时候,可以反变,用作返回类型的时候,可以协变。当又是参数又是返回类型的时候,就既不能协变也不能反变了。在通常的应用中,这是正确的。这里所谓的通常,是指没有高阶函数存在的情况。很不幸的是,其实我们还蛮容易就会碰到有高阶函数的情况。那么具体的内容,让我们在下一篇里再继续。

 

posted @ 2008-11-22 23:00 Justin Shen 阅读(1916) 评论(13) 编辑

2008年4月8日 #

之前工作上偶尔用到WCF,都靠之前的一些知识对付过去了。现在终于下决心要来系统地学习一下。
拿出先前买的O'reilly的"Programming WCF"来读。

第一章对SOA做了个简单的介绍,其实这些概念之前也基本了解的,不过还是浏览一过来加深下印象,结果到也看到不少有趣的地方。
比如书里提到Service的自主性,理论上讲Service除了应该有明确地边界之外,应该还是自主的,不和其它的Service共享数据,需要交互都应该以Message的形式来进行。但是书上也提到,现实中的Service其实对这一条是没办法完全做到。因为业务数据通常是统一存放在一个关系型数据库中的,而其上的Service,例如业务或者报表,必然会共用数据。

于是想到,如果要改变这种状况,必然要把数据的供给也独立成一个Service,而其它的Service都使用这个Service。这里必然要面临的一个问题是性能下降,如果Data Object也要跨边界Serialize和Deserialize,效率应该会有很大的损失。这里,不禁异想天开:既然WCF已经做到Protocol透明,那么是不是也可以封装出一个进程内的,不Serialize的Channel呢?多少有点不切实际吧?

另外,联想到DP Group的Astoria项目已经改名叫ADO.NET Data Service,似乎和我想的这个东西有点类似。突然又联想到自己的工作,是不是给Reporting Services做一个Astoria的data processing extension呢?
posted @ 2008-04-08 23:21 Justin Shen 阅读(518) 评论(1) 编辑

2007年12月16日 #

第一次接触C#的编译,从现在看来确实和过程化语言的编译在Symbol Table的构建上有很大的差异。

MONO的C#编译器中,仿照System.Reflection以及System.Reflection.Emit中的构架,建立了自己的TypeManager,用相同的机制来完成对源代码中的类型和方法的解析以及代码生成。使用RootContext类型来统筹和驱动整个编译的过程。语法分析阶段产生的Parse Tree实际和类型系统被整合在了一起。可以看到ParseTree中的Class类型,实际间接继承自TypeContainer类型。

相对的,在ROTOR的编译器中。Parse Tree的结点在Allnodes.h中定义,而另外有独立的SYM系统,使用的方式似乎更为复杂一些。

在过程化语言的编译过程中,因为类型可能和变量重名,我们通常准备两个Environment(即查找用的Symbol Table),一个对应类型,另一个对应变量和函数。

而在面向对象的语言里,对于一个Identifier的查找,与过程化语言很不一样。
初步考虑仍然使用两套Environment,其中之一为一个类型系统(TypeManager),保存所有已经被解析之后的代码中的类型信息,按照以下的树型方式保存:
Namespace
 |-- Class/Struct/Interface
          |-- Method
          |-- Property
          |-- Member
为了提高作Name Resolving和Type Checking的效率,底层的数据结构可能需要结合HashTable和Tree

因为C#支持引用先于定义(C++的前置声明对编译器来说就是福音啊),因此解析时可能要按照以下的顺序:

解析所有类型的名称 (此时类型的其它信息一概未知,但光有名字的信息已经足以在下步做类型检查了)
   |---> 解析所有的数据成员和方法的声明 (使用上一步中的信息对成员的类型、方法返回类型、方法参数类型做类型检查)
               |---> 解析方法的定义

在解析方法的定义中,将使用到另一套Environment(VariableEnv),这个Environment中,包含了所有的本地变量的Binding,Binding中的信息非常简单,只需要表明该Variable的类型即可。VariableEnv和过程化语言编译中使用的Symbol Table类似,有成熟的算法可以参考,Block之中作用域的扩展和相互屏敝也类似。

对于以下的代码:

MyClass t = new MyClass();
t.Foo();

在解析t.Foo();的时候,首先在VariableEnv查找t的Binding,从而得知类型为MyClass,然后在TypeManager中查找MyClass的方法中,是否有Foo()的定义。

初步设想,当中间代码生成结束之后,把得到的Intermediate Representation(IR)挂接到TypeManager中的Method部分中去,TypeManager中的信息已经足够可以用来做后阶段真正的IL生成了。此时,从Parse Tree到IR的转换也就完成了。

题外话:方法重载的检查其实相对简单,当在解析方法定义的时候,根据参数的类型,在TypeManager中查找相应的方法即可。即使在面向过程的语言之中,对以下的代码:

public void foo();
public void foo(int i);

加入到Environment中的时候,我们只要保证foo->foo(int i)的Binding不会屏敝掉之前加入的foo->foo()的Binding即可。这个既可以通过存放的数据结构来实现也可以通过查找的算法来加以保证。

posted @ 2007-12-16 16:00 Justin Shen 阅读(1778) 评论(3) 编辑

从初学.NET的时候,就常常挂在嘴边的一句话是:托管程序和原生程序最大的区别是编译器在程序集中加入了大量的元数据,因此托管程序是自描述的,我们可以在运行时获得关于源程序的任何信息,从而使用反射之类的高级机制。
 
一直以为自己对这句话的理解已经很透彻了,但自己鼓捣编译器,才豁然发现,原来编译成IL,编译器需要Emit这么多的描述类型描述方法的元数据,在代码生成阶段,我们依然要保留大量的诸如Namespace, Class, Method的信息。而编译成x86 ASM的话,到代码生成阶段,就只有对应指令流的IR树罢了。在源代码层面的类型啊、函数定义啊、操作符啊、函数重载啊之类的信息,已经完全丢失了。
 
还挺好玩的,嘿嘿。
posted @ 2007-12-16 14:29 Justin Shen 阅读(925) 评论(0) 编辑

2005年11月22日 #

工作工作的,似乎忘记了要继续充电了。当然喽,工作上的原因,SQL Server和数据库上的长进还是很大的,不过这些还不够呀。下面是一个欠债清单,要下决心,一个一个还掉了!
 
 
- 阅读Rotor的源码
 
在进公司之前,自己的.NET水准终于差不多了可以去看看Rotor源码的程度了。不过没想到,工作之后,就一点没时间来做这种阅读功课了。现在只有在地铁上有点看书的时间,考虑要不要把源代码打印出来在地铁上看,嘿嘿!至少要把Execution Engine和Thread两大部分给搞懂。 其实对Regular Expression的实现也蛮有兴趣,不过大概没时间。
 
不过上班也有上班的好处,收获以下两本极有价值的书:
 
   + Inside Rotor
   + Shared Source CLI Essentials
 
- 编译原理
 
对编译原理的兴趣始终不减。需要下决定把龙书看完。感觉这次应该把精力集中在代码生成和代码优化上。前端的东西确实比较死。
 
以下的书目也在采购计划上。可惜O'reilly的那本《Lex和Yacc》已经绝版。发现原来书店里都只存2年之内的书,看样子下次看到好书绝对不能手弱。好在现在俺也工作了,不怕。嘿嘿~~
 
 + Advanced Compiler Design and Implementation
 + Crafting a Compiler with C
 
以上两上皆是Amazon上四星半评价的好书。
 
实践的话,想要阅读一下Rotor中,C#的编译器的源码。朋友说里面的词法分析器不是用Lex生成的,而是Hand-craft,似乎还蛮有兴趣去瞧一下的。不过更多代码生成和优化的环节是不是会包含在JIT编译器的代码中呢?
 
- 复习数据结构和算法
 
数据结构是从初中开始就一陪伴着我的东西了。还记得考大学的时候,做数学物理脑袋涨的时候,就会把严蔚敏老师的《数据结构》拿出来翻翻。现在到是越来越怀念高中的生活。前天晚上的时候,梦到高中时的同桌,梦里那份高兴的心情,似乎好久没有在现实生活感受到了。
 
《Introduction to Algorithm》这本书是在学数据结构和算法的时候,一直梦寐以求的。可惜买到的时候,已经没有时间看了。再加上MIT公开课程里的那个课程表和相应的习题、讲义、试卷。闲置在那里实在也太可惜。决定好好从头到尾再复习一遍。MIT的课程表里,似乎三小时一个Session,一共42个Session,似乎需要坚持蛮久才行的样子。
 
以下的是在采购列表上的书:
 
 + Algorithms in C
 
由Robert Sedgewick撰写的这本书,据传被认为是我们敬爱的Knuth教授的那套《The Art of Computer Programming》的一个可阅读版本。嘿嘿。在学校的图书馆里看过一部分,说实话,不太喜欢这种风格的C程序,不过盛名之下,应该会有两把刷子的。可以用来参考。同样也是一本Amazon上四星半评价的好书。
 
- 数据库原理
 
这算是本职工作了。好歹现在做着SQL Server的技术支持,数据库知识上不更上层楼是不行。目前的储备是由著名的Abraham Silberschatz教授编写的《Database System Concept》一书。我同时还有他编写的那本久负盛名的恐龙书《Operating System Concept》,可惜现在大概是真的没时间看了。很多好书总是到最近才出,学完了才买到,真是可惜错过。好在学操作系统的时候,已经有Tanenbaum教授的《Operating Systems: Design and Implementation》(Amazon四星)不然损失就大了。如果你是一个Linux Fan的话,应该会有听说过这本如雷灌耳的书吧。;-)
 
不过《Database System Concept》在Amazon上的评价只有三星,而且MIT公开课程中的数据库原理也不是用这本书做教材,而是直接用数据库理论的那些原始论文来讲的。从公司的Library里借了MIT用作教材的数据库论文选集《Readings on Database Systems(Fourth Edition》。这本选集被称为Red Book,也是四星半评论的名著。只是从美国的总部图书馆漂洋过海而来不知道会要多少时间。到时间一定翻印一本。
 
- Reporting Services
 
这也是工作的一部分,Reporting Services可是属于我的Specialty哦!在进公司之前,就对RS很有兴趣,所以毫不犹豫就Ready了。:) 不过应用层面的Case都有点无聊,到是对RS的Architecture很有兴趣,一个典范式的.NET程序。
 
准备研究的部分,包括PDF Rendering Extension和Email Delivery Extension。目前手上的资料只有这两部份和Report Model的Specification,简略到不行的东西,竟然也是Microsoft Confidential。唉...如果能看到源码就好了,看样子,自己还要不断加油。好在.NET的程序反编译出来看也凑合。
 
动手的部分,是想写一个for word的Rendering Extension。一方面,似乎客户那里对这个功能还是蛮有需求的,另一方面,自己太久不写程序了,也得找个大一点的东东来练手一下。不过这个应该是蛮久之后的事情了,现在连是生成WordML还是原生Word文档都还没有想好。
 
近一点的目标包括,研习一下VS2005示例程序中的ReportViewer的那个范例。ReportViewer的出现,真是解决了Reporting Services的一大难题,终于可以不要再反复回答那一个BT的问题了。
 
- Win32 User Mode Debug
 
手里有的书是《Debugging Applicatoin》,又是Amazon上一本四星半评价的书。可惜不是最新版的,没有.NET的部分。不过新版本的我有电子书,不怕。顺便提下,这书是我从Oliver那里rob过来的,嘿嘿。不过他好象有布置我要看完。
 
另一个重要的参考是《Win32 User Mode Debug》这本内部的培训教材。差不多看完一遍了。第一部分写得还真棒,感觉我是越来越喜欢Microsoft写的培训教材了,再下去要懒得看书了。不过,附带的习题有点BT,为啥调用一下malloc都可以Access Violation?一点也看不出来。啥叫AV in Heap Manager啊? Oliver又去北京了,没人教我,可怜啊...
 
 
呼...写下来发现,欠的东西还真是蛮多,这都要还到哪年哪月呀,不管怎样,还是努力吧!
posted @ 2005-11-22 00:37 Justin Shen 阅读(4756) 评论(14) 编辑

2005年1月3日 #

这本书比较全面地介绍了SharpDevelop的开发原理,写得非常不错,然而中文的翻译版本实在太糟糕了...好在现在它的出版社Apress,提供了免费的电子书下载:

http://www.apress.com/free/content/Dissecting_A_CSharp_Application.pdf
posted @ 2005-01-03 22:04 Justin Shen 阅读(4710) 评论(10) 编辑

2004年12月21日 #

摘要: 还记得去年圣诞节的那一期Special Holiday Episode 吗?今年的圣诞节又快到了,所以又出了一个Speical Holiday Episode II,主要介绍了一下在Avalon里怎样自己实现一个类似FlowPanel的东东。去年节目结束的部分,两人(Don Box and Chris Anderson)自弹自唱了改编后的圣诞歌曲。今年他们再接再励,给大家献上了诗朗诵。这...阅读全文
posted @ 2004-12-21 13:26 Justin Shen 阅读(1366) 评论(3) 编辑

2004年12月17日 #

摘要: 最近在根据PSP(个体软件开发过程)的相关内容,统计我每天花在主要活动上的时间。在坚持了大概1个月之后,总算也积累了一定的数据。我发现每天浪费掉的时间还真是非常多。在统计数据里,很少有单次活动超过1个小时的记录,大部分时候总是会被各种各样的事情打断。而且很多时候,你觉得你整整工作了一个下午,但实际的有效时间可能都只有3小时,真是非常惊讶呢。 我发现做这些统计也带来了一些时间安排上的好处。如果你能够...阅读全文
posted @ 2004-12-17 23:17 Justin Shen 阅读(1522) 评论(5) 编辑

2004年12月12日 #

摘要: 看到这个消息的时候,并不觉得十分意外,因为一直觉得《csdn开发高手》的文章质量比较欠佳,买过最初几期之后,就失去了再买下去的兴趣。 然而抛开个案不谈,在《程序春秋》和《csdn开发高手》相继倒下之后,在开发类的技术杂志领域又只下了《程序员》孤军奋战。这多少是一个不怎么健康且值得思考的现象。或许国内确实比较缺乏做技术杂志的土壤,因为似乎并没有很多人是在关注技术,浏览csdn的论坛,你常常会有这样一...阅读全文
posted @ 2004-12-12 21:58 Justin Shen 阅读(1888) 评论(16) 编辑

摘要: 最近一直在用VS 2005 express,很多功能都让人爱不释手,然而总还是需要写一些必须运行在.net v1.1上的程序,被迫要去用VS 2003,觉得非常不爽。人果然是容易被宠坏的,越来越懒了  所以现在就尝试用VS 2005写代码,然后用nant来编译项目。nant有三种改变编译目标平台的方法: (1)直接在命令行中指定,例如:      ...阅读全文
posted @ 2004-12-12 21:04 Justin Shen 阅读(2167) 评论(3) 编辑

2004年12月10日 #

摘要: 有时候,你不得不佩服微软的想法!http://webmessenger.msn.com/阅读全文
posted @ 2004-12-10 22:39 Justin Shen 阅读(1321) 评论(7) 编辑

2004年12月7日 #

摘要: 在学习.net编程时,你一定有听到过这样的说法,System.String类是不可变字符串,也就是说你不能修改一个字符串的值。 比如以下这段代码 string s = "hello"; s = "world"; 你并不是把s的值修改为world,而是生成了一个新的包含"world"的字符串...阅读全文
posted @ 2004-12-07 22:15 Justin Shen 阅读(1518) 评论(4) 编辑

2004年11月29日 #

摘要: 高中和初进大学的时候,周围总有这么一种论调:好的计算机公司都喜欢招聘数学系或者物理系的毕业生,说是数学功底比较扎实。我的一位学物理出身的长辈曾问我计算机系都学些什么,听了我的描述之后,露出一种不屑的神色,说这些有什么可学的。言下之意似乎只有物理、数学系学的那些东西才算是真正的学问。然而,既然计算机从数学和物理之中分离出来,成了一个独立的学科,总有它独到之处。那么这个独到之处是什么呢?我觉得不是数据...阅读全文
posted @ 2004-11-29 00:06 Justin Shen 阅读(1735) 评论(14) 编辑

2004年11月3日 #

摘要: 如果你不清楚VS 2005的定制Code Expansion的功能,可以看一下我的这篇文章。第一个是用来生成string,其实就是自动生成一对双引号,在中间输入好string之后,按回车可以跳到双引号之后。这个是受了eclipse的启发,不过觉得我的这个更好用,因为输入完字符串之后,不用去按方向键,嘿嘿 <?xml version="1.0" enco...阅读全文
posted @ 2004-11-03 22:56 Justin Shen 阅读(1467) 评论(6) 编辑

摘要: 我在私有新闻组里询问VC2005里是不是会有Refactoring或者Code Expansion的功能,结果得到如下的回复,真是十分遗憾:Unfortunately, we won't be able to support refactoring or code expansion in the Whidbey timeframe.  However, we will defi...阅读全文
posted @ 2004-11-03 19:47 Justin Shen 阅读(1506) 评论(1) 编辑