RSS 2.0 Feed
Inside CLR
Understanding the .NET itself.
摘要:写Javascript的时候,经常会感觉到这个语言有些地方真的很爽,比如你可以写var f = function(){}; 然后把f当作变量到处传,如同C的函数指针;也可以直接写xx.onclick = function(){}; 这就是传说中的匿名方法了 C#的设计者似乎也赞赏这种syntax sugar动态语言的特性垂涎不已,在其2.0版本也推出了匿名方法的概念。我在想Javascript的那些pattern在C# 2.0能用上多少,所以一个一个的试验了下。 [ROUND 1] Javascript:var f = function() {}; C# 2.0:delegate void F(); // 这个没有办法,C#还得保持它的强类型语言的特性F f = delegate() {}; // 这里的f也可以当作一个函数指针传递给其他变量或者方法 Javascript:button1.onclick = function() {}; C# 2.0:button1.Click += delegate(object sender, EventArgs e) {}; // 这里不完全一致,=意味着覆盖了当前的委托链,+=则是在链表上添加了一个新的委托 [ROUND 2] Javascript:var msg = "hello";button1.onclick = function() {alert(msg); }; C# 2.0:string msg = "hello"; // 注意msg可以是某个方法内的局部变量,而不一定是某个类的成员button1.Click += delegate(object sender, EventArgs e) {MessageBox.Show(msg); }; [ROUND 3] Javascript:var msg = "hello";button1.attachEvent("onclick", function() {alert(msg); }); // JScript syntax, IE only C# 2.0:string msg = "hello";button1.Click += delegate(object sender, EventArgs e) {MessageBox.Show(msg); }; 看上去C# 2.0的这个特性和Javascript的不相上下,并且两者的行为非常接近。可是连他们的syntax trap都保持一致: [ROUND 4] Javascript:var msg = "hello";button1.attachEvent("onclick", function() {alert(msg); });msg = "!";button1.attachEvent("onclick", function() {alert(msg); }); // 点击button1时弹出两次"!" C# 2.0:string msg......[阅读全文]

posted @ | Feedback (19) | Filed Under [ Inside CLR ]

摘要:Anonymous Method - Lambda Expression Lambda表达式是C# 3.0的新特性之一,最简单的Lambda表达式,像这样的: Func<int,int> f = x => x + 1; 会被编译为一个方法: int f(int x){return x + 1;} 所以它和匿名方法的实现很类似,可以说是匿名方法的带类型推导的精简版本。和匿名方法一样,这是一种编译器行为;但是除了更“甜”的语法之外,C# 3.0进一步表现出了不少Functional Programming的特性,虽然还不是很“纯”(大概是受到强类型语言总思路的制约吧)。 和Lambda Expression相关的,C# 3.0还有个叫作Expression Tree的概念。这是它和Anonymous Method所不同的地方,Expression Tree不会被直接编译成一个实际的方法,而是编译成一个类似CodeDOM的语法树。比如: Expression<Func<int, bool>> exprLambda = x => (x & 1) == 0;// 请对比:Func<int, bool> nonExprLambda = x => (x & 1) == 0; 会被编译为: ParameterExpression xParam = Expression.Parameter(typeof(int), "x");Expression<Func<int, bool>> exprLambda = Expression.Lambda<Func<int, bool>>(    Expression.EQ(        Expression.BitAnd(xParam, Expression.Constant(1)),        Expression.Constant(0)),    xParam); 这个变量exprLambda是一个真正的方法,而不是一个方法指针(delegate/代理)。C# 3.0 Specification:“Following these assignments, the delegate f references a method that returns x + 1, and the expression tree e references a data structure that describes the expression x + 1.”它是方法体本身的描述,就像CodeDOM那样,通过代码描述代码,并且可以被编译、执行(不过这些动作都是透明的)。显然,和CodeDOM一样,你也可以遍历这个树,修改树结点,或者做一些别的特别的事情。这将使C#变得更加动态化(当然cost就是性能低一点点)。 *LINQ LINQ/DLINQ/XLINQ(以及YLINQ、ZLINQ等等什么LINQ)是基于Lambda语法的。不过这里也存在着大量的语法糖(OK,只要没有带来负面作用那这个词也不是贬义词),比如这句: from c in customersjoin o in orders on c.CustomerID equals......[阅读全文]

posted @ | Feedback (13) | Filed Under [ Inside CLR ]

