RSS 2.0 Feed
2006-01 Entries
摘要:注意:本文版权所有,不得擅自转载 泛型不仅能用来做容器,还能够提供代码复用的手段。在泛型的参与下,许多设计就可能更精妙,更具扩展性。今天我就来演示一个利用泛型增强的抽象工厂模式。我们知道,抽象工厂(Abstract Factory)模式是将工厂和产品全部抽象化,一个抽象工厂生成一组抽象产品,而一个具体工厂则生成具体产品的一个特定组合。它能够维持这种相关对象组合的一致性,并使得用户不需要了解工厂和产品的具体实现。传统的Abstract Factory主要弱点是类型依赖性强,可复用性弱。一个抽象工厂通常是为一个特定需要而设计,通常不能被其他需要抽象工厂的场合使用。而且尽管抽象工厂可以将实际生产任务委派给特定类型的工厂,但这种委派是需要通过自己纯代码实现的,没能利用语言所提供的抽象特性。我们今天的任务就是编写一个不针对特定产品类型和数目的泛型抽象工厂,当你需要特定的抽象工厂时,可随时复用无需再定义专门的抽象工厂实现。 我们首先从只生成一个产品的工厂方法开始,以这作为抽象工厂的原料。很明显,可以设计这样一个接口作为工厂方法的模板: public interface IFactory<T> {     T Create(); } 这个工厂生产一个T类型的对象。当你实现此工厂时,应该让T为抽象产品的类型——即产品通用的基类。比如我们可以实现一个采用无参数构造函数来创建对象的OpNewFactory实现: public class OpNewFactory<TAbstractProduct, TProduct> : IFactory<TAbstractProduct>     where TProduct : TAbstractProduct, new() {     public TAbstractProduct Create()     {         return new TProduct();     } } 从此例子可以看出,你应该仅实现抽象类型的IFactory接口,并生成具体类型。现在我们做完了单一产品的工厂方法模板,就要开始定义生产多个产品的抽象工厂接口了。.NET泛型支持按类型参数个数进行重载,就是说,我们可以定义生产一个、两个、三个……等多种数目的抽象工厂接口,而使用同一个名字。(汗吧,这就是所谓支持“任意数目”的手法)这里免不了要拷贝代码,不过别担心,纯拷贝而已,使用的时候可是非常舒心的哦。我们以生产两种产品类型的抽象工厂为例介绍。能不能定义成这样呢? public interface IAbstractFactory<T1, T2> {     T1 Create();     T2 Create(); //编译错误!!! } 哦不!方法不能以返回类型区分重载,只能靠参数类型重载。然而这里我们显然不能用T1和T2作为参数,因为这些方法就是为了生产T1和T2准备的,怎么能接受它们作为参数呢?难道要命名为Create1和Create2吗?这很难接受,我们希望生产方法能够体现产品的类型,怎么能叫1和2呢。为了解决这个问题,我们引入了TypeToken<T>类型,它的定义如下: public sealed class TypeToken<T> {     static private TypeToken<T> instanceValue = new TypeToken<T>();     static public TypeToken<T> Instance     {         get { return instanceValue; }     }     private TypeToken() { } } 这个类没有成员,并且每个类型实参只能创建一个实例,因此代价极小。但就是这小小的实例上带有其类型实参的类型信息,因此可以作为判断函数重载的依据。我们用TypeToken<T>作为区分生产函数重载的依据,实现如下: public interface IAbstractFactory<T1, T2> {     T1 Create(TypeToken<T1> token);     T2 Create(TypeToken<T2> token); } 现在我们针对抽象工厂实现具体工厂。具体工厂就是利用生产每种产品的单一工厂来组合实现。因此你只要有每种类型的单一工厂就可以直接组合生成抽象工厂,而无需定义一个类来做这件事。注意,对每种数目的抽象工厂接口都需要对应生成一个具体工厂的实现,这里我仅针对生成两个产品的演示: public class ConcreteFactory<T1, T2> : IAbstractFactory<T1, T2> {     private IFactory<T1> factory1;     private IFactory<T2> factory2;     public ConcreteFactory(IFactory<T1> f1, IFactory<T2> f2)     {         factory1 = f1;         factory2 = f2;     }     public T1 Create(TypeToken<T1> token)     {         return factory1.Create();     }     public T2 Create(TypeToken<T2> token)     {         return factory2.Create();     } } public static class ConcretFactory {     public static ConcreteFactory<T1, T2> NewFactory<T1, T2>(IFactory<T1> f1, IFactory<T2> f2)     {         return new ConcreteFactory<T1, T2>(f1, f2);     } } 注意,我又声明了一个没有类型参数的ConcretFactory类,用一个静态方法来生成泛型ConcretFactory的实例,这是因为使用泛型方法可以推测类型参数,使得我们可以不必输入尖括号或Of语句,而泛型类则没有这个功能。现在大功告成!我们用一个例子来演示这个泛型抽象工厂的工作情况。现在假设我们需要一个生产PC的抽象工厂,需要生产两种抽象产品:处理器和内存。处理器和内存的抽象和具体实现如下: Processor 和 Ram public abstract class Processor {     public abstract string Model { get; } } public abstract class Ram {     public abstract int Frequency { get;} } public class PentiumProcessor : Processor {     public override string Model     {         get { return "Pentium Extreme Edition 955"; }     } } public class AthlonProcessor : Processor {     public override string Model     {         get { return "Athlon 64 X2 FX-60"; }     } } public class DDRRam : Ram {     public override int Frequency     {         get { return 400; }     } } public class DDR2Ram : Ram {     public override int Frequency......[阅读全文]

posted @ | Feedback (13) | Filed Under [ 技术随笔 ]

摘要:从今天起,我将分享一些我在.NET泛型方面的研究心得。这些心得大都是我在设计VBF中思考发现的,还受了很多C++模板与JAVA泛型的启发。其中相当多的技巧和概念都是大部分“深入C# 2.0”类文章也见不到的。我考虑把其中概念性的部分系统地整理成书,而技巧性的东西则通过我这个系列不定期地分享到Blog上。希望用到.NET泛型的人能从我这些技巧中受益。 首先我要介绍的技巧是如何提供类型参数之间的转换。我们知道,.NET泛型将每个类型参数理解为一个独立的类型。如果不通过约束指定,编译器不会对类型参数所代表的类型做任何假设。也就是说,如果在某个上下文中有两个不同的类型参数U和V,编译器不会知道运行时他们代表的真实类型能否进行类型转换,因此会拒绝编译如下代码: Public Function GenericCast(Of U, V)(ByVal obj As U) As V    Return CType(obj, V)End Function 除非我们加以约束U是V的子类。这显然是为了类型安全做出的考虑,因为.NET这种跨程序集的泛型无法在编译时做出足够的检查来确保类型安全。但是限制了这种操作,就妨碍了我们做事,很多代码因此写不出来。于是,我们可以利用.NET所有类型的基类Object来绕过这一限制: Return DirectCast(DirectCast(obj, Object), V) VB的DirectCast运算符在泛型的类型参数上作用与C#的括号运算符相同。也就是说,这段代码用C#写起来是这样: return (V)(object)obj; 这样,即使不约束U和V之间的关系,这段代码也能编译了。但是他的功能却不能令我们满意。这样写出的类型转换实际上还是仅当U是V本身或其子类的时候才能转换成功。而其他一切情况都会转换失败。不管U和V的运行时类型之间是否定义有其他类型转换规则。这显然不合我们意愿,我们希望int与double之间的转换等语言内置的类型转换都能够自动进行,否则就和约束没什么两样了。对VB用户来说有一个极为简单的解决方案——把第二次DirectCast变成CType: Public Function GenericCast(Of U, V)(ByVal obj As U) As V    Return CType(DirectCast(obj, Object), V)End Function 现在,GenericCast泛型方法就能执行int与double等内置规则的转换了。很神奇?因为CType运算符在编译时自动调用了VB运行库的转换函数,该函数在运行期间对泛型类型参数的真实类型做了检查。而且这个过程的性能完全可以接受。 现在VB的用户已经轻松享受这一功能了。但是C#的事情还没完,因为C#没有如此智能的类型转换运算符,因此就需要手工实现VB运行库所代办的那些任务。其实就是利用了一下IConvertible: static V GenericCast<U, V>(U obj){    IConvertible convertibleObj = obj as IConvertible;    if (convertibleObj != null)    {        Type t = typeof(V);         switch (Type.GetTypeCode(t))        {            case TypeCode.Boolean:                return (V)(object)convertibleObj.ToBoolean(null);            case TypeCode.Byte:                return (V)(object)convertibleObj.ToByte(null);            case TypeCode.Char:                return (V)(object)convertibleObj.ToChar(null);                        //.........            default:                //None of them, use the following default way..                break;        }    }     return (V)(object)obj;} 不过这个方法看起来不但很丑、很麻烦,功能上还达不到VB版。所以建议您用到这种类型参数之间的转换,就用VB封装这一功能,然后做成dll供C#调用吧。 ————Update 1.8, 19:40———— 经速马提醒,发现Convert.ChangeType方法已经封装了IConvertible判断的那些代码,因此C#版可采用这种写法: static V GenericCast<U, V>(U obj){    return (V)Convert.ChangeType(obj, typeof(V));} 不过它仍然没有VB版的CType功能丰富。此外我还发现了VB运行库的ChangeType还有支持用户自定义类型转换运算符的功能。所以终极版本如下: Public Function GenericCast(Of U, V)(ByVal obj As U) As V    Try       ......[阅读全文]

posted @ | Feedback (11) | Filed Under [ 技术随笔 ]

摘要:在动态编程时,我们常常需要运行时确定调用对象的哪个属性或哪个方法。这个任务通常可以用反射来解决。但众所周知,反射的性能要比静态指定的方式低很多,因为反射要通过运行时复杂的机制完成。能否获得性能和灵活性兼备的动态调用?我在开发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 [ 技术随笔 灵感记录 ]