RSS 2.0 Feed
灵感记录
摘要:在动态编程时,我们常常需要运行时确定调用对象的哪个属性或哪个方法。这个任务通常可以用反射来解决。但众所周知,反射的性能要比静态指定的方式低很多,因为反射要通过运行时复杂的机制完成。能否获得性能和灵活性兼备的动态调用?我在开发VBF的最新功能时反复考虑了这个问题。我们通常动态调用一个对象的属性是采用这样的手法,假设对象a有一个属性叫做MyProp: Type t = a.GetType();PropertyInfo pi = t.GetProperty("MyProp");string value = (string)pi.GetValue(a, null); 注意到什么问题了吗?我们知道这个属性的类型是string,也知道它没有参数。当然也有不知道即将调用的属性类型及参数的时候,但这个场合我们知道,却没有利用,还是当成什么信息都不知道一样使用纯动态的手法获取。这样我们就错失了能利用强类型特性加速这一过程的良机。同样还有方法调用,我们有时候只是方法或属性的名字在编译时不知道(比如需要用户指定),但方法或属性的类型及签名我们是知道的,这种情况下就可以用泛型和委托技术高性能地调用。 泛型技术为处理类型提供了方便,除此之外,.NET的委托还具有一些额外的良好特性。委托可以担当类似接口的任务,但与接口最大的不同就在于,方法无须声明自己满足某个委托,而只要签名符合,即可赋给委托变量。这样我们就可以利用一组事先声明的委托,处理千变万化类型的属性与方法。 C#不允许属性带有参数,除非是索引器。VB允许属性带有参数但很少有人真的大量使用。于是在真实世界中属性的getter和setter的形式就被限定了,绝大部分属性的getter和setter可以用以下两个委托表示: public delegate void PropertySetter<T>(T value);public delegate T PropertyGetter<T>(); 有了这两个委托,我们就可以对已知类型但名字需要动态化的属性进行高速的强类型动态访问了。方法是使用反射获取属性Gettet或Setter的MethodInfo,再使用MethodInfo创建委托: Type t = a.GetType();PropertyInfo pi = t.GetProperty("MyProp");MethodInfo getter = pi.GetGetMethod();PropertyGetter<string> strPropGetter =   (PropertyGetter<string>)Delegate.CreateDelegate(    typeof(PropertyGetter<string>), a, getter);string value = strPropGetter(); 注意,这个方法在调用前进行了更多反射操作,因此,如果你只想一两次地获取属性的值,这种方法还不如直接用放射来的快。但是,当你需要对同一属性进行成千上万次访问时,绝对值得多写这点代码,在string类型的简单属性上,速度可比直接反射获取最多快达1000倍,这是我实测的结果。 接下来我们讨论有index的属性和方法的调用。C#尽允许在索引器的语法上使用属性参数,而在VB看来,索引器不过是类所有带参数的属性中比较特殊的一个,他得到了在对象上使用数组语法访问的特权。不管怎么说,无论是索引器还是普通带参数的属性,他们的getter和setter过程都不像典型属性那样简单。同样还有对方法的调用,方法的签名千变万化,似乎我们很难用预先定义的委托统一进行调用。事实的确如此,不过与针对每一种属性访问器或方法的签名定义一种委托的做法相比,泛型还是给出了一种稍微舒服一点的做法: public delegate R Func<R>();public delegate R Func<T0, R>(T0 a0);public delegate R Func<T0, T1, R>(T0 a0, T1 a1);public delegate R Func<T0, T1, T2, R>(T0 a0, T1 a1, T2 a2); 这样一组泛型委托,可以涵盖参数数目从0-3,有返回值并且没有参数是out或ref的所有方法签名。你还可以定义一组用于无返回值的。有了这样一组泛型委托,就可以在想要某种函数的签名时直接创建出来,而无须声明新的类型。再结合刚才的手法,就可以用统一的手法实现大部分带有参数的属性或方法的动态调用——同时获得动态名称和强类型性能的双重好处。 也许你早已经利用了类似的手法,并用于除了动态调用属性或方法以外的其他任务。我只是在开发VBF时想到了他们,希望能对部分需要的人有所帮助。...[阅读全文]

