在上一篇《
来一点反射,再来一点Emit —— 极度简化Entity!》中,Teddy运用反射和Emit极度简化了Entity的定义方式。本文将在上文的基础上,用自定义KeyValueCollection类代替原来的Dictionary类承载Entity的数据,从而改善Entity的读写性能,并保持Dictionary的方便的使用接口。
为什么Dictionary的性能不好?
当然,这里说的性能不好主要指相对于Private Field-Public Property方式的性能而言。我们知道,.Net2.0中的Dictionary类,实际上是一个泛型的HashTable实现。因此,性能不佳的原因主要就是HashTable算法。FantasySoft在他的文章《 解读Hashtable》中为我们回顾了HashTable算法的原理。纵观整个算法,主要有以下几个地方的性能损失:一个是hashcode的计算,即计算key的hashcode的代码;第二个是hashcode重复的问题,此时,需要进行一个附加的链表查询。并且,Dictionary或HashTable都是通用类型,他们的太多代码,在这里完全没有用到,而白白浪费了实例化他们的内存开销。
为什么不直接使用Private Field-Public Property方式生成Entity?
那么,既然Private Field-Public Property方式的性能绝对是最好的,为什么Teddy又要定义自定义的KeyValueCollection呢?直接的原因是,对于我的已有代码,尤其是emit生成代码来讲,将Dictionary类承载数据改成Private Field-Public Property方式,代码的改动幅度过大,为了避免运行时反射的性能损失,IEntity接口中的方法可能都将不再适合定义在基类Enitity<>中,而必须直接动态emit,也因此,虽然明知Private Field-Public Property的性能最好,但是,我并不愿意冒然向他缴械。也因此,如本文标题,Teddy开始尝试使用自定义的KeyValueCollection来代替Dictionary,希望能够获得和Private Field-Public Property相似的性能,但是又有Dictionary的那样的简单使用接口。
自定义KeyValueCollection!
考虑到我的KeyValueCollection只是用于持久化内部的数据承载,我不需要太大的通用性,只需要必要的接口就好,我将KeyValueCollection类定义如下:
接着,还要将原来对Dictionary的调用全都改成对新的类的调用,基类Entity<>的改动不太大,我就不列举了,运行时生成的Entity改动就比较大了。使用新的KeyValueCollection类后,实际生成的Entity的代码的范例列举如下(IAbout为需要用户定义的接口,static EntityFactory()为修改后的生成运行时About类的emit代码,最后的About为,实际上emit出来的类的等价hard code代码):
注意看最后一个About的Hard Code伪代码,SetPropertyValue是用于EntityFactory创建Entity实例和绑定Entity到DataSet/IDataReader的。这个函数的性能是O(n/2)。考虑到Entity的Property数量不会多至一百几千以上,这里的O(n/2)实际上基本等于O(1)。而set函数和get函数想比较与以前的Dictionary方式,用int索引代替了string型的key,从而避免了hashcode计算和字符串比较,再加上这里直接用一个数组代替,hashtable的存储结构,也就完全避免了hashtable可能的键值重复时的链表查询。
综上所述
采用自定义KeyValueCollection类代替Dictionary/HashTable来承载Entity的数据,获得了类似Dictionary的强类型key类型和简单的使用接口、使得原来的组件代码改动最少,并且,非常理想的获得了极其近似Private Field-Public Property方式的Entity性能。真可谓,一举数得!
示例下载(本示例与上一篇的示例代码区别仅在于用自定义KeyValueCollection代替了Dictionary类,Sample程序是一模一样的,但是引用的编译后的ILungasoft.Helper.Data.dll是修改后的新版本,请酌情下载)
为什么Dictionary的性能不好?
当然,这里说的性能不好主要指相对于Private Field-Public Property方式的性能而言。我们知道,.Net2.0中的Dictionary类,实际上是一个泛型的HashTable实现。因此,性能不佳的原因主要就是HashTable算法。FantasySoft在他的文章《 解读Hashtable》中为我们回顾了HashTable算法的原理。纵观整个算法,主要有以下几个地方的性能损失:一个是hashcode的计算,即计算key的hashcode的代码;第二个是hashcode重复的问题,此时,需要进行一个附加的链表查询。并且,Dictionary或HashTable都是通用类型,他们的太多代码,在这里完全没有用到,而白白浪费了实例化他们的内存开销。
为什么不直接使用Private Field-Public Property方式生成Entity?
那么,既然Private Field-Public Property方式的性能绝对是最好的,为什么Teddy又要定义自定义的KeyValueCollection呢?直接的原因是,对于我的已有代码,尤其是emit生成代码来讲,将Dictionary类承载数据改成Private Field-Public Property方式,代码的改动幅度过大,为了避免运行时反射的性能损失,IEntity接口中的方法可能都将不再适合定义在基类Enitity<>中,而必须直接动态emit,也因此,虽然明知Private Field-Public Property的性能最好,但是,我并不愿意冒然向他缴械。也因此,如本文标题,Teddy开始尝试使用自定义的KeyValueCollection来代替Dictionary,希望能够获得和Private Field-Public Property相似的性能,但是又有Dictionary的那样的简单使用接口。
自定义KeyValueCollection!
考虑到我的KeyValueCollection只是用于持久化内部的数据承载,我不需要太大的通用性,只需要必要的接口就好,我将KeyValueCollection类定义如下:
using
System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
namespace Ilungasoft.Helper.Data
{
public class KeyValueCollection
{
private string[] keys;
private object[] values;
private KeyValueCollection()
{
}
public KeyValueCollection(params PropertyInfo[] pis)
{
if (pis != null)
{
keys = new string[pis.Length];
values = new object[pis.Length];
for (int i = 0; i < pis.Length; i++)
{
keys[i] = pis[i].Name;
values[i] = typeof(Util).GetMethod("DefaultValue", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(pis[i].PropertyType).Invoke(null, null);
}
}
else
{
keys = new string[0];
values = new object[0];
}
}
public string[] GetKeys(params string[] exceptKeys)
{
if (exceptKeys != null && exceptKeys.Length > 0)
{
int retKeyCount = keys.Length - exceptKeys.Length;
if (retKeyCount <= 0)
{
return new string[0];
}
List<string> retKeys = new List<string>(retKeyCount);
foreach (string key in keys)
{
bool isExcept = false;
foreach (string exceptMember in exceptKeys)
{
if (key.Equals(exceptMember))
{
isExcept = true;
break;
}
}
if (!isExcept)
{
retKeys.Add(key);
}
}
return retKeys.ToArray();
}
else
{
return keys;
}
}
public object[] GetValues(params string[] exceptKeys)
{
if (exceptKeys != null && exceptKeys.Length > 0)
{
int retValueCount = keys.Length - exceptKeys.Length;
if (retValueCount <= 0)
{
return new object[0];
}
List<object> retValues = new List<object>(retValueCount);
for (int i = 0; i < keys.Length; i++)
{
bool isExcept = false;
foreach (string exceptMember in exceptKeys)
{
if (keys[i].Equals(exceptMember))
{
isExcept = true;
break;
}
}
if (!isExcept)
{
retValues.Add(values[i]);
}
}
return retValues.ToArray();
}
else
{
return values;
}
}
public KeyValueCollection Clone()
{
KeyValueCollection retKeyValues = new KeyValueCollection();
string [] cloneKeys = new string[keys.Length];
keys.CopyTo(cloneKeys, 0);
retKeyValues.keys = cloneKeys;
object[] cloneValues = new object[values.Length];
values.CopyTo(cloneValues, 0);
retKeyValues.values = cloneValues;
return retKeyValues;
}
public object this[int index]
{
get
{
return values[index];
}
set
{
values[index] = value;
}
}
}
}
using System.Collections.Generic;
using System.Text;
using System.Reflection;
namespace Ilungasoft.Helper.Data
{
public class KeyValueCollection
{
private string[] keys;
private object[] values;
private KeyValueCollection()
{
}
public KeyValueCollection(params PropertyInfo[] pis)
{
if (pis != null)
{
keys = new string[pis.Length];
values = new object[pis.Length];
for (int i = 0; i < pis.Length; i++)
{
keys[i] = pis[i].Name;
values[i] = typeof(Util).GetMethod("DefaultValue", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(pis[i].PropertyType).Invoke(null, null);
}
}
else
{
keys = new string[0];
values = new object[0];
}
}
public string[] GetKeys(params string[] exceptKeys)
{
if (exceptKeys != null && exceptKeys.Length > 0)
{
int retKeyCount = keys.Length - exceptKeys.Length;
if (retKeyCount <= 0)
{
return new string[0];
}
List<string> retKeys = new List<string>(retKeyCount);
foreach (string key in keys)
{
bool isExcept = false;
foreach (string exceptMember in exceptKeys)
{
if (key.Equals(exceptMember))
{
isExcept = true;
break;
}
}
if (!isExcept)
{
retKeys.Add(key);
}
}
return retKeys.ToArray();
}
else
{
return keys;
}
}
public object[] GetValues(params string[] exceptKeys)
{
if (exceptKeys != null && exceptKeys.Length > 0)
{
int retValueCount = keys.Length - exceptKeys.Length;
if (retValueCount <= 0)
{
return new object[0];
}
List<object> retValues = new List<object>(retValueCount);
for (int i = 0; i < keys.Length; i++)
{
bool isExcept = false;
foreach (string exceptMember in exceptKeys)
{
if (keys[i].Equals(exceptMember))
{
isExcept = true;
break;
}
}
if (!isExcept)
{
retValues.Add(values[i]);
}
}
return retValues.ToArray();
}
else
{
return values;
}
}
public KeyValueCollection Clone()
{
KeyValueCollection retKeyValues = new KeyValueCollection();
string [] cloneKeys = new string[keys.Length];
keys.CopyTo(cloneKeys, 0);
retKeyValues.keys = cloneKeys;
object[] cloneValues = new object[values.Length];
values.CopyTo(cloneValues, 0);
retKeyValues.values = cloneValues;
return retKeyValues;
}
public object this[int index]
{
get
{
return values[index];
}
set
{
values[index] = value;
}
}
}
}
接着,还要将原来对Dictionary的调用全都改成对新的类的调用,基类Entity<>的改动不太大,我就不列举了,运行时生成的Entity改动就比较大了。使用新的KeyValueCollection类后,实际生成的Entity的代码的范例列举如下(IAbout为需要用户定义的接口,static EntityFactory()为修改后的生成运行时About类的emit代码,最后的About为,实际上emit出来的类的等价hard code代码):
using
System;
namespace Ilungasoft.Helper.TestApp.DomainObject2
{
public interface About: Ilungasoft.Helper.Data.IEntity
{
int ID { get; set; }
string Title { get; set; }
string Content { get; set; }
bool Deletable { get; set; }
int Order { get; set; }
}
}
namespace Ilungasoft.Helper.TestApp.DomainObject2
{
public interface About: Ilungasoft.Helper.Data.IEntity
{
int ID { get; set; }
string Title { get; set; }
string Content { get; set; }
bool Deletable { get; set; }
int Order { get; set; }
}
}
static
EntityFactory()
{
//create dynamic IEntity Assembly & Type through Emit
if (assBuilder == null)
{
AssemblyName assName = new AssemblyName();
assName.Name = DYNAMIC_ENTITY_NAMESPACE;
assBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assName, AssemblyBuilderAccess.Run);
}
if (modBuilder == null)
{
modBuilder = assBuilder.DefineDynamicModule(DYNAMIC_ENTITY_NAMESPACE);
}
if (typeBuilder == null)
{
typeBuilder = modBuilder.DefineType(DYNAMIC_ENTITY_NAMESPACE + "." + typeof(IEntityType).FullName, TypeAttributes.Public);
typeBuilder.AddInterfaceImplementation(typeof(IEntityType));
Type keyValuesType = typeof(KeyValueCollection);
Type baseType = typeof(Entity<IEntityType>);
typeBuilder.SetParent(baseType);
PropertyInfo[] pis = typeof(IEntityType).GetProperties();
//define SetPropertyValue(string key, object val)
MethodInfo mi = typeof(IEntity).GetMethod("SetPropertyValue");
ParameterInfo[] paramInfos = mi.GetParameters();
int paramlength = paramInfos.Length;
Type[] paramTypes = new Type[paramlength];
for (int i = 0; i < paramlength; i++)
{
paramTypes[i] = paramInfos[i].ParameterType;
}
MethodBuilder setPropValMethodBuilder = typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
for (int i = 0; i < paramlength; i++)
{
ParameterInfo pi = paramInfos[i];
setPropValMethodBuilder.DefineParameter(i + 1, pi.Attributes, pi.Name);
}
typeBuilder.DefineMethodOverride(setPropValMethodBuilder, mi);
ILGenerator setPropValMethodIL = setPropValMethodBuilder.GetILGenerator();
int canWritePropertyCount = 0;
int firstCanWritePropertyIndex = -1;
int finalCanWritePropertyIndex = -1;
for (int k = 0; k < pis.Length; k++)
{
if (pis[k].CanWrite)
{
if (firstCanWritePropertyIndex == -1)
{
firstCanWritePropertyIndex = k;
}
finalCanWritePropertyIndex = k;
canWritePropertyCount++;
}
}
if (canWritePropertyCount == 0)
{
setPropValMethodIL.Emit(OpCodes.Ret);
}
else if (canWritePropertyCount == 1)
{
setPropValMethodIL.Emit(OpCodes.Ldarg_0);
setPropValMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
EmitLoadInt32Value(setPropValMethodIL, firstCanWritePropertyIndex);
setPropValMethodIL.Emit(OpCodes.Ldarg_2);
setPropValMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("set_Item"));
setPropValMethodIL.Emit(OpCodes.Ret);
}
else
{
int nextRetCodeIndex = 0;
for (int k = 0; k < pis.Length; k++)
{
if (pis[k].CanWrite)
{
//work out nextRetCodeIndex
if (k == finalCanWritePropertyIndex)
{
nextRetCodeIndex += 0x1a;
}
else
{
nextRetCodeIndex += 0x1b;
}
setPropValMethodIL.Emit(OpCodes.Ldarg_1);
setPropValMethodIL.Emit(OpCodes.Ldstr, pis[k].Name);
setPropValMethodIL.Emit(OpCodes.Callvirt, typeof(string).GetMethod("Equals", new Type[] { typeof(string) }));
setPropValMethodIL.Emit(OpCodes.Brfalse_S, nextRetCodeIndex);
setPropValMethodIL.Emit(OpCodes.Ldarg_0);
setPropValMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
EmitLoadInt32Value(setPropValMethodIL, k);
setPropValMethodIL.Emit(OpCodes.Ldarg_2);
setPropValMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("set_Item"));
setPropValMethodIL.Emit(OpCodes.Ret);
}
}
}
//define GetMemberNames(params string[] exceptMembers)
mi = typeof(IEntity).GetMethod("GetMemberNames");
paramInfos = mi.GetParameters();
paramlength = paramInfos.Length;
paramTypes = new Type[paramlength];
for (int i = 0; i < paramlength; i++)
{
paramTypes[i] = paramInfos[i].ParameterType;
}
MethodBuilder getMemberNamesMethodBuilder = typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
for (int i = 0; i < paramlength; i++)
{
ParameterInfo pi = paramInfos[i];
getMemberNamesMethodBuilder.DefineParameter(i + 1, pi.Attributes, pi.Name);
}
typeBuilder.DefineMethodOverride(getMemberNamesMethodBuilder, mi);
ILGenerator getMemberNamesMethodIL = getMemberNamesMethodBuilder.GetILGenerator();
getMemberNamesMethodIL.Emit(OpCodes.Ldarg_1);
getMemberNamesMethodIL.Emit(OpCodes.Call, baseType.GetMethod("GetMemberNames", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public));
getMemberNamesMethodIL.Emit(OpCodes.Ret);
//define GetMemberValues(params string[] exceptMembers)
mi = typeof(IEntity).GetMethod("GetMemberValues");
paramInfos = mi.GetParameters();
paramlength = paramInfos.Length;
paramTypes = new Type[paramlength];
for (int i = 0; i < paramlength; i++)
{
paramTypes[i] = paramInfos[i].ParameterType;
}
MethodBuilder getMemberValuesMethodBuilder = typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
for (int i = 0; i < paramlength; i++)
{
ParameterInfo pi = paramInfos[i];
getMemberValuesMethodBuilder.DefineParameter(i + 1, pi.Attributes, pi.Name);
}
typeBuilder.DefineMethodOverride(getMemberValuesMethodBuilder, mi);
ILGenerator getMemberValuesMethodIL = getMemberValuesMethodBuilder.GetILGenerator();
getMemberValuesMethodIL.Emit(OpCodes.Ldarg_0);
getMemberValuesMethodIL.Emit(OpCodes.Ldarg_1);
getMemberValuesMethodIL.Emit(OpCodes.Call, baseType.GetMethod("GetMemberValues"));
getMemberValuesMethodIL.Emit(OpCodes.Ret);
//define default constructor
ConstructorBuilder consBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
ILGenerator ctorIL = consBuilder.GetILGenerator();
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Call, baseType.GetConstructor(new Type[0]));
ctorIL.Emit(OpCodes.Ret);
//define properties
foreach (PropertyInfo pi in pis)
{
PropertyBuilder propBuilder = typeBuilder.DefineProperty(pi.Name, System.Reflection.PropertyAttributes.HasDefault, pi.PropertyType, null);
MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
if (pi.CanRead)
{
//define getMethod
MethodBuilder getPropMethodBuilder = typeBuilder.DefineMethod("get_" + pi.Name, pi.GetGetMethod().Attributes & (~MethodAttributes.Abstract) | getSetAttr, pi.PropertyType, Type.EmptyTypes);
typeBuilder.DefineMethodOverride(getPropMethodBuilder, pi.GetGetMethod());
ILGenerator getPropMethodIL = getPropMethodBuilder.GetILGenerator();
getPropMethodIL.Emit(OpCodes.Ldarg_0);
getPropMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
getPropMethodIL.Emit(OpCodes.Ldstr, pi.Name);
getPropMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("get_Item"));
if (pi.PropertyType.IsValueType)
{
getPropMethodIL.Emit(OpCodes.Unbox_Any, pi.PropertyType);
}
else
{
getPropMethodIL.Emit(OpCodes.Castclass, pi.PropertyType);
}
getPropMethodIL.Emit(OpCodes.Ret);
propBuilder.SetGetMethod(getPropMethodBuilder);
}
if (pi.CanWrite)
{
//define setMethod
MethodBuilder setPropMethodBuilder = typeBuilder.DefineMethod("set_" + pi.PropertyType, pi.GetSetMethod().Attributes & (~MethodAttributes.Abstract) | getSetAttr, null, new Type[] { pi.PropertyType });
typeBuilder.DefineMethodOverride(setPropMethodBuilder, pi.GetSetMethod());
ILGenerator setPropMethodIL = setPropMethodBuilder.GetILGenerator();
setPropMethodIL.Emit(OpCodes.Ldarg_0);
setPropMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
setPropMethodIL.Emit(OpCodes.Ldstr, pi.Name);
setPropMethodIL.Emit(OpCodes.Ldarg_1);
if (pi.PropertyType.IsValueType)
{
setPropMethodIL.Emit(OpCodes.Box, pi.PropertyType);
}
setPropMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("set_Item"));
setPropMethodIL.Emit(OpCodes.Ret);
propBuilder.SetSetMethod(setPropMethodBuilder);
}
}
}
}
{
//create dynamic IEntity Assembly & Type through Emit
if (assBuilder == null)
{
AssemblyName assName = new AssemblyName();
assName.Name = DYNAMIC_ENTITY_NAMESPACE;
assBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assName, AssemblyBuilderAccess.Run);
}
if (modBuilder == null)
{
modBuilder = assBuilder.DefineDynamicModule(DYNAMIC_ENTITY_NAMESPACE);
}
if (typeBuilder == null)
{
typeBuilder = modBuilder.DefineType(DYNAMIC_ENTITY_NAMESPACE + "." + typeof(IEntityType).FullName, TypeAttributes.Public);
typeBuilder.AddInterfaceImplementation(typeof(IEntityType));
Type keyValuesType = typeof(KeyValueCollection);
Type baseType = typeof(Entity<IEntityType>);
typeBuilder.SetParent(baseType);
PropertyInfo[] pis = typeof(IEntityType).GetProperties();
//define SetPropertyValue(string key, object val)
MethodInfo mi = typeof(IEntity).GetMethod("SetPropertyValue");
ParameterInfo[] paramInfos = mi.GetParameters();
int paramlength = paramInfos.Length;
Type[] paramTypes = new Type[paramlength];
for (int i = 0; i < paramlength; i++)
{
paramTypes[i] = paramInfos[i].ParameterType;
}
MethodBuilder setPropValMethodBuilder = typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
for (int i = 0; i < paramlength; i++)
{
ParameterInfo pi = paramInfos[i];
setPropValMethodBuilder.DefineParameter(i + 1, pi.Attributes, pi.Name);
}
typeBuilder.DefineMethodOverride(setPropValMethodBuilder, mi);
ILGenerator setPropValMethodIL = setPropValMethodBuilder.GetILGenerator();
int canWritePropertyCount = 0;
int firstCanWritePropertyIndex = -1;
int finalCanWritePropertyIndex = -1;
for (int k = 0; k < pis.Length; k++)
{
if (pis[k].CanWrite)
{
if (firstCanWritePropertyIndex == -1)
{
firstCanWritePropertyIndex = k;
}
finalCanWritePropertyIndex = k;
canWritePropertyCount++;
}
}
if (canWritePropertyCount == 0)
{
setPropValMethodIL.Emit(OpCodes.Ret);
}
else if (canWritePropertyCount == 1)
{
setPropValMethodIL.Emit(OpCodes.Ldarg_0);
setPropValMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
EmitLoadInt32Value(setPropValMethodIL, firstCanWritePropertyIndex);
setPropValMethodIL.Emit(OpCodes.Ldarg_2);
setPropValMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("set_Item"));
setPropValMethodIL.Emit(OpCodes.Ret);
}
else
{
int nextRetCodeIndex = 0;
for (int k = 0; k < pis.Length; k++)
{
if (pis[k].CanWrite)
{
//work out nextRetCodeIndex
if (k == finalCanWritePropertyIndex)
{
nextRetCodeIndex += 0x1a;
}
else
{
nextRetCodeIndex += 0x1b;
}
setPropValMethodIL.Emit(OpCodes.Ldarg_1);
setPropValMethodIL.Emit(OpCodes.Ldstr, pis[k].Name);
setPropValMethodIL.Emit(OpCodes.Callvirt, typeof(string).GetMethod("Equals", new Type[] { typeof(string) }));
setPropValMethodIL.Emit(OpCodes.Brfalse_S, nextRetCodeIndex);
setPropValMethodIL.Emit(OpCodes.Ldarg_0);
setPropValMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
EmitLoadInt32Value(setPropValMethodIL, k);
setPropValMethodIL.Emit(OpCodes.Ldarg_2);
setPropValMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("set_Item"));
setPropValMethodIL.Emit(OpCodes.Ret);
}
}
}
//define GetMemberNames(params string[] exceptMembers)
mi = typeof(IEntity).GetMethod("GetMemberNames");
paramInfos = mi.GetParameters();
paramlength = paramInfos.Length;
paramTypes = new Type[paramlength];
for (int i = 0; i < paramlength; i++)
{
paramTypes[i] = paramInfos[i].ParameterType;
}
MethodBuilder getMemberNamesMethodBuilder = typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
for (int i = 0; i < paramlength; i++)
{
ParameterInfo pi = paramInfos[i];
getMemberNamesMethodBuilder.DefineParameter(i + 1, pi.Attributes, pi.Name);
}
typeBuilder.DefineMethodOverride(getMemberNamesMethodBuilder, mi);
ILGenerator getMemberNamesMethodIL = getMemberNamesMethodBuilder.GetILGenerator();
getMemberNamesMethodIL.Emit(OpCodes.Ldarg_1);
getMemberNamesMethodIL.Emit(OpCodes.Call, baseType.GetMethod("GetMemberNames", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public));
getMemberNamesMethodIL.Emit(OpCodes.Ret);
//define GetMemberValues(params string[] exceptMembers)
mi = typeof(IEntity).GetMethod("GetMemberValues");
paramInfos = mi.GetParameters();
paramlength = paramInfos.Length;
paramTypes = new Type[paramlength];
for (int i = 0; i < paramlength; i++)
{
paramTypes[i] = paramInfos[i].ParameterType;
}
MethodBuilder getMemberValuesMethodBuilder = typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
for (int i = 0; i < paramlength; i++)
{
ParameterInfo pi = paramInfos[i];
getMemberValuesMethodBuilder.DefineParameter(i + 1, pi.Attributes, pi.Name);
}
typeBuilder.DefineMethodOverride(getMemberValuesMethodBuilder, mi);
ILGenerator getMemberValuesMethodIL = getMemberValuesMethodBuilder.GetILGenerator();
getMemberValuesMethodIL.Emit(OpCodes.Ldarg_0);
getMemberValuesMethodIL.Emit(OpCodes.Ldarg_1);
getMemberValuesMethodIL.Emit(OpCodes.Call, baseType.GetMethod("GetMemberValues"));
getMemberValuesMethodIL.Emit(OpCodes.Ret);
//define default constructor
ConstructorBuilder consBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
ILGenerator ctorIL = consBuilder.GetILGenerator();
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Call, baseType.GetConstructor(new Type[0]));
ctorIL.Emit(OpCodes.Ret);
//define properties
foreach (PropertyInfo pi in pis)
{
PropertyBuilder propBuilder = typeBuilder.DefineProperty(pi.Name, System.Reflection.PropertyAttributes.HasDefault, pi.PropertyType, null);
MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
if (pi.CanRead)
{
//define getMethod
MethodBuilder getPropMethodBuilder = typeBuilder.DefineMethod("get_" + pi.Name, pi.GetGetMethod().Attributes & (~MethodAttributes.Abstract) | getSetAttr, pi.PropertyType, Type.EmptyTypes);
typeBuilder.DefineMethodOverride(getPropMethodBuilder, pi.GetGetMethod());
ILGenerator getPropMethodIL = getPropMethodBuilder.GetILGenerator();
getPropMethodIL.Emit(OpCodes.Ldarg_0);
getPropMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
getPropMethodIL.Emit(OpCodes.Ldstr, pi.Name);
getPropMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("get_Item"));
if (pi.PropertyType.IsValueType)
{
getPropMethodIL.Emit(OpCodes.Unbox_Any, pi.PropertyType);
}
else
{
getPropMethodIL.Emit(OpCodes.Castclass, pi.PropertyType);
}
getPropMethodIL.Emit(OpCodes.Ret);
propBuilder.SetGetMethod(getPropMethodBuilder);
}
if (pi.CanWrite)
{
//define setMethod
MethodBuilder setPropMethodBuilder = typeBuilder.DefineMethod("set_" + pi.PropertyType, pi.GetSetMethod().Attributes & (~MethodAttributes.Abstract) | getSetAttr, null, new Type[] { pi.PropertyType });
typeBuilder.DefineMethodOverride(setPropMethodBuilder, pi.GetSetMethod());
ILGenerator setPropMethodIL = setPropMethodBuilder.GetILGenerator();
setPropMethodIL.Emit(OpCodes.Ldarg_0);
setPropMethodIL.Emit(OpCodes.Ldfld, baseType.GetField("keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
setPropMethodIL.Emit(OpCodes.Ldstr, pi.Name);
setPropMethodIL.Emit(OpCodes.Ldarg_1);
if (pi.PropertyType.IsValueType)
{
setPropMethodIL.Emit(OpCodes.Box, pi.PropertyType);
}
setPropMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod("set_Item"));
setPropMethodIL.Emit(OpCodes.Ret);
propBuilder.SetSetMethod(setPropMethodBuilder);
}
}
}
}
using
System;
using Ilungasoft.Helper.Data;
namespace Ilungasoft.Helper.TestApp.DomainObject
{
public class About : Entity<About>, IEntity
{
public About() : base()
{
}
public int ID
{
get { return (int)keyValues[0]; }
set { keyValues[0] = value; }
}
public string Title
{
get { return (string)keyValues[1]; }
set { keyValues[1] = value; }
}
public string Content
{
get { return (string)keyValues[2]; }
set { keyValues[2] = value; }
}
public bool Deletable
{
get { return (bool)keyValues[3]; }
set { keyValues[3] = value; }
}
public int Order
{
get { return (int)keyValues[4]; }
set { keyValues[4] = value; }
}
public void SetPropertyValue(string key, object val)
{
if (key.Equals("ID"))
{
keyValues[0] = val;
}
else if (key.Equals("Title"))
{
keyValues[1] = val;
}
else if (key.Equals("Content"))
{
keyValues[2] = val;
}
else if (key.Equals("Deletable"))
{
keyValues[3] = val;
}
else if (key.Equals("Order"))
{
keyValues[4] = val;
}
}
public new string[] GetMemberNames(params string[] exceptMembers)
{
return Entity<About>.GetMemberNames(exceptMembers);
}
public new object[] GetMemberValues(params string[] exceptMembers)
{
return base.GetMemberValues(exceptMembers);
}
}
}
using Ilungasoft.Helper.Data;
namespace Ilungasoft.Helper.TestApp.DomainObject
{
public class About : Entity<About>, IEntity
{
public About() : base()
{
}
public int ID
{
get { return (int)keyValues[0]; }
set { keyValues[0] = value; }
}
public string Title
{
get { return (string)keyValues[1]; }
set { keyValues[1] = value; }
}
public string Content
{
get { return (string)keyValues[2]; }
set { keyValues[2] = value; }
}
public bool Deletable
{
get { return (bool)keyValues[3]; }
set { keyValues[3] = value; }
}
public int Order
{
get { return (int)keyValues[4]; }
set { keyValues[4] = value; }
}
public void SetPropertyValue(string key, object val)
{
if (key.Equals("ID"))
{
keyValues[0] = val;
}
else if (key.Equals("Title"))
{
keyValues[1] = val;
}
else if (key.Equals("Content"))
{
keyValues[2] = val;
}
else if (key.Equals("Deletable"))
{
keyValues[3] = val;
}
else if (key.Equals("Order"))
{
keyValues[4] = val;
}
}
public new string[] GetMemberNames(params string[] exceptMembers)
{
return Entity<About>.GetMemberNames(exceptMembers);
}
public new object[] GetMemberValues(params string[] exceptMembers)
{
return base.GetMemberValues(exceptMembers);
}
}
}
注意看最后一个About的Hard Code伪代码,SetPropertyValue是用于EntityFactory创建Entity实例和绑定Entity到DataSet/IDataReader的。这个函数的性能是O(n/2)。考虑到Entity的Property数量不会多至一百几千以上,这里的O(n/2)实际上基本等于O(1)。而set函数和get函数想比较与以前的Dictionary方式,用int索引代替了string型的key,从而避免了hashcode计算和字符串比较,再加上这里直接用一个数组代替,hashtable的存储结构,也就完全避免了hashtable可能的键值重复时的链表查询。
综上所述
采用自定义KeyValueCollection类代替Dictionary/HashTable来承载Entity的数据,获得了类似Dictionary的强类型key类型和简单的使用接口、使得原来的组件代码改动最少,并且,非常理想的获得了极其近似Private Field-Public Property方式的Entity性能。真可谓,一举数得!
示例下载(本示例与上一篇的示例代码区别仅在于用自定义KeyValueCollection代替了Dictionary类,Sample程序是一模一样的,但是引用的编译后的ILungasoft.Helper.Data.dll是修改后的新版本,请酌情下载)