这个方法能否加速?

14

我有一个方法,通过循环遍历7,753+个对象,并获取每个对象的每个属性的值。每个对象有14个属性。

private void InitializeData(IList objects, PropertyInfo[] props, List<DPV> dataPs, List<Dictionary<string, object>> tod)
{
    foreach (var item in objects)
    {
        var kvp = new Dictionary<string, object>();
        foreach (var p in props)
        {
            var dataPs = dataPs.FirstOrDefault(x => x.Name == p.Name);
            object returnData;
            if (dataPoint != null)
            {
                int maxLength = (dataP.MaxLength == null) ? 0 : (int) dataP.MaxLength;
                returnData = p.GetValue(item, null);
                if (!string.IsNullOrEmpty(dataP.FormatString) && !string.IsNullOrEmpty(returnData.ToString()))
                {
                    returnData = FormatDataForDisplay(returnData, dataP, maxLength, "", 8);
                }
            }
            else
            {
                returnData = p.GetValue(item, null);
            }
            kvp.Add(p.Name, returnData);
        }
        tod.Add(kvp);
    }
}

我相信这个方法中占据大部分时间的是GetValue。该方法执行需要约900毫秒,但GetValue被调用了800,000+次,每次大约需要750毫秒(总时间,而非单次调用)
public List<Dictionary<string, object>> GetColumnOptions<T>(List<T> list)
    {

        var tod= new List<Dictionary<string, object>>();



        var objects = (IList)list[0];
        Type objType = objects[0].GetType();

        var props = objType.GetProperties(BindingFlags.DeclaredOnly |
                                                         BindingFlags.Public |
                                                         BindingFlags.Instance);


        var dPs= GetDPs();



        //Initialize aaData
        //I don't believe this is correct
        InitializeData2<T>(new List<T> { (T) objects}, props, dPs, tod);

        return tod;
    }

没有反射调用的等效版本需要多长时间?这是“GetValue”的主要开销,但我很好奇它相对于直接属性引用增加了多少开销。(1,000纳秒不算巨大,但也不是微不足道的) - Guvante
编译时是否已知对象类型?您是否可以为每种已知类型编写类型安全的值getter/formatter,并且您需要做的就是对每个“item”进行类型查找/转换,这将为您生成KeyValuePairs并完全避免反射?编辑:更好的方法是让您的对象实现一个带有单个方法的接口,其工作是返回一组KeyValuePairs; 完全避免类型查找/转换。 - Chris Sinclair
@ChrisSinclair - 对象在运行时已知。它们是来自LINQ to Entities的实体。 - Xaisoft
2
我没有这个代码,但是当你反复使用反射来获取/设置相同的属性时,你可以通过预先编译表达式树中的lambda表达式来改善它,并使用这些lambda表达式。这是一个混乱的语法,需要大量的试错,直到你熟悉它,但基本上你可以编译一次属性访问(通过一次反射),然后将lambda表达式存储在字典或其他形式中,然后安全地调用lambda表达式,而不需要更多的反射。 - Joe Enos
@Xaisoft 或许我的第一个答案不是非常明确,因此我已经更新了它。请告诉我你的想法。 - varocarbas
显示剩余4条评论
4个回答

26

对于你的值类,你可以创建直接设置器和获取器lambda表达式。
性能几乎与直接访问属性相同。

从PropertyInfo获取Get Setter

var propertyInfo = typeof(MyType).GetProperty("MyPropertValue");
var propertySetter = FastInvoke.BuildUntypedSetter<T>(propertyInfo));
var fieldInfo = typeof(MyType).GetField("MyFieldValue");
var fieldSetter = FastInvoke.BuildUntypedSetter<T>(fieldInfo));

循环中的使用

var myTarget = new MyType();
setter(myTarget, aNewValue)

快速获取Setter和Getter的助手

public static class FastInvoke {

    public static Func<T, object> BuildUntypedGetter<T>(MemberInfo memberInfo)
    {
        var targetType = memberInfo.DeclaringType;
        var exInstance = Expression.Parameter(targetType, "t");

        var exMemberAccess = Expression.MakeMemberAccess(exInstance, memberInfo);       // t.PropertyName
        var exConvertToObject = Expression.Convert(exMemberAccess, typeof(object));     // Convert(t.PropertyName, typeof(object))
        var lambda = Expression.Lambda<Func<T, object>>(exConvertToObject, exInstance);

        var action = lambda.Compile();
        return action;
    }