posted @ | Feedback (7) | Filed Under [ 技术随笔 灵感记录 ]

摘要:除了J#外,所有微软支持的.NET开发语言现在均支持运算符重载,因此纯粹为C#简化写法一样特性现已成为一种.NET开发中值得研究的一项重要语言特性。有人认为运算符重载其实就是简化写法,满足模拟基本类型操作的小功能,没有必要深究。但我觉得要多思考一层,为什么我们总希望模拟基本类型的操作?因为运算符重载能够将操作中缀化,能够自动推测静态过程的主体。 首先是操作中缀化。函数调用其实是一种前缀操作,函数(代表操作)总是在参数(代表操作数)之前写出。这样执行序列操作时运行的顺序其实和书写的顺序相反: H(x,y)G(H(x, y), z)F(G(H(x, y), z), w) 序列运行的顺序是H->G->F但是却要反过来写,二元参数距离函数名越来越远。我们按照计算机执行的顺序思考,却要反过来写,多少有些不爽。成员函数与扩展方法的写法则是将操作数(对象)写在前面: x.H(y)x.H(y).G(z)x.H(y).G(z).F(w) 这样就将书写的顺序正过来了。这是一个甚好的方案,但是在不具备扩展方法的今天,有些事情是成员函数做不了的。比如在我的VBF里,我希望Functor<T, bool>可以进行And, Or等逻辑运算,而Functor<T, int>之间只能进行算术运算,Functor<T, string>之间只能进行连接运算,而且规则还不一样……但是成员函数没有根据类型参数选取不同重载的能力,也就是说.NET泛型无法进行特化操作。在.NET中具有编译期类型判定的机制只有两个:函数根据参数类型的重载和用户自定义隐式转换(相当于根据返回类型重载)。我们可以用Functor<,>类型的静态方法来实现根据类型参数不同的不同重载。但是静态方法不但要写全类型的名字,还是前缀操作,使用起来让人甚为不爽,这时就会发现,运算符重载是我们梦寐以求的东西。 Type.op_Operator(x, y) '静态方法x op y '运算符写法 以上两种是等价的,可以看到运算符重载不仅可以通过x,y的类型推测静态方法的调用主体Type,还可以将操作转化为中缀写法——比后缀更适合表现二元运算。既然这么完美,我们能不能这样写呢? Class Functor(Of T, U)    Public Shared Operator And(x As Functor(Of T, Boolean), y As Functor(Of T, Boolean)) _        As Functor(Of T, Boolean)    End OperatorEnd Class 很遗憾,这样会编译错误。作为运算符重载过程,其参数至少有一个必须是定义运算符的类型。在编译器看来,必须是Functor(Of T, U),两个类型参数都必须是该泛型类定义的。就在我对此大感抱怨时,我偶然在C#编译器的源代码(见Rotor)中看到了它识别运算符的规则,其中并没有这些限制,只有两条规则——方法必须是静态的,特定名称的方法;方法必须带有specialname属性。那么我们完全可以骗过编译器,不用它提供的Operator关键字来声明运算符重载过程,而是使用自己编写特定名称的方法,并加以specialname的手法来打造运算符重载过程: Imports System.Runtime.CompilerServicesClass Functor(Of T, U)    <SpecialName()> _    Public Shared Function op_BitwiseAnd(x As Functor(Of T, Boolean), y As Functor(Of T, Boolean)) _        As Functor(Of T, Boolean)    End FunctionEnd Class System.Runtime.CompilerServices.SpecialNameAttribute是一个指示编译器为声明成员添加specialname的特殊属性,C#和VB编译器都支持。op_BitwiseAnd是VB和C#等语言所识别的与操作运算符过程名称。这样写完以后编译成类库,再以引用DLL的方式引用它,你就会看到编译器将他识别成了我们要的运算符重载过程。当你在Functor<T, int>这样的类型上使用And操作时,编译器会告诉你不支持该运算符,仅在Functor<T, bool>上才能进行这一操作,编译错误信息准确无误,真是太棒了。 在我们结束前,我们还可以看看如此手工打造还能突破哪些编译器人为的限制: 可重载Protected和Private的运算符(尽管这样做几乎没有意义)可不成对重载比较运算符(=, >, >=, <=, <, <>)可以让移位运算符的第二个操作数不是int(>>和<<样子很好看,但是有了这个限制我们就不能拿它来干别的事情,现在好了)可以在C#中重载仅VB支持的运算符,也可以在VB中重载仅C#支持的运算符(当然要到对方语言中才能生效)可以让用户自定义显式转换支持泛型类型参数之间更加神奇的写法用了这种手法,似乎还可以重载诸如operator+(int, int)之类的运算符,但它们并不能生效。 .NET语言编译器中每一项特性,都可能有隐藏在其表面之下的深层次用途。善加研究后常能发现原来所认识不到的功能。我当然不是在推荐大家乱用运算符重载,只是一种思考,一种新的灵感。...[阅读全文]

