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 customers
join o in orders on c.CustomerID equals o.CustomerID
select new { c.Name, o.OrderDate, o.Total }
等价形式为:
customers.Join(orders, c => c.CustomerID, o => o.CustomerID,
(c, o) => new { c.Name, o.OrderDate, o.Total })
所以*LINQ也是个编译器特性(与CLR无关)?
一切改变都很酷,也确实能提高开发效率,不过我唯一觉得不喜欢的是,C# 3.0一下子引入了这么多的新语法,它已经从一个短小精悍的语言变得越来越复杂了。。
Reference
C# 3.0 Language Specification
http://download.microsoft.com/download/5/8/6/5868081c-68aa-40de-9a45-a3803d8134b8/csharp_3.0_specification.doc
C# 3.0 and LINQ - Expression Trees
http://www.interact-sw.co.uk/iangblog/2005/09/30/expressiontrees
啊,今天真的很闲,这是今天写的第三篇了,哈哈,超过了前面每月的平均水平。
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这样的公司有福了。
在前面提到,.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;
// public property (no use in serialization)
public int Age

...{

get ...{ return age; }

set ...{ age = value; }
}
[NonSerialized]
public string Award;
// event/delegate
public event EventHandler Birthday;
public Person()

...{
Award = "MVP";
}
protected Person(SerializationInfo info, StreamingContext context)

...{
SerializationHelper.Deserialize(this, info, context);
Award = "MVP";
}
public void GetObjectData(SerializationInfo info, StreamingContext context)

...{
SerializationHelper.Serialize(this, info, context);
}
}

加上前面提到的background,就很容易理解了嗯。
注意:
这个不是微软官方提供的方案,我也不提供技术支持。我只能说这个应该是可行的,我自己做了不少测试也证明了这一点,但是,万一出了问题可不要怪俺。
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.dll
Additional 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 type to which the SerializableAttribute is applied to) are ignored by the BinaryFormatter or the SoapFormatter. This enables version-tolerant serialization of types created for older versions of an application that serializes data. For example, when the formatters encounter a stream produced by a version that does not include the new fields, no exception is thrown, and the existing data on the older type is processed as normal.
这个feature在新/旧版本的应用程序用.NET Remoting进行互操作时特别有用。新版本的类型可能增加了一些新的字段(而老版本没有),这样,对于旧类型的对象通过序列化得到的数据,当你试图通过反序列化生成新类型的对象时,会发生找不到类型成员的错误。现在,你可以用这个OptionalFieldAttribute特性类标记那些新字段,使新/旧版本的应用程序互相兼容。
需要注意的是,要使新/旧版本的应用程序互相兼容,这个OptionalFieldAttribute特性类只是提供了一个approach而已,它不保证忽略(缺少)一些字段后的对象仍然有正常的行为。显而易见,这就像在运行时突然把一个对象的某个私有字段改成null或0值,谁也不知道将会发生什么事情。所以,使用这个特性类时,还需要有设计方面的保证。
然而,要设计这样一个向后兼容的新类型,就算使用非常senior的程序员再加上非常细心的设计也很难保证不出错 — 特别是类型很复杂的情形。对我来说,不到万不得已是不会冒险使用这个OptionalFieldAttribute的。
3、序列化/反序列化事件可以让你更容易地自定义序列化行为
在.NET 1.x,要自定义序列化行为,你得让类型继承ISerializable接口(当然还是要应用[Serializable]特性),实现GetObjectData方法和一个反序列化构造函数,像这样:
[Serializable]
class Person : ISerializable

