問題が実際にPropertyInfo.GetValue
メソッド呼び出しにある場合は、プロパティ ゲッター キャッシュを構築するアプローチを使用できます (たとえば、コンパイルされた式を介して)。次のサンプルは、14 個のプロパティ (ホット キャッシュあり) を持つ 8000 個のオブジェクトで、このアプローチが元の方法よりも最大 30 ~ 40% 高速であることを示しています。
static void Main(string[] args) {
IList objects = new List<Obj>();
for(int i = 0; i < 8000; i++)
objects.Add(new Obj());
var properties = typeof(Obj).GetProperties();
var sw1 = System.Diagnostics.Stopwatch.StartNew();
InitializeData1(objects, properties, new List<Dictionary<string, object>>());
sw1.Stop();
Console.WriteLine("Reflection PropertyInfo.GetValue: " + sw1.ElapsedTicks.ToString());
// cold cache testing
var sw2_coldCache = System.Diagnostics.Stopwatch.StartNew();
InitializeData2<Obj>(objects, properties, new List<Dictionary<string, object>>(), new Dictionary<string, Func<Obj, object>>());
sw2_coldCache.Stop();
Console.WriteLine("Cached Getters (Cold cache): " + sw2_coldCache.ElapsedTicks.ToString());
// cache initialization
InitializeData2<Obj>(new List<Obj> { new Obj() }, properties, new List<Dictionary<string, object>>(), gettersCache);
// hot cache testing
var sw2_hotCache = System.Diagnostics.Stopwatch.StartNew();
InitializeData2<Obj>(objects, properties, new List<Dictionary<string, object>>(), gettersCache);
sw2_hotCache.Stop();
Console.WriteLine("Cached Getters (Hot cache): " + sw2_hotCache.ElapsedTicks.ToString());
var sw3 = System.Diagnostics.Stopwatch.StartNew();
InitializeData3(objects, properties, new List<Dictionary<string, object>>());
sw3.Stop();
Console.WriteLine("returnProps special method: " + sw3.ElapsedTicks.ToString());
var sw4 = System.Diagnostics.Stopwatch.StartNew();
InitializeData2_NonGeneric(objects, properties, new List<Dictionary<string, object>>());
sw4.Stop();
Console.WriteLine("Cached Getters (runtime types resolving): " + sw4.ElapsedTicks.ToString());
}
元の実装は次のとおりです (テスト目的で縮小されています)。
static void InitializeData1(IList objects, PropertyInfo[] props, List<Dictionary<string, object>> tod) {
foreach(var item in objects) {
var kvp = new Dictionary<string, object>();
foreach(var p in props) {
kvp.Add(p.Name, p.GetValue(item, null));
}
tod.Add(kvp);
}
}
最適化された実装は次のとおりです。
static IDictionary<string, Func<Obj, object>> gettersCache = new Dictionary<string, Func<Obj, object>>();
static void InitializeData2<T>(IList objects, PropertyInfo[] props, List<Dictionary<string, object>> tod, IDictionary<string, Func<T, object>> getters) {
Func<T, object> getter;
foreach(T item in objects) {
var kvp = new Dictionary<string, object>();
foreach(var p in props) {
if(!getters.TryGetValue(p.Name, out getter)) {
getter = GetValueGetter<T>(p);
getters.Add(p.Name, getter);
}
kvp.Add(p.Name, getter(item));
}
tod.Add(kvp);
}
}
static Func<T, object> GetValueGetter<T>(PropertyInfo propertyInfo) {
var instance = System.Linq.Expressions.Expression.Parameter(propertyInfo.DeclaringType, "i");
var property = System.Linq.Expressions.Expression.Property(instance, propertyInfo);
var convert = System.Linq.Expressions.Expression.TypeAs(property, typeof(object));
return (Func<T, object>)System.Linq.Expressions.Expression.Lambda(convert, instance).Compile();
}
テストクラス:
class Obj {
public int p00 { set; get; }
public string p01 { set; get; }
public float p02 { set; get; }
public double p03 { set; get; }
public char p04 { set; get; }
public byte p05 { set; get; }
public long p06 { set; get; }
public int p07 { set; get; }
public string p08 { set; get; }
public float p09 { set; get; }
public double p10 { set; get; }
public char p11 { set; get; }
public byte p12 { set; get; }
public long p13 { set; get; }
}
更新: varocarbasからテストへのソリューションを追加
static void InitializeData3(IList objects, PropertyInfo[] props, List<Dictionary<string, object>> tod) {
foreach(Obj item in objects) {
var kvp = new Dictionary<string, object>();
foreach(var p in props) {
kvp.Add(p.Name, returnProps(p.Name, item));
}
tod.Add(kvp);
}
}
static object returnProps(string propName, Obj curObject) {
if(propName == "p00") {
return curObject.p00;
}
else if(propName == "p01") {
return curObject.p01;
}
else if(propName == "p02") {
return curObject.p02;
}
else if(propName == "p03") {
return curObject.p03;
}
else if(propName == "p04") {
return curObject.p04;
}
else if(propName == "p05") {
return curObject.p05;
}
else if(propName == "p06") {
return curObject.p06;
}
else if(propName == "p07") {
return curObject.p07;
}
else if(propName == "p08") {
return curObject.p08;
}
else if(propName == "p09") {
return curObject.p09;
}
else if(propName == "p10") {
return curObject.p10;
}
else if(propName == "p11") {
return curObject.p11;
}
else if(propName == "p12") {
return curObject.p12;
}
else if(propName == "p13") {
return curObject.p13;
}
return new object();
}
コンソールの結果: (リリース、x64) (Core i5 M560 @2.67 GHz、8GB RAM、Win7x64)
Reflection PropertyInfo.GetValue: 161288
Cached Getters (Cold cache): 153808
Cached Getters (Hot cache): 110837
returnProps special method: 128905
したがって、キャッシング アプローチが最適です。
UPDATE2サンプルで示されているメソッドは、コンパイル時に要素
のタイプがわかっている場合に使用することを目的としています(一般的な方法)。objects
InitializeData2<Obj>(...)
コンパイル時にタイプが不明なオブジェクト リストを使用している場合は、次の方法を使用して、InitializeData2<>
実行時にジェネリック メソッドを呼び出すことができます。
InitializeData2_NonGeneric(objects, properties, new List<Dictionary<string, object>>());
//...
static void InitializeData2_NonGeneric(IList objects, PropertyInfo[] props, List<Dictionary<string, object>> tod) {
Type elementType = objects[0].GetType();
var genericMethodInfo = typeof(Program).GetMethod("InitializeData2", BindingFlags.Static | BindingFlags.NonPublic);
var genericMethod = genericMethodInfo.MakeGenericMethod(new Type[] { elementType });
var genericGetterType = typeof(Func<,>).MakeGenericType(elementType,typeof(object));
var genericCacheType = typeof(Dictionary<,>).MakeGenericType(typeof(string), genericGetterType);
var genericCacheConstructor = genericCacheType.GetConstructor(new Type[] { });
genericMethod.Invoke(null, new object[] { objects, props, tod, genericCacheConstructor.Invoke(new object[] { }) });
}