posted @ | Feedback (8) | Filed Under [ 技术随笔 灵感记录 ]

摘要:我在介绍Visual Basic 9.0的时候,曾经多次提到Tuple这个概念,当时是作为匿名类型的实例出现的。现在我们单独来讨论一下这个概念。Tuple常常译为“组元”,在大部分支持Tuple的语言中,常常表示成员数目确定,每个成员类型也确定的结构。常常用于表示函数的多个返回值或者查询的结果等。Tuple应当是强类型的,即所有成员的类型在编译时确定。比如,假想语法下 Dim t = New Tuple(Of String, Integer, Double) 那么t将具有三个成员,该数目无法改变;同时三个成员的类型分别为String, Integer和Double,也无法改变。如你所见,Tuple可以看作不用事先声明的结构体,可以根据所使用的场合灵活地创建。那么VB9和C#3的匿名类型当然是Tuple很好的实现方案。但是这都是N年后的东西了,我们在.NET 2.0中能否实现Tuple?最关键的难点在于,我们要在希望使用的地方创建Tuple的结构,而不是事先声明,因此就必须有个灵活的机制来完成。 方法一:TypeList 我是某一天在公共汽车上想到这个办法,后来看到和Loki的TypeList有相似之处。当然.NET没有特化和记录类型的能力,所以无法实现TypeList。但我们把静态类型运算的思路移到运行时,就可以做Typed Variable List——那就是Tuple。 public abstract class TypeNode { internal TypeNode {} }public sealed class Tail : TypeNode { }public sealed class Tuple<T, TNode> : TypeNode where TNode : TypeNode, new(){    public T Field = default(T);    public TNode Next = new TNode();} 我充分利用了.NET泛型的约束特性来达成我的设计。TypeNode被设计为abstract,因此约束了new()的泛型参数TNode将无法取值TypeNode本身的类型。而其internal的构造函数又限制了用户继承于它。这个手法就将TNode的取值范围限定在Tail和Tuple两个类型上。这个用法是我认为约束用法中相当巧妙的一种。 这个类型的原理很简单,就是利用泛型,在创建TypeList的实例时自动生成相同结构的链表。比如我们要创建一个String, Integer, Double的Tuple,就是这样写: Tuple<string, Tuple<int, Tuple<double, Tail>>> t; 如你所见,这种Tuple的类型参数第一个是某节点的类型,第二个要么是另一个Tuple,要么是Tail(表示终结列表)。这个对象创建出来以后就会自动生成一个“各个节点类型都不相同”的链表。 t = new Tuple<string, Tuple<int, Tuple<double, Tail>>>();t.Field = " a string ";t.Next.Field = 123;t.Next.Next.Field = 13.56; Tail没有Next字段,因此遇到Tail就代表Tuple终结了,这可以由编译器检查,因此没有越界的危险。而且这种Tuple可以达到无限长。不过这种方法也是有缺陷的,首先使用的语法方面非常不便,如果要用第7个字段,要写成myTuple.Next.Next.Next.Next.Next.Next.Field,稍不注意就会写错。无论VB还是C#都没有足够的抽象能力简化这一操作。第二个缺陷是建立Tuple时的一连串new操作开销很大,因为这里的new是通过反射进行的。所以受限于语言特性的缺乏,这种方法无法达到很完美的地步,不过这个思路也许在其他场合可以用上。 方法二:重载原型 模仿泛型委托的思路,我们可以用完全泛型化的一系列同名结构来模拟即时创建的Tuple: struct Tuple<T0>{    public T0 Field0;}struct Tuple<T0, T1>{    public T0 Field0;    public T1 Field1;}struct Tuple<T0, T1, T2>{    public T0 Field0;    public T1 Field1;    public T2 Field2;}......struct Tuple<T0, T1, T2, T3, T4, T5, T6,......[阅读全文]