...{
public Person()

...{
}
protected Person(SerializationInfo info, StreamingContext context)

...{
}
public void GetObjectData(SerializationInfo info, StreamingContext context)

...{
}
}
这确实提供了自定义序列化/反序列化行为的途径,你可以在GetObjectData方法中向SerializationInfo手工添加任何数据(哪怕不是对象的成员),然后在反序列化构造函数中从SerializationInfo重新取出数据,再手工初始化对象。
这是一个完备的方案,但是有些情况下它未免有点麻烦。
根据我的experiance,需要自己实现ISerializable接口的情形,往往是因为“大部分字段可以自动序列化(比如int、string),而少数字段不支持序列化(比如Thread、WaitHandle),但这些字段对于对象又是必不可少的”的需求。也就是说,这些不支持序列化的字段在反序列化后仍不允许为空时,你得自己控制序列化/反序列化行为,在反序列化构造函数重新建立它们的实例。
这里的问题是,大部分字段都可以自动序列化的;但是为了少数几个不支持序列化的字段,却需要编写代码为所有字段都进行手工的序列化 — 这些代码不但冗繁,而且容易出错(比如忘掉了处理某个字段)。我一直希望能自己参与自动序列化/反序列化的行为,比如在自动反序列化之后,运行时能自动调用一段我自己的代码,处理那些不支持序列化的字段。
.NET/FCL 2.0满足了我的这个需求,它提供了四个序列化/反序列化事件:OnSerializing、OnSerialized、OnDeserializing、OnDeserialized。它们对应了四个加了Attribute后缀的特性类。这里绑定事件的方法不同于普通.NET事件:事件绑定是通过为方法应用特性来实现的(而不是普通的+=和-=)。不必再详细描述,直接看代码:
[Serializable]
class Person : ISerializable

...{
public Person()

...{
}
protected Person(SerializationInfo info, StreamingContext context)

...{
Console.WriteLine(".ctor(SerializationInfo, StreamingContext)");
}
public void GetObjectData(SerializationInfo info, StreamingContext context)

...{
Console.WriteLine("GetObjectData");
}
[OnSerializing]
internal void OnSerializing(StreamingContext context)

...{
Console.WriteLine("OnSerializing");
}
[OnSerialized]
internal void OnSerialized(StreamingContext context)

...{
Console.WriteLine("OnSerialized");
}
[OnDeserializing]
internal void OnDeserializing(StreamingContext context)

...{
Console.WriteLine("OnDeserializing");
}
[OnDeserialized]
internal void OnDeserialized(StreamingContext context)

...{
Console.WriteLine("OnDeserialized");
}
}
static void Main(string[] args)

...{
BinaryFormatter bf = new BinaryFormatter();
Person p = new Person();
MemoryStream ms = new MemoryStream();
bf.Serialize(ms, p);
ms.Position = 0;
p = bf.Deserialize(ms) as Person;
}
Console输出:
OnSerializing
GetObjectData
OnSerialized
OnDeserializing
.ctor(SerializationInfo, StreamingContext)
OnDeserialized
这里加了ISerializable接口只是为了演示事件发生顺序之用,一般情况下,既然已经使用了这四个事件,那这里是不需要这个ISerializable接口的。
下一篇,在.NET 1.x使用自定义序列化时的一个技巧:在.NET 1.x进行“自动”的序列化/反序列化,同时也让自定义代码自动参与序列化过程。
在我前面的这个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
看来我的担心变成了现实,这里有人重现了同样的错误。
昨天在尝试使用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 (i++ < 100)
{
asyncOperation.Post(progressReporter, i);
Thread.Sleep(50);
}
}
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骗了... 
----- 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, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z;
}
LargeDataStruct data1 = new LargeDataStruct();
List<LargeDataStruct> list1 = new List<LargeDataStruct>();
list1.Add(data1);
data1 = list1[0];
LargeDataClass data2 = new LargeDataClass();
List<LargeDataClass> list2 = new List<LargeDataClass>();
list1.Add(data2);
data2 = list2[0];
实际运行速度如何?完全是天壤之别,根据我的测试,前者所花费的时间是后者的100倍左右;测试可能不精确,但一两个数量级肯定是有的。
我觉得有必要把这个观点也发表到博客堂来,一来可以检查我的观点是否正确,二来可以让更多的人看到。
写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 = "hello";
button1.Click += delegate(object sender, EventArgs e) {MessageBox.Show(msg); };
msg = "!";
button1.Click += delegate(object sender, EventArgs e) {MessageBox.Show(msg); }; // 点击button1时也弹出两次"!"
肯定有人会认为应该都是先弹出"hello",再弹出"!",但事实不是这样。这里得特别注意。
测试环境:Windows XP SP2, IE6, VS 2005 RC1
---- updated?on 9/28/2005
修改了一些用词,确实有点misleading。
原来想在Part.2写一些编译器如何实现这种语法的背景信息,不过现在觉得没啥意思(对编程没什么帮助)。而Ninupter认为这个语法带来的动态特性的意义非常重大,我又不懂这个,所以接开心的tag游戏,把下一棒交给?
Refer to: http://www.theserverside.net/tt/articles/showarticle.tss?id=AnonymousMethods