摘要:啊,今天真的很闲,这是今天写的第三篇了,哈哈,超过了前面每月的平均水平。 VCKBASE的翻译团队刚刚把MSDN的一篇关于ManagedSpy的文章翻译了过来,我在这里直接引用一下,就不多说废话介绍了: ManagedSpy是一个诊断工具,和Spy++类似。它显示受托管的属性,允许你记录事件,并且是一个使用ManagedSpyLib的很好例子。ManagedSpyLib引入了一个称为ControlProxy的类。一个ControlProxy是一个System.Windows.Forms.Control的代表,允许你获得或设置属性并预定事件好似你正在目标进程中运行着一样。使用ManagedSpyLib来做自动化测试,为兼容性做事件记录、进程交互通讯,或白盒测试。 译文:http://www.vckbase.com/document/viewdoc/?id=1625原文:http://msdn.microsoft.com/msdnmag/issues/06/04/ManagedSpy/default.aspx下载:http://download.microsoft.com/download/f/2/7/f279e71e-efb0-4155-873d-5554a0608523/ManagedSpy.exe 截图: 很酷吧~ Managed Spy是开源的,它用C++/CLI封装了一个基础类ControlProxy(小小的展现了一下C++/CLI的NB之处),外加一个用C#写的UI(就是你看到的这个截图)。你只可以用Managed Spy来spy基于.NET 2.0的Windows窗体应用程序。 它的ControlProxy类还能用来做别的事情,除了察看别的进程里控件的属性和事件之外,我在想是不是可以利用它写一个记录UI操作的Recorder,这样连测试脚本都不用写了,Beyondsoft这样的公司有福了。...[阅读全文]

posted @ | Feedback (6) | Filed Under [ Inside CLR Patterns and Practice ]

摘要:在我前面的这个post,曾谈到AsyncOperation类的一个令我着迷的功能,那就是它可以把一个方法提交到另一个线程上执行。它也是实现BackgroundWorker的关键组件。 However,前几天在一个Console程序再次试验这个类时,发现了一个不同的行为,就是.Post方法提交的delegate运行于哪个线程是不确定的,具体的说: 在Windows程序,如果AsyncOperation类是UI线程创建的,那么delegate将在UI线程执行(这个符合预期) 在Windows程序,如果AsyncOperation类是非UI线程创建的,那么delegate将在一个“随机”的来自线程池的Background线程上执行 — 而不一定是创建AsyncOperation实例的那个非UI线程 在Console程序,同第二条 也就是说,运行于其它线程的AsyncOperation.Post方法并不总是把delegate提交到AsyncOperation实例的创建者线程。对应到BackgroundWorker类的ReportProgress方法,同样如此。按照FCL的设计,应该是这样的。这是其一。 其二,假如说AsyncOperation和BackgroundWorker类专门针对Windows程序而设计,那么第2、3两条应该归为我的应用程序的设计问题。但是,有人在这里提到,即便是Windows程序(满足上面的第一条),也不能保证提交的delegate一定在UI线程执行! 这里不是讨论线程并发冲突的问题,而是BackgroundWorker和AsyncOperation类是否能真正简化Windows程序中多线程的应用。这里提到的问题,我自己暂时还没碰到。但如果属实,那我估计会放弃AsyncOperation和BackgroundWorker类,因为这相对于原先的猜测:“这个类只是一个线程的简单包装,用多线程模拟了异步调用而已”,其结果没有什么两样。 我不反对这两个类的使用,只是在ReportProgress时,也要用Control.Invoke来更新UI控件,防止RPWT。 更深的原因是,AsyncOperation.Post方法的实现是未知的,我曾经想用Reflector找出答案,如上个post所述,未果。我不知道它究竟把delegate提交到了哪里。按照MSDN的说法:“Invokes a delegate on the thread or context appropriate for the application model.” — 轻描淡写。 在.NET 1.x的Threading ≈ Thread + ThreadPool + lock(syncRoot) + WaitHandle + Control.Invoke,我觉得基本上就是这套东西了;而在.NET 2.0,现在看来AsyncOperation.Post()这种新奇的做法最好还是慎用~ ----- update on 4/4/2006 看来我的担心变成了现实,这里有人重现了同样的错误。...[阅读全文]

posted @ | Feedback (3) | Filed Under [ Inside CLR Patterns and Practice ]