posted @ | Feedback (7) | Filed Under [ 技术随笔 灵感记录 ]

摘要:我们都知道.NET Framework 2.0支持了泛型,在它给我们带来性能收益和强类型的方便的同时,由于接口、委托等元素对泛型的支持,一些前所未有的泛型设计和算法也逐渐展现出来。 C# 2.0支持了一种叫“匿名方法”的特性,它与泛型委托结合之后,能够产生出许多诱人的新用法。比如,若想找出列表中所有大于10的元素,只需要:List<int> l1 = new List<int>(); //populate l1 List<int> l2 = l1.FindAll(delegate(int i)...{ return i > 10; }); 对于首次接触这个新特性的人来说,此语法较为费解。我们看看Generic List类的FindAll方法的定义:public List<T> FindAll( Predicate<T> match ); 我们看到,唯一的参数match是“Predicate<T>”类型的,那Predicate<T>又是什么呢,请看定义:public delegate bool Predicate<T>( T obj ); 这是一个委托,接受T类型的一个参数obj,并返回一个布尔类型,表示对obj的一个判断。现在我们就明白了,FindAll方法本应接受一个函数委托作为参数,以表示查找满足判断的所有元素,而C#的匿名方法则提供了一种就地提供函数委托的方法,使得委托的逻辑可以在同一条语句中表达。 除了Generic List,System.Array和许多其它泛型容器都提供了这种接受泛型委托的方法,在支持匿名方法的C#中,当然如鱼得水,发挥最大的作用。而VB(和多数其它的.NET语言)不支持匿名方法,在使用此类接受泛型委托的方法时,就必须单独创建一个函数,大大削弱了可读性,使用此类方法的优势也就不复存在。为了弥补此缺陷,让VB也能享受这类方法的便利,我们提出“谓词”的概念。其实“谓词”就是“判断”的一个更加名词化的说法。我们试图将常用的判断函数封装成类,使得他们可以被反复重用。首先定义一个封装Generic System.Predicate委托的类:UnaryPredicate(Of T)作为一元谓词的基类。Public MustInherit Class UnaryPredicateClass UnaryPredicate(Of T) Public Shared Widening Operator CType(ByVal predicate As UnaryPredicate(Of T)) As Predicate(Of T) Return AddressOf predicate.PredicateProcedure End Operator Protected......[阅读全文]

posted @ | Feedback (6) | Filed Under [ 技术随笔 灵感记录 ]

摘要:  大家已经很熟悉C# 2005将加入内置的重构支持,方便用户快速对代码实施常用的重构。重构是十分重要的维护代码的手段,不仅C#的用户需要,VB的用户更加需要。因为从VB6升级来的代码,经过升级向导的“蹂躏”后已经面目全非,而且许多旧的代码风格与新的.NET特性格格不入。因此,重构成了升级旧代码,改善旧代码的重要手段。可惜的是,除了重命名之外,VB2005并没有加入重构特性,这让VB的程序员十分扫兴。好在微软承诺下一个版本支持重构的同时,还委托第三方软件开发商专门为VB2005提供了重构插件,VB2005的用户可直接免费下载安装,这就是Refactor! for VB2005。Refactor! Pro是由Developer Express Inc开发的VS插件,可用于所有VS.NET版本和编程语言,而Refactor! for VB2005则只支持Visual Studio 2005和Visual Basic语言,功能也比完全版的Refactor! Pro来的少。但是,它的功能却已经超过了VC# 2005所内置的重构功能。从今天起,我就开始介绍Refactor! for VB2005所提供的重构功能。

