泛型、反射、特性都是.Net强大的功能之一,关于这3个的强大之处我就不再重复说了,今天想说的是,将这三者结合起来,组成一个强大的以静制动、以不变应万变的方案。
这个方案,我已经通过一些实验,将它变成了真实的代码。当然如果已经有人有了类似的方案,纯属巧合。本方案的核心是将特性引入目前已经有很多人讨论过的Emit中。
方案的目的:
使用方需要知道:
1、接口ITestDataEntity
这个目标看起来很酷,有可能实现吗?
下面,我们来说说实现,和里面的如何运用泛型、反射和特性的:
提供一个对象工厂(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>接口:
现在问题集中到EntityTransparentFactory<T>的类型构造里面了,为什么我要把代码放到这里哪?我原来是用字典的方式,但是还要考虑到锁的问题,感觉比较麻烦,干脆就利用类型构造只跑一次的特点,在泛型类型中,每种泛型只跑一次类型构造,正好符合我的要求,而且这个是CLR级别保证的,不需要手动去控制。
问题回到类型EntityTransparentFactory<T>构造的时候,用Emit创建动态类型,符合接口T。这个类型怎么建哪?
首先,我规定这个接口类型T必须带有一个这个特性
如果需要保存这个DataRow,那么在Emit生成的类型中,仅存在一个唯一的字段:DataRow,所有的属性需要访问这个DataRow。
如果不需要保存这个DataRow,那么在Emit生成的类型中,存在每一个属性的对应字段,但是对这些属性的更改,不能反映到DataRow上。
然后是属性,接口中只允许有属性,否则参数检查不通过,并且,每个属性需要带有ParserInfomationAttribute特性:
当然别忘了这个类型的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,内容如下:
另外,要注意的是,因为某些类型本身不能序列化,所以并不保证序列化一定成功。(例如: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------------
最新源代码下载
这个方案,我已经通过一些实验,将它变成了真实的代码。当然如果已经有人有了类似的方案,纯属巧合。本方案的核心是将特性引入目前已经有很多人讨论过的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、两个可重复使用的Parserpublic interface ITestDataEntity
{
[ParserInfomation(typeof(StringParser), "text")]
string Text { get; set; }
[ParserInfomation(typeof(IntegerParser), "count")]
int Count { get; set; }
}
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、使用方的数据库表为:: 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
}
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 = 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;
}
DataTable dt = GetTestTable();
List<ITestDataEntity> list = new List<ITestDataEntity>();
foreach (DataRow row in dt.Rows)
list.Add(DalEntityFactory.CreateObject<ITestDataEntity>(row));
这就是全部的使用者的代码,不需要写一个实体类,取而代之的是写一个实体接口,以及一些特性。和一些Parser类,这些类是可以重用的,并且当属性需要是一个自定义的类型的时候,只需要再创建一个自定义类的Parser就可以了(提供很强的扩展性)。List<ITestDataEntity> list = new List<ITestDataEntity>();
foreach (DataRow row in dt.Rows)
list.Add(DalEntityFactory.CreateObject<ITestDataEntity>(row));
这个目标看起来很酷,有可能实现吗?
下面,我们来说说实现,和里面的如何运用泛型、反射和特性的:
提供一个对象工厂(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方法就行了。{
T Create(DataRow row);
}
现在问题集中到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)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,那么在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 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; }
}
}
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,保存结果到对应的字段)。{
object ParseFromRow(DataRow row, string parserArg);
void ParseToRow(DataRow row, string parserArg, object obj);
Type ResultType { get; }
}
当然别忘了这个类型的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,内容如下: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
}
[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反序列化时使用了反射,这也导致了性能的下降,因为没有泛型的支持,这就不可避免了。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
}
另外,要注意的是,因为某些类型本身不能序列化,所以并不保证序列化一定成功。(例如: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------------
最新源代码下载