- 浏览: 3022258 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (430)
- Programming Languages (23)
- Compiler (20)
- Virtual Machine (57)
- Garbage Collection (4)
- HotSpot VM (26)
- Mono (2)
- SSCLI Rotor (1)
- Harmony (0)
- DLR (19)
- Ruby (28)
- C# (38)
- F# (3)
- Haskell (0)
- Scheme (1)
- Regular Expression (5)
- Python (4)
- ECMAScript (2)
- JavaScript (18)
- ActionScript (7)
- Squirrel (2)
- C (6)
- C++ (10)
- D (2)
- .NET (13)
- Java (86)
- Scala (1)
- Groovy (3)
- Optimization (6)
- Data Structure and Algorithm (3)
- Books (4)
- WPF (1)
- Game Engines (7)
- 吉里吉里 (12)
- UML (1)
- Reverse Engineering (11)
- NSIS (4)
- Utilities (3)
- Design Patterns (1)
- Visual Studio (9)
- Windows 7 (3)
- x86 Assembler (1)
- Android (2)
- School Assignment / Test (6)
- Anti-virus (1)
- REST (1)
- Profiling (1)
- misc (39)
- NetOA (12)
- rant (6)
- anime (5)
- Links (12)
- CLR (7)
- GC (1)
- OpenJDK (2)
- JVM (4)
- KVM (0)
- Rhino (1)
- LINQ (2)
- JScript (0)
- Nashorn (0)
- Dalvik (1)
- DTrace (0)
- LLVM (0)
- MSIL (0)
最新评论
-
mldxs:
虽然很多还是看不懂,写的很好!
虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 -
HanyuKing:
Java的多维数组 -
funnyone:
Java 8的default method与method resolution -
ljs_nogard:
Xamarin workbook - .Net Core 中不 ...
LINQ的恶搞…… -
txm119161336:
allocatestlye1 顺序为 // Fields o ...
最近做的两次Java/JVM分享的概要
看来阅读一个开发人员的blog是获取知识的一个捷径,特别是当那位开发人员负责的产品是你天天都用的基础设施之一,例如说……编译器。在阅读Eric Lippert的blog时,我无意中了解到了很多我以前所不熟悉的知识,例如说一些语言特性,一些编程思想之类;但更有趣的,我了解到了很多他所负责的产品中的诡异地方。
===================================================
开篇花絮:
假如我们现在有一个枚举类型E,其中有一个枚举值的名字是x。
你或许知道这个表达式是对的:
但是你或许不知道这个表达式(根据语言规范应该)是错的:
对此感到好奇的请到原文查看详情:The Root Of All Evil, Part One
错误在于,C# 2.0的规范中说明“字面量0”可以被转化为任意枚举类型。是“字面量0”,而不是“编译时常量0”。
这这这...Aargh, it's driving me nuts! (模仿Eric的语气
如果你把下面的代码放到.NET Framework 3.5 Beta 2中编译测试的话,会看到编译器完全没对上面提及的第二种情况作出警告:
编译器会抱怨局部变量e没有被使用过(也就潜在意味着这个变量没有作用,是多余的),但并没对这里我们关心的问题给出警告。正好刚装上了.NET Framework 3.5的RTM,测试结果仍然一样。Mono 1.2.5.1的在这点上的行为与前述一致。
在Unified C# 3.0 Specification的1.10 Enum中,规定了
与前几个版本的规定没怎么改变,仍然是说“字面量0”而不是“编译时常量0”可以被转换为任意枚举类型。
于是.NET Framework与Mono都“很无奈”的在这点上无法与规范保持一致了。=_=||
===================================================
C#里派生类的方法里的匿名delegate调用基类的方法会产生无法验证的代码
原文:Why are base class calls from anonymous delegates nonverifiable?
前面开篇花絮里提到的是没有熟思而做的优化带来的后果,而下面要关注的问题就稍微复杂一些了。
考虑这段代码片段:
用.NET Framework 3.5 Beta 2附带的C#编译器(csc.exe)编译上面的代码,会得到以下警告:
刚装了.NET Framework 3.5的RTM,测试结果一样。至于Mono 1.2.5.1更有趣,完全没有报错。
这里有什么问题呢?Charlie()方法里用this/base去访问自身/基类的成员,不是很正常的么。问题出在C#中应对闭包生成的代码。
在C# 2.0中,引入了匿名delegate的概念,因而可以定义嵌套方法;在C# 3.0中,更进一步引入了Lambda Expression,同样可以用于定义嵌套方法。这里,嵌套的方法的作用域遵守词法作用域,也就是说内部方法可以访问外部包围作用域的变量,包括外部的“this”。外部包围作用域就对嵌套内部方法形成了“闭包”。
由于当一个嵌套方法生成(实例化)后,它的生命周期与它的外部方法不一定相同。它从外部环境中“捕获”到的变量,就像是从外部“逃逸”出来了一样。上面的例子中,Charlie()方法里x和this都成为了逃逸变量。
这些逃逸变量必须与嵌套方法的生命周期相同,即使外部方法已经返回也不能被立即销毁;因此这些逃逸变量也不能在栈上分配。这样,就需要为逃逸变量另外分配空间,常见的做法是在堆上分配。
Unified C# 3.0 Specification中,
不同语言为闭包分配空间的具体方式不同。C#中,编译器会将逃逸变量提升为成员变量,并将匿名delegate提升为一个成员方法——不过并不是提升到原本的类中,而是一个由编译器构造的私有内部类中。上面的代码,会被编译器变成类似以下的形式:
当然这只是伪代码。C#中并没有"__nonvirtual__"关键字。一般来说,C#中的方法调用都是通过callvirt的IL指令完成的;而通过base关键字所做的方法调用则不遵循虚函数要使用最具体版本的规则,因而使用的是call的IL指令来完成。这里所谓"__nonvirtual__"就是要表现这个意思。
可以看到,原本代码中匿名delegate里对base的访问,实际上被生成到了另外一个类(私有内部类)的方法中,而那个类的"base"其实应该是System.Object……于是就有问题了。关键字“base”本来应该只能在同一个继承系的派生类中使用,这样生成的代码就像是让“base”的作用范围泄露了一般。没错,编译出来的代码确实是能运行,却变得不可验证(unverifiable)。
但这并不是使用.NET Framework的程序员的错;他们只是想在正确的地方正确的使用base而已。所以.NET Framework的应对方法是给出一个警告信息,提醒程序员修改代码来避开这个问题。不幸的是,Mono并没有提供任何警告提示。用Mono 1.2.5.1编译上面的代码,并用.NET Framework的PEVerify来验证,会看到下面的错误信息:
错误所对应的IL代码为:
也就是原本的base.Blah()。
一个有趣的观察:虽然C#的语言规范中没有说明具体该如何为闭包生成代码,但.NET Framework与Mono所做的几乎是一样的。这大概是因为Mono要尽量保持与.NET Framework的兼容吧。
前面的例子是用匿名delegate,在C# 3.0中换成Lambda Expression也一样:
---------------------------------------
上面提到了编译器会生成不可验证代码的状况。不过要是把前面例子中Charlie()里的x给去掉,变成这样的话:
那么.NET Framework的C#编译器能发现唯一的逃逸变量是this,于是不会生成一个私有的内部类,而是直接将那个匿名delegate生成为Bravo的一个私有成员方法。也就是生成类似这样的代码:
换句话说,当逃逸变量只有this时,编译器并不会生成不可验证的变量。不过为了外表上行为的一致性,.NET Framework的C#编译器仍然会给出跟上面一样的警告。
Mono方面则是没有做这样的优化,仍然会与前面所说的状况一样,生成不可验证的代码。
===================================================
开篇花絮:
假如我们现在有一个枚举类型E,其中有一个枚举值的名字是x。
你或许知道这个表达式是对的:
0 | E.x
但是你或许不知道这个表达式(根据语言规范应该)是错的:
0 | 0 | E.x
对此感到好奇的请到原文查看详情:The Root Of All Evil, Part One
错误在于,C# 2.0的规范中说明“字面量0”可以被转化为任意枚举类型。是“字面量0”,而不是“编译时常量0”。
这这这...Aargh, it's driving me nuts! (模仿Eric的语气
如果你把下面的代码放到.NET Framework 3.5 Beta 2中编译测试的话,会看到编译器完全没对上面提及的第二种情况作出警告:
enum E { x = 1 } class Program { public static void Main(string[] args) { E e = 0 | 0 | E.x; } }
编译器会抱怨局部变量e没有被使用过(也就潜在意味着这个变量没有作用,是多余的),但并没对这里我们关心的问题给出警告。正好刚装上了.NET Framework 3.5的RTM,测试结果仍然一样。Mono 1.2.5.1的在这点上的行为与前述一致。
在Unified C# 3.0 Specification的1.10 Enum中,规定了
引用
In order for the default value of an enum type to be easily available, the literal 0 implicitly converts to any enum type.
与前几个版本的规定没怎么改变,仍然是说“字面量0”而不是“编译时常量0”可以被转换为任意枚举类型。
于是.NET Framework与Mono都“很无奈”的在这点上无法与规范保持一致了。=_=||
===================================================
C#里派生类的方法里的匿名delegate调用基类的方法会产生无法验证的代码
原文:Why are base class calls from anonymous delegates nonverifiable?
前面开篇花絮里提到的是没有熟思而做的优化带来的后果,而下面要关注的问题就稍微复杂一些了。
考虑这段代码片段:
引用
using System; public delegate void D( ); public class Alpha { public virtual void Blah( ) { Console.WriteLine( "Alpha.Blah" ); } } public class Bravo : Alpha { public override void Blah( ) { Console.WriteLine( "Bravo.Blah" ); base.Blah( ); } public void Charlie( ) { int x = 123; D d = delegate { this.Blah( ); base.Blah( ); Console.WriteLine( x ); }; d( ); } } class Program { // do nothing, just to make the compiler happy // else we'd compiler with /target:library public static void Main(string[] args) { } }
用.NET Framework 3.5 Beta 2附带的C#编译器(csc.exe)编译上面的代码,会得到以下警告:
引用
Microsoft (R) Visual C# 2008 Compiler Beta 2 version 3.05.20706.1 for Microsoft (R) .NET Framework version 3.5
版权所有 (C) Microsoft Corporation。保留所有权利。
test1.cs(23,13): warning CS1911: 从匿名方法、lambda表达式、查询表达式或迭代器通过“base”关键字访问成员“Alpha.Blah()”会导致代码无法验证。请考虑将这种访问移入针对包含类型的辅助方法中。
版权所有 (C) Microsoft Corporation。保留所有权利。
test1.cs(23,13): warning CS1911: 从匿名方法、lambda表达式、查询表达式或迭代器通过“base”关键字访问成员“Alpha.Blah()”会导致代码无法验证。请考虑将这种访问移入针对包含类型的辅助方法中。
刚装了.NET Framework 3.5的RTM,测试结果一样。至于Mono 1.2.5.1更有趣,完全没有报错。
这里有什么问题呢?Charlie()方法里用this/base去访问自身/基类的成员,不是很正常的么。问题出在C#中应对闭包生成的代码。
在C# 2.0中,引入了匿名delegate的概念,因而可以定义嵌套方法;在C# 3.0中,更进一步引入了Lambda Expression,同样可以用于定义嵌套方法。这里,嵌套的方法的作用域遵守词法作用域,也就是说内部方法可以访问外部包围作用域的变量,包括外部的“this”。外部包围作用域就对嵌套内部方法形成了“闭包”。
由于当一个嵌套方法生成(实例化)后,它的生命周期与它的外部方法不一定相同。它从外部环境中“捕获”到的变量,就像是从外部“逃逸”出来了一样。上面的例子中,Charlie()方法里x和this都成为了逃逸变量。
这些逃逸变量必须与嵌套方法的生命周期相同,即使外部方法已经返回也不能被立即销毁;因此这些逃逸变量也不能在栈上分配。这样,就需要为逃逸变量另外分配空间,常见的做法是在堆上分配。
Unified C# 3.0 Specification中,
引用
7.14.4 Outer variables
Any local variable, value parameter, or parameter array whose scope includes the lambda-expression or anonymous-method-expression is called an outer variable of the anonymous function. In an instance function member of a class, the this value is considered a value parameter and is an outer variable of any anonymous function contained within the function member.
7.14.4.1 Captured outer variables
When an outer variable is referenced by an anonymous function, the outer variable is said to have been captured by the anonymous function. Ordinarily, the lifetime of a local variable is limited to execution of the block or statement with which it is associated (§5.1.7). However, the lifetime of a captured outer variable is extended at least until the delegate or expression tree created from the anonymous function becomes eligible for garbage collection.
Any local variable, value parameter, or parameter array whose scope includes the lambda-expression or anonymous-method-expression is called an outer variable of the anonymous function. In an instance function member of a class, the this value is considered a value parameter and is an outer variable of any anonymous function contained within the function member.
7.14.4.1 Captured outer variables
When an outer variable is referenced by an anonymous function, the outer variable is said to have been captured by the anonymous function. Ordinarily, the lifetime of a local variable is limited to execution of the block or statement with which it is associated (§5.1.7). However, the lifetime of a captured outer variable is extended at least until the delegate or expression tree created from the anonymous function becomes eligible for garbage collection.
不同语言为闭包分配空间的具体方式不同。C#中,编译器会将逃逸变量提升为成员变量,并将匿名delegate提升为一个成员方法——不过并不是提升到原本的类中,而是一个由编译器构造的私有内部类中。上面的代码,会被编译器变成类似以下的形式:
引用
public class Bravo : Alpha { public override void Blah() { Console.WriteLine("Bravo.Blah"); base.Blah(); } // compiler generated inner class private class __locals { public int __x; public Bravo __this; public void __method() { this.__this.blah(); // on the next line, no such "__nonvirtual__" in C# __nonvirtual__ ((Alpha)this.__this).Blah()); Console.WriteLine(this.__x); } } public void Charlie() { __locals locals = new __locals(); locals.__x = 123; locals.__this = this; D d = new D(locals.__method); d(); } }
当然这只是伪代码。C#中并没有"__nonvirtual__"关键字。一般来说,C#中的方法调用都是通过callvirt的IL指令完成的;而通过base关键字所做的方法调用则不遵循虚函数要使用最具体版本的规则,因而使用的是call的IL指令来完成。这里所谓"__nonvirtual__"就是要表现这个意思。
可以看到,原本代码中匿名delegate里对base的访问,实际上被生成到了另外一个类(私有内部类)的方法中,而那个类的"base"其实应该是System.Object……于是就有问题了。关键字“base”本来应该只能在同一个继承系的派生类中使用,这样生成的代码就像是让“base”的作用范围泄露了一般。没错,编译出来的代码确实是能运行,却变得不可验证(unverifiable)。
但这并不是使用.NET Framework的程序员的错;他们只是想在正确的地方正确的使用base而已。所以.NET Framework的应对方法是给出一个警告信息,提醒程序员修改代码来避开这个问题。不幸的是,Mono并没有提供任何警告提示。用Mono 1.2.5.1编译上面的代码,并用.NET Framework的PEVerify来验证,会看到下面的错误信息:
引用
Microsoft (R) .NET Framework PE Verifier. Version 3.5.20706.1
Copyright (c) Microsoft Corporation. All rights reserved.
[IL]: Error: [F:\FX\share\testClosure.exe : Bravo+<>c__CompilerGenerated0::<Charlie>c__1][offset 0x00000011] The 'this' parameter to the call must be the calling method's 'this' parameter.
1 Error Verifying testClosure.exe
Copyright (c) Microsoft Corporation. All rights reserved.
[IL]: Error: [F:\FX\share\testClosure.exe : Bravo+<>c__CompilerGenerated0::<Charlie>c__1][offset 0x00000011] The 'this' parameter to the call must be the calling method's 'this' parameter.
1 Error Verifying testClosure.exe
错误所对应的IL代码为:
IL_0011: call instance void Alpha::Blah()
也就是原本的base.Blah()。
一个有趣的观察:虽然C#的语言规范中没有说明具体该如何为闭包生成代码,但.NET Framework与Mono所做的几乎是一样的。这大概是因为Mono要尽量保持与.NET Framework的兼容吧。
前面的例子是用匿名delegate,在C# 3.0中换成Lambda Expression也一样:
public void Charlie( ) { // int x = 123; int x = 123; D d = ( ) => { this.Blah( ); base.Blah( ); Console.WriteLine( x ); }; d( ); }
---------------------------------------
上面提到了编译器会生成不可验证代码的状况。不过要是把前面例子中Charlie()里的x给去掉,变成这样的话:
public void Charlie( ) { D d = delegate { this.Blah( ); base.Blah( ); }; d( ); }
那么.NET Framework的C#编译器能发现唯一的逃逸变量是this,于是不会生成一个私有的内部类,而是直接将那个匿名delegate生成为Bravo的一个私有成员方法。也就是生成类似这样的代码:
public class Bravo : Alpha { public override void Blah() { Console.WriteLine("Bravo.Blah"); base.Blah(); } // compiler generated method public void __method() { this.blah(); // on the next line, no such "__nonvirtual__" in C# __nonvirtual__ ((Alpha)this).Blah()); } public void Charlie() { D d = new D(this.__method); d(); } }
换句话说,当逃逸变量只有this时,编译器并不会生成不可验证的变量。不过为了外表上行为的一致性,.NET Framework的C#编译器仍然会给出跟上面一样的警告。
Mono方面则是没有做这样的优化,仍然会与前面所说的状况一样,生成不可验证的代码。
发表评论
-
字符串的一般封装方式的内存布局 (1): 元数据与字符串内容,整体还是分离?
2013-11-07 17:44 22280(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局
2013-11-01 12:55 0(Disclaimer:未经许可请 ... -
关于string,内存布局,C++ std::string,CoW
2013-10-30 20:45 0(Disclaimer:未经许可请 ... -
对象的重量
2011-08-21 17:15 0http://domino.research.ibm.com/ ... -
GetCustomAttribute()每次都返回新Attribute实例
2009-11-10 10:30 0Jeffrey Zhao: 一次失败的尝试(上):原来GetC ... -
委托与方法和隐藏参数
2009-09-07 15:32 3258之前正好发了些帖子是关于CLR里的委托的,然后看到老赵说事件也 ... -
要让CLR挂掉的话(第二弹)……
2009-09-04 03:26 12780(Disclaimer:如果需要转 ... -
要让CLR挂掉的话……
2009-09-02 16:53 4709(Disclaimer:如果需要转载请先与我联系。 作者:Re ... -
趣味编程:函数式链表的快速排序
2009-08-31 08:53 3395(恢复自2009-08-28的备份 ... -
事件处理器导致内存泄漏
2009-08-25 15:03 0Memory leak via event handlers ... -
C# 3.0的类型推导
2009-08-23 12:24 0Howard Dierking: Lambda, Lambda ... -
把lock的意思给弄混了 T T
2009-08-20 17:49 2560悲剧啊……前几天有个同学不停问我Java里的同步问题,今天写C ... -
把IEnumerable<T>和IObservable<T>粘起来?
2009-07-23 03:02 0Channel 9: Expert to Expert: Br ... -
Scott Peterson: Variance, Thy Name is Ambiguity
2009-07-01 23:49 1600原文作者:Scott Peterson 原文地址:http:/ ... -
void无法协变
2009-06-30 11:17 0Eric Lippert The void is invari ... -
同一个表达式算出来的浮点数结果会不相等?
2009-05-30 03:27 0浮点数有很多可把玩的地方。例如下面这段C程序: #includ ... -
C#开始默认引用Microsoft.CSharp.dll
2009-05-20 16:14 0记得VB6的运行时么?留意到VB.NET的程序都需要额外的VB ... -
反射与显式实现接口的方法
2009-05-20 11:43 4017在前一帖里,我用到了下面三处Expression.Call() ... -
看到一个关于ref参数与多态的问题,记一下
2009-05-18 10:48 1919刚才读到Alan McGovern的一帖,问为什么形式参数是r ... -
C#的+=运算符两例
2009-05-06 18:18 1986刚偶尔看到了justjavac写的java解惑 - 半斤八两( ...
相关推荐
关于Delegate【代理、委托】是C#中一个非常重要的概念,向前可以推演到C++的指针,向后可以延续到匿名方法、lambda表达式。 现在我就从一个最简单最实用的一个小例子出发分析一下Delegate的使用。 现在有两个窗体...
C# Delegate讲解C# Delegate讲解C# Delegate讲解C# Delegate讲解
C# delegate thread范例 若不懂在代码中,线程如何调用的可以看看
委托声明定义从类 System.Delegate 派生的类。委托实例封装一个或多个方法,每个方法都被称为可调用实体。对于实例方法,可调用实体由一个实例和该实例上的方法组成。对于静态方法,可调用实体仅由一个方法组成。...
本文实例分析了C#匿名方法与Delegate类型转换错误。分享给大家供大家参考。具体分析如下: 问题描述 C#2.0出现了匿名方法, 这在一定程度上节省了我们维护代码上下文的精力, 也不需要思考为某个方法取什么名字比较...
适合初学者了解C#的delegate,是一个非常简单的例子。
委托回调
最近收集的VB.Net-C#多线程Thread-代理委托delegate编程。文章列表: c#.net多线程同步.txt C#WebBrowser页面与WinForm交互技巧一.txt C#多线程编程-多细全.txt C#多线程编程简单实例.txt C#多线程窗体控件安全访问....
C#中的事件和委托(Delegate,Event)
C#例子代码 A0031_delegateC#例子代码 A0031_delegateC#例子代码 A0031_delegateC#例子代码 A0031_delegateC#例子代码 A0031_delegateC#例子代码 A0031_delegateC#例子代码 A0031_delegateC#例子代码 A0031_...
virtual在派生类中声明其实现可由重写成员更改的方法或访问器。 volatile指示字段可由操作系统、硬件或并发执行的线程等在程序中进行修改。 9,语句 语句是程序指令。除非特别说明,语句都按顺序执行。C# 具有下列...
c#的回调函数(delegate关键字)
委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易。它们就像是一道槛儿,过了这个槛的人,觉得真是太容易了,而没有过去的人每次见到委托和事件就...
delegate 与指针的使用,dll处理数据,调用C#代理函数。
CSharp_Delegate C#委托 本人博客中的示例代码
C#中的各种名词: 常数:声明时用const修饰,是隐式静态类型 域:一个代表和某对象或类相关的变量的成员 字段:和属性相同,是用来存储对象的值,可以...派生类调用基类的方法可以使用base关键字,如base.Method();
在写代码前我们先来熟悉.net框架中和事件有关的类和委托,了解C#中预定义事件的处理。 EventArgs是包含事件数据的类的基类,用于传递事件的细节。 EventHandler是一个委托声明如下 public delegate void ...
委托(Delegate):委托是一种类型,它代表一个方法,可以用来将方法作为参数传递给其他方法。 Invoke和BeginInvoke方法:在Windows Forms中,每个控件都有一个Invoke方法和一个BeginInvoke方法。 BackgroundWorker...
delegate与事件的实例,很简单的例子,帮助初学者理解,其中有两个delegate,一个有参数,一个无参数