泛型、反射、特性都是.Net强大的功能之一,关于这3个的强大之处我就不再重复说了,今天想说的是,将这三者结合起来,组成一个强大的以静制动、以不变应万变的方案。 这个方案,我已经通过一些实验,将它变成了真实的代码。当然如果已经有人有了类似的方案,纯属巧合。本方案的核心是将特性引入目前已经有很多人讨论过的Emit中。 方案的目的: 使用方需要知道: 1、接口ITestDataEntity [DalEntity(KeepDataRow = false, AllowRemoting = false)] public interface ITestDataEntity { [ParserInfomation(typeof(StringParser), "text")] string Text { get; set; } [ParserInfomation(typeof(IntegerParser), "count")] int Count { get; set; } } 2、两个可重复使用的Parser public sealed class StringParser : ITypedParser {
public StringParser() { }
#region ITypedParser Members
public object ParseFromRow(DataRow row, string parserArg) { return row[parserArg].ToString(); }
public void ParseToRow(DataRow row, string parserArg, object obj) { row[parserArg] = obj; }
public Type ResultType { get { return typeof(string); } }
#endregion
} public sealed class IntegerParser : ITypedParser {
public IntegerParser() { }
#region ITypedParser Members
public object ParseFromRow(DataRow row, string parserArg) { return (int)row[parserArg]; }
public void ParseToRow(DataRow row, string parserArg, object obj) { row[parserArg] = obj; }
public Type ResultType { get { return typeof(int); } }
#endregion } 3、使用方的数据库表为: static DataTable GetTestTable() { DataTable dt = new DataTable(); dt.Columns.Add("text", typeof(string)); dt.Columns.Add("count", typeof(int)); dt.Rows.Add("the first row", 1); dt.Rows.Add("the second row", 2); return dt; } 4、使用方的代码为: DataTable dt = GetTestTable(); List<ITestDataEntity> list = new List<ITestDataEntity>(); foreach (DataRow row in dt.Rows) list.Add(DalEntityFactory.CreateObject<ITestDataEntity>(row)); 这就是全部的使用者的代码,不需要写一个实体类,取而代之的是写一个实体接口,以及一些特性。和一些Parser类,这些类是可以重用的,并且当属性需要是一个自定义的类型的时候,只需要再创建一个自定义类的Parser就可以了(提供很强的扩展性)。 这个目标看起来很酷,有可能实现吗? 下面,我们来说说实现,和里面的如何运用泛型、反射和特性的: 提供一个对象工厂(ObjectFactory),实验中,将它缩小为一个DalEntityFactory,其中有一个静态方法T Create<T>(object obj),实验中,我假设这个Object是一个DataRow,也就是,我的代码中是T Create<T>(DataRow row)。 这里,类型参数T在编译时是不可知道的,在调用方调用时才知道T的真实类型,当然这里有个限制,T必须是一个公开的接口,并且满足一些其他的限制,这些可以通过参数检查来做到,当然我并不想把这么一个复杂的参数检查放在这里,因为放在这里的话,即使这个类型参数通过了检查,但是下一次调用的时候,还要经过这么一个复杂的参数检查,显然是一种浪费,而且参数检查需要用到反射,频繁的反射,会降低程序的性能,因此,我新建一个实体透明工厂(EntityTransparentFactory<T>),在这个工厂里面有一个T Create(DataRow row)的方法,那么T EntityFactory.Create<T>(object obj)的实现就很简单,只需要做基本的参数检查,然后,将任务交给T EntityTransparentFactory<T>.Create(DataRow row)。 对象透明工厂为什么叫这个名字,很简单,这个工厂其实并不知道怎么创建T的实例,它仅仅是一个代理而已,(也许你会说为什么要这么一个代理,这里直接用Emit创建类型,在用Activator创建这个类型就可以了,确实没错,之前我就是这么写的,但是,我又作了些改良,具体请继续看),这个透明工厂在类型创建时会自动使用反射和Emit创建一个合适的类(动态类),实现接口T,但是,这还没完创建出这个实体的类型后,再创建一个这个实体类型的简单工厂类型(动态类),并且,这个简单工厂类型符合IEntityRealFactory<T>接口: public interface IEntityRealFactory<T> { T Create(DataRow row); } 然后用Activator创建这个简单工厂类型的实例,并且as成IEntityRealFactory<T>,由透明工厂保存这个实例。之后实体透明工厂的Create就很简单了,只需要调用真实工厂的Create方法就行了。 现在问题集中到EntityTransparentFactory<T>的类型构造里面了,为什么我要把代码放到这里哪?我原来是用字典的方式,但是还要考虑到锁的问题,感觉比较麻烦,干脆就利用类型构造只跑一次的特点,在泛型类型中,每种泛型只跑一次类型构造,正好符合我的要求,而且这个是CLR级别保证的,不需要手动去控制。 问题回到类型EntityTransparentFactory<T>构造的时候,用Emit创建动态类型,符合接口T。这个类型怎么建哪? 首先,我规定这个接口类型T必须带有一个这个特性 [AttributeUsage(AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] public sealed class DalEntityAttribute : Attribute { private bool _keepDataRow; private bool _allowRemoting;
public DalEntityAttribute() { }
public bool KeepDataRow { get { return _keepDataRow; } set { _keepDataRow = value; } }
public bool AllowRemoting { get { return _allowRemoting; } set { _allowRemoting = value; } } } 这个特性告诉我的动态类工厂这个接口的相关信息,例如:是否需要保存DataRow,这个数据实体是否需要支持Remoting(即继承自MarshalByRefObject) 如果需要保存这个DataRow,那么在Emit生成的类型中,仅存在一个唯一的字段:DataRow,所有的属性需要访问这个DataRow。 如果不需要保存这个DataRow,那么在Emit生成的类型中,存在每一个属性的对应字段,但是对这些属性的更改,不能反映到DataRow上。 然后是属性,接口中只允许有属性,否则参数检查不通过,并且,每个属性需要带有ParserInfomationAttribute特性: [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] public sealed class ParserInfomationAttribute : Attribute { private readonly Type _parserType; private readonly string _parserArg;
public ParserInfomationAttribute(Type parserType, string parserArg) { _parserType = parserType; _parserArg = parserArg; }
public Type ParserType { get { return _parserType; } }
public string ParserArg { get { return _parserArg; } }
} 这个特性主要是提供解析器的相关信息,其中的ParserType必须实现ITypedParser接口,并且有空参的构造。 public interface ITypedParser { object ParseFromRow(DataRow row, string parserArg); void ParseToRow(DataRow row, string parserArg, object obj); Type ResultType { get; } } 然后在每个属性的Get、Set时,创建一个ParserType的实例,调用对应的ParseFromRow或ParseToRow(在不保存DataRow时,在Ctor时就调用ParseFormRow,保存结果到对应的字段)。 当然别忘了这个类型的Ctor,这个构造函数在之后还要用到。 符合接口T的动态类型我们已经创建好了,剩下来的,我们只需要创建这个类型的实例就可以了,当然,最简单的方式是用前面提到的Activator去生成,但是这么做有个缺点,那就是每次都要反射,影响性能。 除了用这个Activator之外还有什么方法,想想设计模式里面,关于创建实例的模式有哪些,主要还是工厂模式、原形模式、单件模式,单件模式不可能使用,剩下工厂模式和原形模式,原形模式需要接口继承另一个原形模式的基础接口,并且有相应实现基类,感觉比较麻烦,我就选了剩下来的工厂模式,这就是上面写到的IEntityRealFactory<T>,问题是,这个类不能是一个静态类,因为它需要创建的实例的类型在编译时是不可知的。不过,想想这么复杂的Entity类我们都Emit出来了,为什么不再Emit这个简单的工厂类哪? 程序跑到这里,我们已经拥有了这个接口T和实现这个接口T的动态类型,以及这个动态类型的构造函数,也就是说,在Emit一个简单工厂完全可行。于是,就Emit这个简单工厂,问题是这个动态工厂怎么调用哪?如果再用反射,一切又回到起点。我们先泛型接口求助,IEntityRealFactory<T>可以清楚的定义这个类型的Create方法和它的返回值是T,也就是说,我们Emit这个动态简单工厂,使它符合IEntityRealFactory<T>,创建它的实例(用Activator,但是创建这个工厂的实例仅仅只有一次),用这个接口变量保存,以后我们在需要实例时仅仅需要调用这个T IEntityRealFactory<T>.Create(DataRow row)这个函数,就绕开了每次创建的反射。
核心部分差不多就是这些了。 但是,还有几点可以改进的: 1、提供对接口继承的支持(这个比较简单,改进一下Emit部分就可以了) 2、提供对序列化和反序列化的支持(这里是指跨AppDomain的情况,即:没有这个动态类型的情况)
为了支持序列化和反序列化,我本人是使用ISerializable接口+SerializeShell实现的。也就是说,只要接口继承了ISerializable接口,就认为这个接口对象是可以序列化的。需要略为改动一下符合接口T的动态类和对应的动态类工厂,增加相应的反序列化的部分。 因为二进制序列化和Xml/Soap序列化略有不同,Xml/Soap序列化不支持泛型,因此,使用了两种SerializeShell,二进制序列化使用SerializableShellForBinary,内容如下: [Serializable] public sealed class SerializableShellForBinary<T> : ISerializable where T : class, ISerializable {
#region Fields private T _value; #endregion
#region Ctors
public SerializableShellForBinary(T dalObj) { _value = dalObj; }
private SerializableShellForBinary(SerializationInfo info, StreamingContext context) { _value = EntityTransparentFactory<T>.Create(info, context); }
#endregion
#region ISerializable Members
public void GetObjectData(SerializationInfo info, StreamingContext context) { _value.GetObjectData(info, context); }
#endregion
#region Members
public T Value { get { return _value; } }
#endregion
} 而Xml/Soap序列化使用SerializableShellForXml,内容如下: [Serializable] public sealed class SerializableShellForXml : ISerializable {
private static Dictionary<Type, MethodInfo> _dict = new Dictionary<Type, MethodInfo>(); private static Type UnboundType = typeof(EntityTransparentFactory<>); private Type _type; private ISerializable _value;
private SerializableShellForXml(SerializationInfo info, StreamingContext context) { string typeName = info.GetString("Type"); if (typeName == null) throw new SerializationException("Missing Type"); Type _type = Type.GetType(typeName, false); if (_type == null) throw new SerializationException("Missing Type:" + typeName); MethodInfo mi = GetCreateMethod(_type); _value = mi.Invoke(null, new object[] { info, context }) as ISerializable; }
public SerializableShellForXml(Type t, ISerializable value) { if (t == null) throw new ArgumentNullException("t"); if (value == null) throw new ArgumentNullException("value"); if (!t.IsInterface || !t.IsPublic) throw new ArgumentException(_type.AssemblyQualifiedName + " is not a public interface.", "t"); if (!t.IsInstanceOfType(value)) throw new ArgumentException("value is not an instance of " + t.AssemblyQualifiedName, "value"); _type = t; _value = value; }
public Type InterfaceType { get { return _type; } }
public ISerializable Value { get { return _value; } }
private static MethodInfo GetCreateMethod(Type type) { lock (_dict) { MethodInfo result; if (!_dict.TryGetValue(type, out result)) { if (!type.IsInterface || !type.IsPublic) throw new SerializationException(type.AssemblyQualifiedName + " is not a public interface."); Type boundType = UnboundType.MakeGenericType(type); result = boundType.GetMethod("Create", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof(SerializationInfo), typeof(StreamingContext) }, null); _dict.Add(type, result); } return result; } }
#region ISerializable Members
public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Type", InterfaceType.AssemblyQualifiedName); _value.GetObjectData(info, context); }
#endregion
} 我们可以发现这里在Xml/Soap反序列化时使用了反射,这也导致了性能的下降,因为没有泛型的支持,这就不可避免了。 另外,要注意的是,因为某些类型本身不能序列化,所以并不保证序列化一定成功。(例如:DataRow不支持序列化,任何保存DataRow的都无法成功的序列化) 最后写一下序列化的效率,在我的机器上的测试10000次序列化和反序列化结果大致为: 二进制序列化与普通静态的代码写的类相比略慢(750ms),但大幅领先于DataTable(3437.5ms) 二进制反序列化与普通静态的代码写的类相比略慢(890.625ms),但大幅领先于DataTable(7296.875ms) Soap序列化与普通静态的代码写的类相比略慢(1812.5ms),但大幅领先于DataTable(5765.625ms) Soap反序列化与普通静态的代码写的类相比慢较多(3718.75ms),但仍然比反序列化DataTable快1倍多(9203.125ms)
|