泛型、反射、特性都是.Net强大的功能之一,关于这3个的强大之处我就不再重复说了,今天想说的是,将这三者结合起来,组成一个强大的以静制动、以不变应万变的方案。
    这个方案,我已经通过一些实验,将它变成了真实的代码。当然如果已经有人有了类似的方案,纯属巧合。本方案的核心是将特性引入目前已经有很多人讨论过的Emit中。
    方案的目的:
    使用方需要知道:
    1、接口ITestDataEntity
    [DalEntity(KeepDataRow = false, AllowRemoting = false)]
    
public interface ITestDataEntity
    {
        [ParserInfomation(
typeof(StringParser), "text")]
        
string Text { getset; }
        [ParserInfomation(
typeof(IntegerParser), "count")]
        
int Count { getset; }
    }
    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(nullnew 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)

---------------2007.7.2------------
最新源代码下载
posted on 2007-06-11 14:42  Zhenway  阅读(1046)  评论(1编辑  收藏  举报