Refactor!支持Visual Studio 2005独特的智能标记,即将重构功能置于上下文的智能标记中,而不是在一个“重构”菜单选项中提供选择。这样用户更容易知道在当前的代码上可进行什么样的操作,而不是从一堆不知所措的重构名字中选取。这类智能标记通常要等用户将输入光标置于可重构的对象上才会出现,如子程序或函数名称、变量名称等等。 重新排列参数是Refactor!多项重构功能之一,它可以将函数或子程序的参数重新排列。更神奇的是,他可以将按引用传递的参数转化为函数的返回值,或进行相反的操作。比如有这样一个子程序:   Public Sub Sub1(ByVal a As Integer, ByVal b As String) '调用方 Call Sub1(10, "str") 通过“重新排列参数”可以将上述程序重构成:   Public Sub Sub1(ByVal b As String, ByVal a As Integer) '调用方 Call Sub1("str", 10)   再比如有这样的子程序:   Public Sub Sub2(ByRef x As Integer) '调用方 Dim i As Integer Call Sub2(i)   同过“重新排列参数”可以将按引用传递的参数移出而变成返回值:   Public Function Sub2()As Integer '调用方 Dim i As Integer i = Sub2()   还可以进行相反的重构,将函数的返回值变成按引用传递的参数。有许多VB程序员习惯用参数传出所需要的参数。现在你可以用这个重构功能项,将合适的子程序转变成函数,或进行相反的操作。 进行这项操作的步骤是:1、先保存和编译项目。2、将光标放在函数的定义上,点击出现的智能标记,并选择“Reorder Parameters”。3、按Tab键,选择要移动的参数或返回值,然后按左右方向键移动这个参数,到合适的位置。4、按Enter键确定修改,接下来根据提示将函数所有的调用处更改为新的签名。5、根据需要,进行Rename Local重构,将此重构引入的变量名更改为适当的名字。下面的动画演示了“重新排列参数”重构进行的过程,交换参数的视觉效果确实很酷!(速度较慢请包涵) 重命名本地变量是另一项非常有用的功能。实际上VB2005已经内含安全的重命名功能,但重命名本地变量功能可以将重命名的范围限定在当前的函数或子程序中,同时明显的视觉效果可以让你对被重命名的变量一目了然。操作步骤是:1、选择要重命名的符号,单击智能标记并选择“Rename Local”。2、在绿色的可替换区域内输入新的名称,按Tab键可在所有出现该名称的区域间跳转,可借此观察此项重构的影响范围。3、确定后,按下Enter键。很简单。 ...[阅读全文]

posted @ | Feedback (17) | Filed Under [ 技术随笔 灵感记录 ]