    public static Action<T, object> BuildUntypedSetter<T>(MemberInfo memberInfo)
    {
        var targetType = memberInfo.DeclaringType;
        var exInstance = Expression.Parameter(targetType, "t");

        var exMemberAccess = Expression.MakeMemberAccess(exInstance, memberInfo);

        // t.PropertValue(Convert(p))
        var exValue = Expression.Parameter(typeof(object), "p");
        var exConvertedValue = Expression.Convert(exValue, GetUnderlyingType(memberInfo));
        var exBody = Expression.Assign(exMemberAccess, exConvertedValue);

        var lambda = Expression.Lambda<Action<T, object>>(exBody, exInstance, exValue);
        var action = lambda.Compile();
        return action;
    }

    private static Type GetUnderlyingType(this MemberInfo member)
    {
        switch (member.MemberType)
        {
            case MemberTypes.Event:
                return ((EventInfo)member).EventHandlerType;
            case MemberTypes.Field:
                return ((FieldInfo)member).FieldType;
            case MemberTypes.Method:
                return ((MethodInfo)member).ReturnType;
            case MemberTypes.Property:
                return ((PropertyInfo)member).PropertyType;
            default:
                throw new ArgumentException
                (
                 "Input MemberInfo must be if type EventInfo, FieldInfo, MethodInfo, or PropertyInfo"
                );
        }
    }
}

============= 添加性能分析 ===================

5百万个对象,20个属性

  • 3.4秒 直接属性访问
  • 130.0秒 通过PropertyInfo.SetValue访问
  • 4.0秒 通过TypedSetter (在文章中展示的代码) 访问
  • 9.8秒 通过UnTypedSetter (上面的代码) 访问

诀窍是为每个类生成一次属性设置器和获取器,并重用它们。

// Create an fill objects fast from DataReader
// http://flurfunk.sdx-ag.de/2012/05/c-performance-bei-der-befullungmapping.html 
static List<T> CreateObjectFromReader<T>(IDataReader reader)
    where T : new()
{
  // Prepare
  List<string> fieldNames = GetFieldNames(reader);
  List<Action<T, object>> setterList = new List<Action<T, object>>();
 
  // Create Property-Setter and store it in an array 
  foreach (var field in fieldNames)
  {
    var propertyInfo = typeof(T).GetProperty(field);
    setterList.Add(FastInvoke.BuildUntypedSetter<T>(propertyInfo));
  }
  Action<T, object>[] setterArray = setterList.ToArray();
 
  // generate and fill objects
  while (reader.Read())
  {
    T xclass = new T();
    int fieldNumber = 0;
 
    for (int i = 0; i< setterArray.Length; i++) 
    {
        // call setter
        setterArray[i](xclass, reader.GetValue(i));
        fieldNumber++;
    } 
    result.Add(xclass);
  }
}

我原始的文章(德语文本和较旧的代码)在https://web.archive.org/web/20141020092917/http://flurfunk.sdx-ag.de/2012/05/c-performance-bei-der-befullungmapping.html上。

该文章介绍了关于C++中填充映射时如何提高性能的方法。如果您对此感兴趣,可以点击链接查看原文。

1
对于字段来说,是否也可以做到相同的效果呢?还是“SetValue = (o1, o2) => fieldInfo.SetValue(o1, o2);”就能够胜任了呢? - Guedez
1
@Guedez:现在你可以用它来处理属性和字段了。我通过使用MakeMemberAccess函数改变了实现方式(我在2016年不知道这个函数;)。 - Fried
@RyanThomas:据我所知,您正在尝试将字符串值设置为int属性?与propertyInfo.SetValue(..)相同,FastInvoke也会执行Convert(),这也会引发异常。因此,行为是正确的。您的请求是在表达式中添加额外的int.Parse(string)resp。propertyType.Parse(sourceType)。我认为不可能为所有源和目标类型实现它,因此您可以扩展表达式以访问字符串类型并提供基本类型。不太容易... - Fried
@Fried 是的,你是正确的。真遗憾。我想我还是会坚持使用 FieldInfo.SetValue - 经过我的测试,使用这种方式在对象上设置1000个字段时,执行时间介于0和1毫秒之间,这是如此微小的量,没有必要进行优化。无论如何,非常感谢。 :) - Ryan Thomas
2
文章的缓存副本:archive.org链接 - Michael Silver
显示剩余3条评论

3
如果问题确实在于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

