- 浏览: 3048804 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (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 22393(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 3304之前正好发了些帖子是关于CLR里的委托的,然后看到老赵说事件也 ... -
要让CLR挂掉的话(第二弹)……
2009-09-04 03:26 12872(Disclaimer:如果需要转 ... -
要让CLR挂掉的话……
2009-09-02 16:53 4776(Disclaimer:如果需要转载请先与我联系。 作者:Re ... -
趣味编程:函数式链表的快速排序
2009-08-31 08:53 3444(恢复自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 2598悲剧啊……前几天有个同学不停问我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 1635原文作者: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 4057在前一帖里,我用到了下面三处Expression.Call() ... -
看到一个关于ref参数与多态的问题,记一下
2009-05-18 10:48 1941刚才读到Alan McGovern的一帖,问为什么形式参数是r ... -
C#的+=运算符两例
2009-05-06 18:18 2032刚偶尔看到了justjavac写的java解惑 - 半斤八两( ...
相关推荐
在C#编译器处理委托时,会自动产生一个派生自System.MulticastDelegate的密封类,这个类和它的基类System.Delegate一起为委托提供必要的基础设施。我们可以使用ildasm.exe查看生成的委托类,发现它定义了三个公共的...
匿名方法也可以被封装在委托中,尽管这种方式在C#新版本中已不常用。 委托可以链接在一起,即可以将多个委托实例连接起来,形成一个委托链。当调用一个委托链时,链中的每一个委托都将依次被调用。委托链调用时,...
`展示了如何在派生类中直接调用基类的成员函数,而无需重新编写代码。 接口在C#中扮演着契约的角色,它定义了一组方法签名,但不提供实现。类可以实现多个接口,这体现了多态性。例如,`public class Graduate: ...
8. 在派生类中重新定义基类方法时,应保持与基类完全相同的方法名、参数列表和返回类型,这是方法的重写(Override),否则就是定义新的方法。 9. C#中所有异常类的基类是`Exception`,主动引发异常的语句关键字是`...
当用户点击按钮时,`button1_Click`方法会被调用,并且`sender`参数将会是`button1`对象本身,而`e`参数则是一个`EventArgs`对象(默认情况下,由于没有附加数据,所以使用基本的`EventArgs`类)。 #### 四、自定义...
- 派生类实例化:在`main()`方法中,`Derived dr_obj = new Derived()`创建了一个派生类的对象,然后调用`dr_obj.Base_fun1()`执行基类的方法,显示了继承的特性,即无需在派生类中重新编写基类的功能。 - **多态...
事件是类的一种特殊成员,用于声明特定情况的发生,而响应方法则是处理这些事件的代码。下面我们将深入探讨C#中事件和响应方法的工作原理。 首先,事件在C#中通过`event`关键字声明。例如,`public event ...
virtual在派生类中声明其实现可由重写成员更改的方法或访问器。 volatile指示字段可由操作系统、硬件或并发执行的线程等在程序中进行修改。 9,语句 语句是程序指令。除非特别说明,语句都按顺序执行。C# 具有下列...
当一个派生类想要使用基类的事件时,它通常会通过覆盖与事件关联的方法来实现这一目标。在我们的例子中,`MyBusiness`类通过重写`OnProgress`方法来实现对`ProgressEvent`事件的自定义处理。 ### 总结 C#中的事件...
抽象方法使得基类可以定义方法的签名,而具体的实现留给派生类完成。例如: ```csharp public abstract class Plant { public abstract void Grow(); } public class Flower : Plant { public override void ...
调用`DeriveClass`的`F`方法时,会先调用基类的`F`方法,然后执行派生类中的额外逻辑。 ### 隐藏(Hiding) 在C#中,如果派生类中有一个与基类同名的方法或字段,即使签名相同,也不一定会发生重写。这种情况下,...
由于代码未给出完整部分,这里假设`B`类的构造函数会调用基类的构造函数,因此输出结果将是"A",表示`A`类的构造函数被调用。 以上是对《C#程序设计》试卷中部分内容的详细解析,涵盖了字符串、引用类型、索引器、...
- **通过继承隐藏:** 派生类中的成员可以隐藏基类中的同名成员。 **3.8 命名空间和类型名称** - **命名空间:** 用于组织相关类型。 - **类型名称:** 类、接口、枚举等的名称。 - **完全限定名:** 包含命名空间...
如果基类没有默认构造函数,则派生类必须显式地调用基类的构造函数。 **示例代码:** ```csharp public class BaseClass { public BaseClass() { Console.WriteLine("BaseClass constructor called."); } } ...
如果一个方法被声明为`virtual`,那么在派生类中可以通过`override`关键字来提供不同的实现。 3. .NET与Java事件模型的区别: - .NET框架使用委托(Delegate)来实现事件模型。委托是一种类型安全的函数指针,可以...
3. **提供受保护的虚拟方法**:为了使派生类能够触发事件,基类通常会提供一个受保护的虚拟方法,如`OnProgress`,该方法实际上执行事件的调用。 4. **派生类重写**:在派生类中,可以通过重写基类的`OnProgress`...