摘要:前一段时间我介绍了泛型,其中有个很重要的特性叫“约束”。使用约束,可以减小泛型类型参数的取值范围,同时能够允许在泛型类型上应用所约束类型的功能。许多人为了在泛型类型上进行所需的操作,约束大量的类型。事实上,我觉得没有特别要求,泛型的类型参数应该尽量不约束任何类型,因为它会严重缩小你的泛型类的使用范围。而在类型参数上进行操作着一需求,并不是只能通过约束来达成。 规则:当你希望对类型参数实施的操作只用于你的泛型类一部分功能时,用辅助对象代替约束。 比如你希望编写一个容器类,它有很多功能,其中一个是排序。排序需要对象之间进行比较。如果容器类的其他功能并不依赖于排序,那么对整个类的类型参数使用约束就不合适。因为我们有可能根本不用排序,为什么还要有此约束呢。比如你将类写成: Public Class MyContainer(Of T As IComparable(Of T))    Public Sub Sort()        Dim a, b As T        If a.CompareTo(b) Then            'code        End If    End SubEnd Class 那么T所能取值的范围就大大减少了。我们不妨让用户在需要排序的时候,自行指定比较的依据,而不是强迫类型自己必须能够比较大小。改动成这样: Public Class MyContainer(Of T)    Public Sub Sort(ByVal compareHelper As Comparison(Of T))        Dim a, b As T        If compareHelper(a, b) Then            'code        End If    End SubEnd Class 这里,compareHelper就充当了辅助对象的功能。辅助对象是独立于类型T之外的,因此T不需要进行任何约束。而用户则必须为该功能提供正确的辅助对象。将辅助对象设置为委托是相当好的,这样用户可以只编写一个方法来规定辅助对性的功能(而不用编写一个完整的类)。C#用户甚至可以用匿名方法就地指定辅助对象的功能。.NET Framework为我们头提供了几个常用的泛型委托,都可以用作辅助对象的类型: System.Action(Of T) '是一个无返回值的方法,用于进行作用于一个T对象的动作 System.Comparison(Of T) '用于比较了两个T类型的对象 System.Converter(Of T, U) '用于指定将一个T对象转成U类型的规则 System.Predicate(Of T) '用于指定T类型的一个对象是否符合某项判断 当然,我们很容易就可以指定自己的辅助对象类型。不仅仅是委托,还可以是更加复杂的功能。用辅助对象代替约束,可以确保你的泛型类型有最大的应用范围,同时能让用户对特定的功能做深层次的定制。那什么时候用约束?当你的类确实只对某一类对象起作用,换句话说,你想要类型参数具有的功能贯穿整个泛型类的设计,那么约束就是更好的选择了。...[阅读全文]

posted @ | Feedback (3) | Filed Under [ 技术随笔 灵感记录 ]

摘要:最近灵感之源大哥常常在MSN中向我倾诉移植VB6代码的痛苦过程。我一看他的代码——好家伙,这都是原先在VB6种最高深的用法。什么不安全的指针啊,复制内存啊,一应俱全。调用平台的时候指针是少不了的,而VB.NET又不支持,所以麻烦常来,确实让人感到痛苦。我建议他用C#或者MC++,可是他愿意更纯的VB,那只好我来做了。为了能够让指针操作得以在VB中进行,必须对他们进行封装,泛型在这里是个很好的选择。而C#根本不允许对泛型的类型参数使用指针,那我们只有C++/CLI了。 C++/CLI支持两种托管的指针:内部指针和顶指针。内部指针(stdcli::language::interior_ptr)是一个指向托管堆中对象的动态指针。当托管堆中对象发生移动时,内部指针可以同步得到更新。内部指针具有很到的灵活性,他可以和C++本地指针进行类型转换,还可以进行指针运算。定指针(stdcli::language::pin_ptr)也是在一种指向托管堆对象的指针,被定指针所指的对象将被“定”在内存中,CLR不能移动它在内存中的位置,因此定指针可以用来向非托管的代码传递托管对象的指针,它能确保非托管代码操作的时候对象不会移动。最令人欣喜的就是,无论是定指针还是内部指针,他们都能对泛型的类型参数进行操作。所以我们可以封装一段代码来操作指针: ref class PointerHelper sealed{    private:    PointerHelper(void)    {    }    public:    generic<typename T> where T : System::ValueType    static T GetValue(IntPtr address)    {        return *(interior_ptr<T>)(void *)address;    }    generic<typename T> where T : System::ValueType    static void UnsafeSetValue(IntPtr address, T value)    {        interior_ptr<T> ptr = (interior_ptr<T>)(void*)address;        *ptr = value;    }    generic<typename T>where T : System::ValueType    static IntPtr UnsafeGetAddress(T %value)    {        pin_ptr<T> pp = &value;        return IntPtr((void*)pp);    }}; 我也不太确定这段代码会是什么效果,来试验一下。这是一段VB的代码: Dim x As IntegerDim p As IntPtr = PointerHelper.UnsafeGetAddress(x)PointerHelper.UnsafeSetValue(p, 123)MsgBox(x.ToString) 运行下看x的值,真的变了。相信有了这个,灵感之源所遇到的痛苦应该可以减弱一点了吧。至少从指针里获取指的操作不需要复制内存来做了。以前无法获取变量的地址现在也可以做到了。灵感之源只需要耐心等到VS2005发布,就可以轻松使用这段代码,逃离苦海了。 不过……这样使用终归是不安全的。VB对指针的类型没有任何检查和约束,我也没办法把这个东西做成强类型的。如果有人愿意,他完全可以这样写 Dim x As LongDim p As IntPtr = PointerHelper.UnsafeGetAddress(x)PointerHelper.UnsafeSetValue(p, 123.0F)MsgBox(PointerHelper.GetValue(Of Guid)(p).ToString) 这完全是可以运行的,把123.0F的浮点数塞到整型变量的地址里,然后按照Guid取出……不难想象若这种东西滥用起来,就会再次恢复到VB6那种“大师级”无法阅读、维护的代码了。...[阅读全文]