因此,缓存方法是最佳选择。 更新2
示例中演示的方法旨在在编译时已知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[] { }) });
}

我的对象不是以List<object>的形式出现的。它们以List<T> list的形式出现,为了获取属性类型,我需要这样做:var objects = (IList)list[0]; Type objType = objects[0].GetType(); - Xaisoft
@Xaisoft 第一,您可以明确指定:InitializeData2<T>(objects, properties, tod); 第二,无论如何,我没有看到任何修改我的示例的问题...您始终可以澄清您的问题或创建新问题以演示确切的问题。 - DmitryG
在进行更改后,我收到了以下错误消息:无法将类型为'System.Func2 [<>f__AnonymousTypea12 [System.Int32,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String],System.Object]'的对象强制转换为类型'System.Func2 [System.Object,System.Object]',位于行:return (Func<T,object>)System.Linq.Expressions.Expression.Lambda(convert,instance).Compile(); - Xaisoft
@Xaisoft,由于缺乏信息,很难确定此问题的原因。请使用产生此问题的代码更新问题。 - DmitryG
@DmitryG 感谢您收录我的方法。我打算运行您的代码进行一些测试,但遇到了困难。我正在使用VS2010并需要适应各个部分;此外,我对Tlist等内容不太熟悉。我主要的问题在于“foreach(T item in objects) {”中,我不知道如何将“objects”中的“items”变成“T”(实际上它们是“Object”);如果我让“items”保持为“Object”,我就无法执行“kvp.Add(p.Name, getter(item));”(最重要的部分),因为“getter”需要一个“T”。请问我做错了什么? - varocarbas
显示剩余7条评论

1
我进行了一个简单的测试,用一个执行简单赋值的函数替换了有问题的 .GetValue(“如果属性名称是 blabla,则该值为 Object.blabla”)。测试仅包括您的函数/变量/属性的简单版本和一个循环,允许完全控制迭代次数。结果非常惊人:新方法快了10倍!请注意,在我的原始测试中(50000次迭代),时间分别为2276毫秒(旧)和234毫秒(新)。这种差异对于不同的场景保持恒定;例如,对于8000次迭代,它提供了358毫秒与36毫秒之间的差异。我在一台相当强大的电脑上以及 C# winforms 上进行了这些测试;@Xaisoft 可以使用下面的代码,在他的特定条件下进行测试并告知结果。
 private void Form1_Load(object sender, EventArgs e)
 {
     List<List> var = new List<List>();

     List var1 = new List();
     var1.var = 1;
     var1.var2 = 1;
     var1.var3 = 1;
     var1.var4 = 1;
     var1.var5 = 1;

     List var2 = new List();
     var2.var = 1;
     var2.var2 = 1;
     var2.var3 = 1;
     var2.var4 = 1;
     var2.var5 = 1;

     List var3 = new List();
     var3.var = 1;
     var3.var2 = 1;
     var3.var3 = 1;
     var3.var4 = 1;
     var3.var5 = 1;

     List var4 = new List();
     var4.var = 1;
     var4.var2 = 1;
     var4.var3 = 1;
     var4.var4 = 1;
     var4.var5 = 1;

     var.Add(var1);
     var.Add(var2);
     var.Add(var3);
     var.Add(var4);

     InitializeData(var, typeof(List).GetProperties());
 }

 private static void InitializeData(List<List> objects, PropertyInfo[] props)
 {
     DateTime start = DateTime.Now;

     int count = 0;
     do
     {
         count = count + 1;
         foreach (var item in objects)
         {

             foreach (var p in props)
             {
                 object returnData = p.GetValue(item, null); //returnProps(p.Name, item);
             }
         }

     } while (count < 50000);


     TimeSpan timer = new TimeSpan();
     timer = DateTime.Now.Subtract(start);
 }

 private class List
 {
     public int var { set; get; }
     public int var2 { set; get; }
     public int var3 { set; get; }
     public int var4 { set; get; }
     public int var5 { set; get; }
     public int var6 { set; get; }
     public int var7 { set; get; }
     public int var8 { set; get; }
     public int var9 { set; get; }
     public int var10 { set; get; }
     public int var11 { set; get; }
     public int var12 { set; get; }
     public int var13 { set; get; }
     public int var14 { set; get; }
 }
 private static object returnProps(string propName, List curObject)
 {
     if (propName == "var")
     {
         return curObject.var;
     }
     else if (propName == "var2")
     {
         return curObject.var2;
     }
     else if (propName == "var3")
     {
         return curObject.var3;
     }
     else if (propName == "var4")
     {
         return curObject.var4;
     }
     else if (propName == "var5")
     {
         return curObject.var5;
     }
     else if (propName == "var6")
     {
         return curObject.var6;
     }
     else if (propName == "var7")
     {
         return curObject.var7;
     }
     else if (propName == "var8")
     {
         return curObject.var8;
     }
     else if (propName == "var9")
     {
         return curObject.var9;
     }
     else if (propName == "var10")
     {
         return curObject.var10;
     }
     else if (propName == "var11")
     {
         return curObject.var11;
     }
     else if (propName == "var12")
     {
         return curObject.var12;
     }
     else if (propName == "var13")
     {
         return curObject.var13;
     }
     else if (propName == "var14")
     {
         return curObject.var14;
     }

     return new object();
 }

最后一点:我希望人们能够更普遍地理解令人印象深刻的结果,而不仅仅应用于.GetValue。如今,计算机可以处理许多事情,您不需要最大化每个单独位的性能,这是正确的。另一方面,如果您有性能问题并且需要以更相关的方式“节省资源”,则应将改进重点放在“越简单,越快”的想法上。我自己使用了相当数量的ListsDictionaries来改进代码的性能,即使是每个单独的更改(从List转换为传统的Array),结果也是明显的。在这方面,您不需要过于警惕,但是,如果需要,请记住,与Array相比,List的存储器消耗/关联时间要求更高(而且两个元素基本相同)。对于多维数组,长长度的数组等也是如此。

------ 更详细的性能分析

尽管我从一开始就表达了我的观点(只是一个必须适应每种情况的想法),但我确实理解我的声明(快10倍)需要适当的定义。我已经在不同条件下进行了测试,以下是结果:

注意:上述结果是由32位可执行文件输出的;下面所有结果均来自64位可执行文件。我发现从32位到64位的转换可以提高.GetValue性能。更新后的64位版本结果为(毫秒):

                      GetValue       Direct Assignation     
50000 iterations ->    1197                 157
80000 iterations ->    1922                 253
100000 iterations ->   2354                 310

因此,比率从10倍变为7.5倍。
我开始增加属性的数量(每次在64位上),GetValue 变得越来越好。结果:
28 Properties
                          GetValue       Direct Assignation     
    50000 iterations ->    2386                552
    80000 iterations ->    3857                872

Aver. ratio = 4.37

50 Properties
                          GetValue       Direct Assignation     
    50000 iterations ->    4292                1707
    80000 iterations ->    6772                2711

Aver. ratio = 2.475

我不确定 GetValue 的改进是否会继续,并且是否会达到比简单方法更好的程度,但谁在乎呢?此时,很明显,属性数量的增加对简单方法不利,因此是时候尝试另一种(同样相当简单的)替代方案了:全局数组存储所有属性。
  private static int[,] List0;

在给定属性的同时并行填充(即当 object.propX = any value 时,数组中相应的位置也会被填充),并且由对象/属性位置引用(第一个对象,第三个属性等)。从逻辑上讲,这具有对象数量的限制(将第一维度增加到1000以上似乎不可取),但是您可以依赖不同的数组(一个存储从第一个对象到第1000个对象,另一个存储从第1001个到第2000个对象等);您可以设置一个函数,以对象名称为参数,并返回相应的数组。

主循环中的修改:

int countObject = -1;
foreach (var item in objects)
{
    countObject = countObject + 1;
    int countProp = -1;
    foreach (var p in props)
    {
        countProp = countProp + 1;
        object returnData = List0[countObject, countProp];
    }
}

通过在上述情况下运行这种新方法,我得到了:
50 Properties
                         GetValue           2D Array    
   80000 iterations ->    6772                155

Aver. ratio = 45.146

再来一个:

70 Properties
                          GetValue          2D Array     
    80000 iterations ->    10444               213

Aver. ratio = 49.06

我在这里停止了测试。我想这已经足以证明我的观点。

不同的方法在不同的条件下会产生不同的性能,因此了解适合某种情况的理想配置的最佳方式实际上是进行测试。依赖于终极真理很少是解决问题的最佳方案(虽然我可能错了...仍在等待DmitryG的回复,以在不同的条件下测试他的解决方案)。因此,在测试条件下,似乎原始的简单方法对于属性数量相对较少的情况(即低于20)是可接受的;在20个以上的情况下,所需的硬编码工作似乎不值得,依赖于不同的替代方案(如我提出的二维数组)更好。无论如何,GetValue的性能表现都很差,可以通过许多不同的方式进行改进。

希望我不需要再更新这个答案 :)


