• 泛型+反射+特性=以静制动


        泛型、反射、特性都是.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------------
    最新源代码下载
  • 相关阅读:
    jQuery禁用或启用
    ASP.NET MVC一次删除多笔记录
    在你的ASP.NET MVC中使用查找功能
    Get radio selected value
    绑定一个值给radio
    ASP.NET MVC实现权限控制
    为Guid数据类型的属性(property)赋值
    Razor语法中绑定一个值给checkbox
    判断IEnumerable<T>集合中是否包含有T对象
    SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM.
  • 原文地址:https://www.cnblogs.com/vwxyzh/p/779234.html
Copyright © 2020-2023  润新知