posted @ | Feedback (9) | Filed Under [ 技术随笔 灵感记录 ]

摘要:用Enum的时候,可能会有要遍历Enum中所有已定义值的功能。而当前各种.NET语言都没有提供对此需求的语法支持。我们只能用Enum.GetValues来实现这个任务。但是,Enum.GetValues一来是要用到运行时类型信息,让人不爽;二来得到的数组不直接是所需枚举类型的,需要转换。最好有一种强类型的语法可以帮助我们做这件事。所以,我就写了这个简单的小程序。其实内部还是得用Enum.GetValues,但是只在第一次使用时调用,后面就将这个取得的值集合保存起来,即使多次使用也不会担心性能受损。这个程序用C++/CLI BETA1写成,不适用于C++/CLI CTP或Tools Update。用C++的原因是,C#和VB.NET都对泛型约束语法做了过分的限制,让我写不了这个程序…… 首先是ValueCollection类: generic<typename T> where T : System::Enum[DefaultMember("Item")] //让Item属性成为默认属性(C#中的索引器)public ref class EnumValueCollection : public IEnumerable<T>{    private:    initonly array<T>^ values;    IEnumerator<T>^ enumerator;    protected:    virtual IEnumerator<T>^ GetValuesEnumerator() = IEnumerable<T>::GetEnumerator    {        if (!enumerator)        {            enumerator = ((IEnumerable<T>^)values)->GetEnumerator();        }        return enumerator;    }    private public: // internal: in future version    EnumValueCollection(void)    {        values = (array<T>^)Enum::GetValues(typeid<T>);    }    public:    property T Item[] //参数化属性    {        T get(int index)        {            try            {                return values[index];            }            catch(Exception^ e)            {                throw gcnew ArgumentOutOfRangeException("index",                     "This enum does not contain a value of this index");            }        }    }}; 接下来要有一个维护ValueCollection实例Singleton的帮助类: generic<typename T> where T : System::Enumpublic ref class EnumHelper sealed{    private:    EnumHelper()    {        //不能创建此类的实例   ......[阅读全文]

posted @ | Feedback (5) | Filed Under [ 灵感记录 ]