@Xaisoft,因为每个人都带着新的想法和实际成果而来(我开玩笑地说这很烂等),所以我更新了我的回答(最后一段)来强调这种方法实际上代表了什么:只需做这一点就可以实现90%的改进。我想你已经明白了,但以防万一...有时候我开玩笑开得太多,无法传达重点。 - varocarbas
不要只是建议人们放弃ListDictionary,如果你不让它增长太多(你需要事先知道数组的大小,所以没有好处),前者具有良好的性能。Dictionary在正确使用时远远优于前者(在这种情况下作为两个对象的简单包装器,因此不是理想的选择,但也不是坏选择)。永远不要使用临时文件,文件系统非常慢。我敢打赌他不会得到很好的性能,他的示例有800k个项目要查看,而你的只有20个。在这些大小下,内存分页非常重要。 - Guvante
@Guvante,你对于列表和字典的看法是正确的,我可能有点过于危言耸听了,我会在这方面纠正我的回答。关于临时文件,我之前提到它只是为了支持我的“字典和列表不好”的想法,同样也有点过于危言耸听了。唯一让我有些不理解的是你最后的陈述:我的代码进行了简单的比较(调用这个函数X次并将时间要求与另一个函数进行比较),结果差别惊人(快了10倍),这是毋庸置疑的。同时请注意,我的测试花费了2276ms,而原始测试只需要900ms。 - varocarbas
@Guvante 我已经更新了我的回答。希望现在的想法更清晰了。 - varocarbas
@Guvante 我已经向OP提供了测试我的方法并告诉结果的机会。无论如何,我非常确定确切的框架对问题的位置没有任何影响,因为问题位于单行中。在我的测试中,两种方法完全相同(例如,通过反射获取属性),直到这一行。在此行之前或之后所做的任何事情都不会影响此行所做的事情(或者这是我的假设)。无论如何,我想最好的方法是在实际条件下进行测试:实现我的方法只需要1分钟。 - varocarbas
显示剩余7条评论