摘要:在前面提到,.NET 1.x提供的自定义序列化的solution很不方便。不过,IMPOSSIBLE IS NOTHING,我们可以写两个辅助方法来绕过这个问题。class SerializationHelper ...{ public static void Serialize(object obj, SerializationInfo info, StreamingContext context) ...{ MemberInfo[] members = FormatterServices.GetSerializableMembers(obj.GetType(), context); foreach(FieldInfo field in members) ...{ info.AddValue(field.Name, field.GetValue(obj), field.FieldType); } } public static void Deserialize(object obj, SerializationInfo info, StreamingContext context) ...{ MemberInfo[] members = FormatterServices.GetSerializableMembers(obj.GetType(), context); foreach(FieldInfo field in members) ...{ field.SetValue(obj, info.GetValue(field.Name, field.FieldType)); } } } 如果你已经看明白了我的意图,那就不必往下看了。 下面是利用了这个SerializationHelper的自定义序列化方案:[Serializable] class Person : ISerializable ...{ // public field public string Name = "Vista Xia"; // private field private int age = 23; //......[阅读全文]

posted @ | Feedback (3) | Filed Under [ Inside CLR Patterns and Practice ]

摘要:1、支持绑定到非公有方法的委托 关于这一点,与其说是个增强,在我看来这似乎是设计思路上略微的转变。具体地说,在.NET 1.x,像这样的代码:[Serializable] class Person ...{ public event EventHandler Birthday; } static void Main(string[] args) ...{ Person p = new Person(); p.Birthday += new EventHandler(p_Birthday); BinaryFormatter bf = new BinaryFormatter(); MemoryStream ms = new MemoryStream(); bf.Serialize(ms, p); } static void p_Birthday(object sender, EventArgs e) ...{ Console.WriteLine("Birthday is coming!"); } 会抛出一个这样的异常: An unhandled exception of type 'System.Runtime.Serialization.SerializationException' occurred in mscorlib.dllAdditional information: Serialization will not deserialize delegates to non-public methods. 提示信息是很明确的,它不支持绑定到非公有方法(non-public method)的委托。要解决这个问题,你得在p_Birthday方法前加一个public关键字。然而,对于类Person的设计者来说,他不知道事件Birthday将被绑定到什么样的方法之上,所以这带来了一定程度的不确定性。 .NET 2.0已经把这个行为改掉了,这段代码现在可以正常运行。这样,在做序列化/反序列化操作时,这增强了应用程序的稳定性。 2、可选字段增强了不同版本的应用程序之间的互操作性 .NET/FCL 2.0的System.Runtime.Serialization命名空间引入了一个新的名为OptionalFieldAttribute的特性类: Specifies that a field can be missing from a serialization stream so that the BinaryFormatter and the SoapFormatter does not throw an exception. This attribute allows you to specify that new fields in a serializable type (a......[阅读全文]

posted @ | Feedback (3) | Filed Under [ Inside CLR ]

摘要:昨天在尝试使用System.ComponentModel.BackgroundWorker时,发现这个类的行为和我预料的大不一样,可以说是惊喜。原来以为这个类只是一个线程的简单包装,用多线程模拟了异步调用而已;但是看下面的这段代码: Thread.CurrentThread.Name = "Main Thread";backgroundWorker1.RunWorkerAsync(); ... private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e){    int i = 0;    while (i++ < 100)    {        backgroundWorker1.ReportProgress(i);        Thread.Sleep(50);    }} private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e){    this.Text = e.ProgressPercentage + "% - " + Thread.CurrentThread.Name;} private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e){    this.Text = "DONE - " + Thread.CurrentThread.Name;} 毫无疑问,_DoWork方法是运行在另一个不同线程之上的(很容易验证这一点,这也符合BackgroundWorker的设计),而这个方法又调用了backgroundWorker1.ReportProgress方法,触发了ProgressChanged事件。在通常的异步实现,_ProgressChanged方法应该运行于事件触发者相同的线程中;但在这里,它运行于主线程(名为Main Thread的线程)。_RunWorkerCompleted方法也是一样。 在我看来,这个行为非常特别,实际上它也非常有用。这样_ProgressChanged方法体中操作UI控件的代码都无需使用Control.Invoke包装了,让程序的编写大为简化。而我真正感兴趣的是这个类究竟是怎么实现的,我用Reflector打开它的源码之后,原来关键在于它用到了一个名为AsyncOperation的类(System.ComponentModel.AsyncOperation)。 AsyncOperation类有个Post方法,可以用来把一个委托(作为方法指针/列表)提交给另一个线程执行。继续反编译下去,又查到了System.Threading.SynchronizationContext类。不过具体怎么实现是无从得知了,因为追踪到最后,停在了一个[MethodImpl(MethodImplOptions.InternalCall)]的方法,它由CLR本身实现。(我个人猜测,其中很可能利用了Windows API:Get/SetThreadContext,和结构体CONTEXT,改掉了线程上下文。) 退一步说,它怎么实现的并不是那么重要,重要的是我们可以用这个AsyncOperation类实现自己的BackgroundWorker。这里是我写的和上面代码基本等价的实现: AsyncOperation asyncOperation;SendOrPostCallback progressReporter;Thread workerThread; public MainForm(){    InitializeComponent();     asyncOperation = AsyncOperationManager.CreateOperation(null);    progressReporter = new SendOrPostCallback(ReportProgress);    workerThread = new Thread(new ThreadStart(WorkOnWorkerThread));} private void MainForm_Load(object sender, EventArgs e){    Thread.CurrentThread.Name = "Main Thread";    workerThread.Name = "Worker Thread";    workerThread.IsBackground = true;    workerThread.Start();} void ReportProgress(object obj){    this.Text = obj.ToString() + "% - " + Thread.CurrentThread.Name;} void WorkOnWorkerThread(){    int i = 0;    while......[阅读全文]

posted @ | Feedback (4) | Filed Under [ Inside CLR ]

摘要:DebuggerDisplayAttribute是.NET 2.0带来的一个新的功能,它可以让你定义一个对象在调试器中的文本表示。这个feature本来可以为调试带来很多方便,没想到弄得我郁闷了好多天 有了DebuggerDisplayAttribute,举例说,代码可以这么写:     [DebuggerDisplay("{Name}")]    class Person    {        string _name;        public string Name        {            get { return _name; }            set { _name = value; }        }    } 这样调试器里面显示Person对象时就会用它的Name属性显示,像这样: 不过... 你要是忘了给属性赋值,比如这里忘了给Name属性赋值,那调试器就是这个样子: 我就是在这里愣了好几天,这段简单的代码可能一看就知道是什么问题了;但我手上的一个东西偏偏就是给了个NullReferenceException,我单步跟踪时,居然发现C#的new操作符也返回了null!当然,这是被DebuggerDisplayAttribute骗了。而后来发现是因为别的原因抛出的NullReferenceException(所谓无巧不成书)。 后来还把我的代码发给在微软的一个朋友看(我心说用这个bug可以换个XBOX什么的~),不过到现在也没找到问题,似乎也被这个DebuggerDisplayAttribute骗了... ...[阅读全文]

posted @ | Feedback (0) | Filed Under [ Inside CLR ]

摘要:----- update on 12/19/2005 泛型解决了boxing/unboxing的问题,但System.Collections.Generic下面的集合类没有解决性能问题(这个性能问题不是boxing/unboxing带来的,是数据复制带来的)。这应该是更精确的描述(换句话说,前面我说的是不对的)。 所以不要把大量的值类型大对象放到集合里,这个.NET 1.x里面的适用的结论在有了泛型的.NET 2.0依然适用。(这个结论对于明白人来说是显而易见的。而我的这个post出于论坛,是为了纠正论坛里的一些错误言论,见回复) ----- update on 12/18/2005 原贴名为“C#范型的用处不是很大”,现在改为“C# 2.0泛型探讨”。 我原来确实认为C#泛型用处不会很大,因为我看了很多介绍泛型的文章,像这个,把C#泛型的好处主要归纳为这三点:类型安全、二进制代码重用、性能。更好的类型安全是显而易见的,但后面两个好处并不明显。 关于“二进制代码重用”,是建立在“不使用泛型就得为一个功能的实现写多个版本的代码”的假设上的,然而实际上用List Generic能完成的事情ArrayList也能做到,只是类型安全很难保证。这样最终还是类型安全的好处。 关于泛型的性能,你可以看到这个帖子引来了这么多的讨论,我现在觉得这个 问题得分应用场合而讨论。 对于用泛型实现的集合类,比如System.Collections.Generic namespace下面那些,范型可以提升一些性能,但仍然无法避免boxing/unboxing带来的性能问题,所以依然不可滥用值类型。  — 这是我认为泛型在性能上也无太大优势的主要原因。最后我总结道:我写这个blog并不是说范型没用,而是说范型可能没有很多人想象中的那么有用。特别是,不要认为范型能解决boxing/unboxing带来的性能问题。就这样。 而对于其它一些领域的范型应用,比如我在回复中提到的quick sort算法,泛型确实可以提高很多的性能,我的测试代码显示它提高了三倍的性能。  — 这些领域的泛型应用大概是Ninputer反驳我的主要原因。他这样总结道:使用“object来支持任意类型”的做法相比,能够消除unboxing/boxing,因此在使用object后unboxing/boxing在你的程序里影响了性能的时候,泛型能够帮助你避免它。 这两种应用场合完全不同,对范型性能问题的研究自然有不同的结果。所以在各自的应用场合下,我和Ninputer的结论都应该是正确的。(这大概反映了每个程序员的关注点都很不一样)我首先关注的是最常用和最重要的部分,对于.NET泛型,给程序员带来最大好处的肯定非System.Collections.Generic莫属。结果却是忽略了其他的一些方面。 但我原来的标题里面的大结论是错误的(原来我认为泛型用处不大。换句话说,就是.NET的泛型玩不出什么花样),Ninputer用他的VBF有力的证明了这一点。所以现在改了一下标题。同时为update之前的错误言论造成的误导而道歉。 ----- update on 12/17/2005 我的这段果然成功的让很多人不满意了 要完整地评估泛型在性能上的问题,可以做这样一个表来对比: Int32 LargeDataStruct LargeDataClass ArrayList 87.7 2808.7 76.9 List Generic 31.3 1903.1 70.5 Athlon64 2800+/Biostar NForce4/Apacer 512M DDR400 x 2 这是在我的机器上的测试,把数据装入集合再从集合获取数据,循环50w次得到的结果。注意横向的比较(标为红色的部分)。 所以我的结论是: 泛型总是能带来性能提升,但提升不大;或者说仅在int这样很小的数据类型上才有很明显的作用 泛型并不能解决boxing/unboxing的问题,大型的值类型,还是应该设计为引用类型 这回应该说清楚了吧 ----- original 前些天尝试让CSDN的.NET论坛增加一点讨论的气氛,于是发了这个帖子: C# 2.0会给我们带来什么 说来说去还是说到了泛型。但我觉得这个feature真的没什么,最起码没有想象中的那么有用,和C++模板实际上差别很大的,为什么说到C# 2.0就都要先说泛型。最后我这样回复: .NET的泛型,除了能用编译器更好的进行类型检查之外,别的地方用处不大。并且,性能提升并不明显,装箱/拆箱的过程,不要和类型转换相混淆。以装箱为例:int i = 5;object o = i;这里实际上做了两步操作,一个是把数据从线程堆栈复制到GC堆上,另一步是类型转换。这两步形成了装箱的操作。 泛型只能避免第二步,得到一点点性能提升;而最影响效率的地方却是第一步:把数据从线程堆栈复制到GC堆上。 C#的泛型也远不如C++模板那样灵活,要做“模板元编程”之类的高级用法,在C#根本就不可能。各位有兴趣的话可以比较一下C#和C++在这方面的差别。 PS.我这段大概要让人失望了,但这是事实,C#泛型被讨论的实在太多了,而实际上它的用处不大。其好处主要在编译时更好的类型检查,减少人为错误的可能性。 我目前能看到的好处仅在于“编译时更好的类型检查”而已 — 当然,这个好处也非常重要,但别的真的就没什么了。特别是,很多网友把C#泛型当作解决boxing/unboxing性能问题的药方,实际上根本就不是这样,这种错误的观点会带来更大的性能问题。 误导来自于这种测试代码:int i = 5; ArrayList list1 = new ArrayList(); list1.Add(i); i = (int)list1[0]; List<int> list2 = new List<int>(); list2.Add(i); i = list2[0]; 没错,后者的性能几乎要比前面的高100%。这让人产生了泛型可以解决boxing/unboxing性能问题的错觉,但实际上这是因为这里是小小的int,boxing/unboxing和类型转换的代价是差不多的。但是可以从反面证明我的观点: 假如泛型能解决boxing/unboxing性能问题,那么这两段代码的运行速度应该是大致相当的:struct LargeDataStruct ...{ decimal A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; } class LargeDataClass ...{ decimal A, B, C, D, E,......[阅读全文]

posted @ | Feedback (78) | Filed Under [ Inside CLR ]