摘要:用过VB6的人都会对控件数组念念不忘,因为控件数组在处理多个控件统一事件上确实很方便。.NET Framework没有引入控件数组这一概念,这是因为.NET Framework的类型系统很完善,可以实现控件数组原来的功能。只是这样凭空增加了一些麻烦,我们不是需要在Handls字句后面写一长串控件名称,就是要用写数遍AddHandler或C#的+=语句给多个控件绑定同一个事件。为了减轻没有控件数组日子的痛苦,我写了这个替代方案:共享事件容器。在编写它的过程中,我用了泛型,但其实不用泛型也可以做到,只是泛型能够约束容器内控件的类型,减少可能的运行时错误。 在看实现方法之前,先来看看共享事件容器是怎么操作的。首先你先要放一些相同类型控件到窗体上,这些就是你的控件数组元素了,假设是button1,button2和button3。然后在窗体的构造函数或Load事件中添加如下代码: ba = New ShareEventsContainer(Of Button) '创建一个共享事件容器ba.AddRange(New Button() {Button1, Button2}) '用AddRange可以添加多个控件ba.Add(Button3) '当然Add方法也是支持的了 这就是共享事件容器了,就像是一般的集合一样,你可以用数组的语法操纵其中的对象。接下来就是重头戏了,我们要将容器内的全部控件的Click事件都绑定到同一个方法上,比如这个方法 Public Sub Buttons_Click(ByVal sender As Object, ByVal e As EventArgs)    MsgBox("Hello! I'm " & CType(sender, Button).Name)End Sub 则接着在刚才初始化共享事件容器语句的后面写事件绑定代码: ba.AddHandler("Click", New EventHandler(AddressOf Buttons_Click)) 有点像VB的语法,其实我就是取了个神似。这里事件名称是要用字符串表示的,可别写错了。而事件处理程序的委托也与一般的事件绑定语句不一样,这时必须将委托类型写出来,还得写正确了。VB和C#2.0的委托类型推定在这时是不起作用的。你还可以写更多的AddHandler,绑定其他事件。 好了,就这么简单,事件已经绑定好了。更进一步的是,将来继续往这个容器内添加新的按钮,他们的事件也会自动绑定到这时预定的所有处理程序上,而从容器中移除控件,其事件会自动解除绑定。你还可以在任意地方用AddHandler和RemoveHandler方法添加和删除新的事件处理程序,支持多播事件方法。 如果你觉得这个方法值得用用,那就可以看下面的实现代码了:VB2005 BETA1 Imports System.Collections.GenericImports System.Reflection'包含委托的列表类型Imports DelegateList = System.Collections.Generic.List(Of System.MulticastDelegate)''' <summary>''' 提供一个共享事件容器,容器内的控件可以共享事件处理过程''' </summary>''' <typeparam name="T">控件的类型,不支持Menu</typeparam>''' <remarks>''' 共享事件容器在事件处理上类似于Visual Basic 6.0或更早版本的控件数组,''' 它可以将多个控件的事件绑定到同一个处理过程上,方便进行功能一致的操作。''' </remarks>Public Class ShareEventsContainer(Of T)    Inherits Collection(Of T)    '保存当前共享事件容器所预定的全部事件    '为了支持多播事件,必须采用这种结构的存储方式    Private events As Dictionary(Of String, DelegateList)    Private controlType As Type    ''' <summary>    ''' 初始化一个默认的共享事件容器    ''' </summary>    Public Sub New()        MyBase.New()        controlType = GetType(T)        events = New Dictionary(Of String, DelegateList)    End Sub    ''' <summary>    ''' 初始化一个共享事件容器,并添加初始的控件    ''' </summary>    ''' <param name="controls"></param>    Public Sub......[阅读全文]

posted @ | Feedback (15) | Filed Under [ 灵感记录 ]

摘要:记得在Framework 2.0还叫1.2的时候,我曾经进行过几个简单的实验,以便测试对带有约束的泛型类中,类型参数对象的方法调用的性能。其结果令我非常失望——类型参数对象的方法调用比用接口调用甚至通过Object传递的方式都慢得多,泛型的优势完全没有体现出来。到了BETA1,我又可以重复这个简单的实验,结果却大大不同了。这是我的四个实验方法: Private Sub Test1(Of T As ITest)(ByVal obj As T)    obj.Test()End Sub Private Sub Test2(ByVal obj As ITest)    obj.Test()End Sub Private Sub Test3(ByVal obj As Object)    DirectCast(obj, TTest).Test()End Sub Private Sub Test4(ByVal obj As TTest)    obj.Test()End Sub 其中用到的结构及接口定义如下: Private Interface ITest    Sub Test()End Interface Private Structure TTest    Implements ITest     Public i As Integer     Public Sub Test() Implements ITest.Test        'no code    End SubEnd Structure 很简单,第一个方