0

继续上面的帖子: 您的代码构建了一个属性名称和(格式化的)值的字典。 因此,我们只需要一个列表作为输入。从 T 中,我们可以得出所有信息。

    public Dictionary<string, object> ExtractParameterNameAndValue<T>(List<T> colleciton)
        where T : class
    {
        var result = new Dictionary<string, object>();

        // out of the loop - generate getters
        var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        var getterList = new List<Func<T,object>>();
        foreach (var p in properties)
        {
            getterList.Add(MyStatic.BuildUntypedGetter<T>(p));
        }

        // Array of getters
        var getters = getterList.ToArray(); // improving performance (?) - never use Dictionary
        // Corresponding array of Names
        var names = properties.Select(p => p.Name).ToArray();

        // iterate all data
        int counter = 0;
        foreach (var item in colleciton)
        {
            for (int i = 0; i< getters.Length; i++)
            {
                var name = names[i]; // name from property
                var value = getters[i](item);  // value from getter-call
                result.Add(counter + " " + name, value); 
            }
            counter++;
        }

        return result; ;
    }

方法BuildUntypedGetter()类似于

   // see http://flurfunk.sdx-ag.de/2012/05/c-performance-bei-der-befullungmapping.html
   public static Func<T, object> BuildUntypedGetter<T>(PropertyInfo propertyInfo)
    {
        var targetType = propertyInfo.DeclaringType;
        var methodInfo = propertyInfo.GetGetMethod();
        var returnType = methodInfo.ReturnType;

        var exTarget = Expression.Parameter(targetType, "t");
        var exBody = Expression.Call(exTarget, methodInfo);
        var exBody2 = Expression.Convert(exBody, typeof(object));

        var lambda = Expression.Lambda<Func<T, object>>(exBody2, exTarget);

        var action = lambda.Compile();
        return action;
    }

在调用时不需要指定类型,它会被类型推断检测出来。

       var accountList = new List<Account>()
        {
            new Account { Name = "X1", Name2 ="X2"},
            new Account { Name = "X3", Name2 ="X4"},
            new Account { Name = "X5", Name2 ="X6"},
        };

        var result = ExtractParameterNameAndValue(accountList);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接