2010-07-09 76 views
3
private static void ConvertToUpper(object entity, Hashtable visited) 
    { 
     if (entity != null && !visited.ContainsKey(entity)) 
     { 
      visited.Add(entity, entity); 

      foreach (PropertyInfo propertyInfo in entity.GetType().GetProperties()) 
      { 
       if (!propertyInfo.CanRead || !propertyInfo.CanWrite) 
        continue; 

       object propertyValue = propertyInfo.GetValue(entity, null); 

       Type propertyType; 
       if ((propertyType = propertyInfo.PropertyType) == typeof(string)) 
       { 
        if (propertyValue != null && !propertyInfo.Name.Contains("password")) 
        { 
         propertyInfo.SetValue(entity, ((string)propertyValue).ToUpper(), null); 
        } 
        continue; 
       } 

       if (!propertyType.IsValueType) 
       { 
        IEnumerable enumerable; 
        if ((enumerable = propertyValue as IEnumerable) != null) 
        { 
         foreach (object value in enumerable) 
         { 
          ConvertToUpper(value, visited); 
         } 
        } 
        else 
        { 
         ConvertToUpper(propertyValue, visited); 
        } 
       } 
      } 
     } 
    } 

现在,它适用于列表相对较小的对象,但一旦对象列表变得更大,它需要永远。我将如何优化这一点,并设置最大深度的限制。如何优化这种方法

感谢您的任何帮助

+1

你能定义“更大”和“永远”吗?它有可能永久地递归或者类似的东西? – 2010-07-09 16:36:35

+0

我认为它是递归的东西,它不应该。例如,一个学生表有一个相关联系表和一个调查表。 如果我做ConvertToUpper(调查调查),它看起来像正在进行调查以及所有已接受调查的学生和他们的地址。 – AlteredConcept 2010-07-12 15:29:08

回答

1

它看起来很瘦我。我能想到的唯一的事情就是将其并行化。如果我有机会,我会尝试解决问题并编辑我的答案。

以下是如何限制深度。

private static void ConvertToUpper(object entity, Hashtable visited, int depth) 
{ 
    if (depth > MAX_DEPTH) return; 

    // Omitted code for brevity. 

    // Example usage here. 
    ConvertToUppder(..., ..., depth + 1); 
} 
1

这里的代码应该工作申请布赖恩基甸提到的以及平行事情有点的最大深度限制了博客。这并不完美,因为我将值类型和非值类型属性分解为2个linq查询,所以可以稍微改进一点。

private static void ConvertToUpper(object entity, Hashtable visited, int depth) 
     { 
      if (entity == null || visited.ContainsKey(entity) || depth > MAX_DEPTH) 
      { 
       return; 
      } 

      visited.Add(entity, entity); 

      var properties = from p in entity.GetType().GetProperties() 
             where p.CanRead && 
                p.CanWrite && 
                p.PropertyType == typeof(string) && 
                !p.Name.Contains("password") && 
                p.GetValue(entity, null) != null 
             select p; 

      Parallel.ForEach(properties, (p) => 
      { 
       p.SetValue(entity, ((string)p.GetValue(entity, null)).ToUpper(), null); 
      }); 

      var valProperties = from p in entity.GetType().GetProperties() 
          where p.CanRead && 
             p.CanWrite && 
             !p.PropertyType.IsValueType && 
             !p.Name.Contains("password") && 
             p.GetValue(entity, null) != null 
          select p; 

      Parallel.ForEach(valProperties, (p) => 
      { 
       if (p.GetValue(entity, null) as IEnumerable != null) 
       { 
        foreach(var value in p.GetValue(entity, null) as IEnumerable) 
         ConvertToUpper(value, visted, depth +1); 
       } 
       else 
       { 
        ConvertToUpper(p, visited, depth +1); 
       } 
      }); 
     } 
+0

+1:非常酷。这为我省去了麻烦! – 2010-07-09 16:55:59

1

你可以做的是有一个Dictionary与类型为关键和相关属性的值。然后,您只需要搜索一次您感兴趣的属性(通过IEnumerablestring的外观) - 毕竟,类型的属性不会改变(除非您正在做一些时髦的Emit的东西,但我不太熟悉)

一旦你有了这个,你可以简单地迭代Dictionary使用对象类型作为关键所有属性。

服用点是这样的(我没有实际测试过它,但它确实请编译:))

private static Dictionary<Type, List<PropertyInfo>> _properties = new Dictionary<Type, List<PropertyInfo>>(); 

    private static void ExtractProperties(List<PropertyInfo> list, Type type) 
    { 
     if (type == null || type == typeof(object)) 
     { 
      return; // We've reached the top 
     } 

     // Modify which properties you want here 
     // This is for Public, Protected, Private 
     const BindingFlags PropertyFlags = BindingFlags.DeclaredOnly | 
              BindingFlags.Instance | 
              BindingFlags.NonPublic | 
              BindingFlags.Public; 

     foreach (var property in type.GetProperties(PropertyFlags)) 
     { 
      if (!property.CanRead || !property.CanWrite) 
       continue; 

      if ((property.PropertyType == typeof(string)) || 
       (property.PropertyType.GetInterface("IEnumerable") != null)) 
      { 
       if (!property.Name.Contains("password")) 
       { 
        list.Add(property); 
       } 
      } 
     } 

     // OPTIONAL: Navigate the base type 
     ExtractProperties(list, type.BaseType); 
    } 

    private static void ConvertToUpper(object entity, Hashtable visited) 
    { 
     if (entity != null && !visited.ContainsKey(entity)) 
     { 
      visited.Add(entity, entity); 

      List<PropertyInfo> properties; 
      if (!_properties.TryGetValue(entity.GetType(), out properties)) 
      { 
       properties = new List<PropertyInfo>(); 
       ExtractProperties(properties, entity.GetType()); 
       _properties.Add(entity.GetType(), properties); 
      } 

      foreach (PropertyInfo propertyInfo in properties) 
      { 
       object propertyValue = propertyInfo.GetValue(entity, null); 

       Type propertyType = propertyInfo.PropertyType; 
       if (propertyType == typeof(string)) 
       { 
        propertyInfo.SetValue(entity, ((string)propertyValue).ToUpper(), null); 
       } 
       else // It's IEnumerable 
       { 
        foreach (object value in (IEnumerable)propertyValue) 
        { 
         ConvertToUpper(value, visited); 
        } 
       } 
      } 
     } 
    } 

1

有一对夫妇的紧迫问题:

  1. 有重复评价我所承担的财产信息是相同的类型。

  2. 反射比较慢。

问题1.可以通过memoizing关于类型的属性信息和缓存它,所以它不必重新计算每个重复的类型,我们看到来解决。

通过使用IL代码生成和动态方法可以帮助解决问题2的性能问题。我从here中抓取代码以实现动态(也从第1点开始记忆)生成的和高效的获取和设置属性值的调用。基本上,IL代码是动态生成的,可以调用set并获取属性并封装在方法包装器中 - 绕过所有反射步骤(以及一些安全检查...)。在下面的代码引用“DynamicProperty”的地方,我已经使用了上一个链接中的代码。

这种方法也可以像其他人所建议的那样并行化,只要确保“visited”缓存和计算属性缓存同步即可。

private static readonly Dictionary<Type, List<ProperyInfoWrapper>> _typePropertyCache = new Dictionary<Type, List<ProperyInfoWrapper>>(); 

private class ProperyInfoWrapper 
{ 
    public GenericSetter PropertySetter { get; set; } 
    public GenericGetter PropertyGetter { get; set; } 
    public bool IsString { get; set; } 
    public bool IsEnumerable { get; set; } 
} 

private static void ConvertToUpper(object entity, Hashtable visited) 
{ 
    if (entity != null && !visited.Contains(entity)) 
    { 
     visited.Add(entity, entity); 

     foreach (ProperyInfoWrapper wrapper in GetMatchingProperties(entity)) 
     { 
      object propertyValue = wrapper.PropertyGetter(entity); 

      if(propertyValue == null) continue; 

      if (wrapper.IsString) 
      { 
       wrapper.PropertySetter(entity, (((string)propertyValue).ToUpper())); 
       continue; 
      } 

      if (wrapper.IsEnumerable) 
      { 
       IEnumerable enumerable = (IEnumerable)propertyValue; 

       foreach (object value in enumerable) 
       { 
        ConvertToUpper(value, visited); 
       } 
      } 
      else 
      { 
       ConvertToUpper(propertyValue, visited); 
      } 
     } 
    } 
} 

private static IEnumerable<ProperyInfoWrapper> GetMatchingProperties(object entity) 
{ 
    List<ProperyInfoWrapper> matchingProperties; 

    if (!_typePropertyCache.TryGetValue(entity.GetType(), out matchingProperties)) 
    { 
     matchingProperties = new List<ProperyInfoWrapper>(); 

     foreach (PropertyInfo propertyInfo in entity.GetType().GetProperties()) 
     { 
      if (!propertyInfo.CanRead || !propertyInfo.CanWrite) 
       continue; 

      if (propertyInfo.PropertyType == typeof(string)) 
      { 
       if (!propertyInfo.Name.Contains("password")) 
       { 
        ProperyInfoWrapper wrapper = new ProperyInfoWrapper 
        { 
         PropertySetter = DynamicProperty.CreateSetMethod(propertyInfo), 
         PropertyGetter = DynamicProperty.CreateGetMethod(propertyInfo), 
         IsString = true, 
         IsEnumerable = false 
        }; 

        matchingProperties.Add(wrapper); 
        continue; 
       } 
      } 

      if (!propertyInfo.PropertyType.IsValueType) 
      { 
       object propertyValue = propertyInfo.GetValue(entity, null); 

       bool isEnumerable = (propertyValue as IEnumerable) != null; 

       ProperyInfoWrapper wrapper = new ProperyInfoWrapper 
       { 
        PropertySetter = DynamicProperty.CreateSetMethod(propertyInfo), 
        PropertyGetter = DynamicProperty.CreateGetMethod(propertyInfo), 
        IsString = false, 
        IsEnumerable = isEnumerable 
       }; 

       matchingProperties.Add(wrapper); 
      } 
     } 

     _typePropertyCache.Add(entity.GetType(), matchingProperties); 
    } 

    return matchingProperties; 
}     
1

虽然你的问题是关于代码的性能,还有另一个问题,即其他人似乎错过:可维护性。

虽然您可能认为这不像您遇到的性能问题那么重要,但让代码更具可读性和可维护性,可以更轻松地解决问题。

这里是你的代码可能看起来怎么样,几个重构之后的示例:

class HierarchyUpperCaseConverter 
{ 
    private HashSet<object> visited = new HashSet<object>(); 

    public static void ConvertToUpper(object entity) 
    { 
     new HierarchyUpperCaseConverter_v1().ProcessEntity(entity); 
    } 

    private void ProcessEntity(object entity) 
    { 
     // Don't process null references. 
     if (entity == null) 
     { 
      return; 
     } 

     // Prevent processing types that already have been processed. 
     if (this.visited.Contains(entity)) 
     { 
      return; 
     } 

     this.visited.Add(entity); 

     this.ProcessEntity(entity); 
    } 

    private void ProcessEntity(object entity) 
    { 
     var properties = 
      this.GetProcessableProperties(entity.GetType()); 

     foreach (var property in properties) 
     { 
      this.ProcessEntityProperty(entity, property); 
     } 
    } 

    private IEnumerable<PropertyInfo> GetProcessableProperties(Type type) 
    { 
     var properties = 
      from property in type.GetProperties() 
      where property.CanRead && property.CanWrite 
      where !property.PropertyType.IsValueType 
      where !(property.Name.Contains("password") && 
       property.PropertyType == typeof(string)) 
      select property; 

     return properties; 
    } 

    private void ProcessEntityProperty(object entity, PropertyInfo property) 
    { 
     object value = property.GetValue(entity, null); 

     if (value != null) 
     { 
      if (value is IEnumerable) 
      { 
       this.ProcessCollectionProperty(value as IEnumerable); 
      } 
      else if (value is string) 
      { 
       this.ProcessStringProperty(entity, property, (string)value); 
      } 
      else 
      { 
       this.AlterHierarchyToUpper(value); 
      } 
     } 
    } 

    private void ProcessCollectionProperty(IEnumerable value) 
    { 
     foreach (object item in (IEnumerable)value) 
     { 
      // Make a recursive call. 
      this.AlterHierarchyToUpper(item); 
     } 
    } 

    private void ProcessStringProperty(object entity, PropertyInfo property, string value) 
    { 
     string upperCaseValue = ConvertToUpperCase(value); 

     property.SetValue(entity, upperCaseValue, null); 
    } 

    private string ConvertToUpperCase(string value) 
    { 
     // TODO: ToUpper is culture sensitive. 
     // Shouldn't we use ToUpperInvariant? 
     return value.ToUpper(); 
    } 
} 

尽管此代码是超过两倍,只要您的代码段,更是更易于维护。在重构你的代码的过程中,我甚至在代码中发现了一个可能的错误。这个bug在你的代码中很难找到。在您的代码中,您尝试将所有字符串值转换为大写,但不转换存储在对象属性中的字符串值。在下面的代码中查找实例。

class A 
{ 
    public object Value { get; set; } 
} 

var a = new A() { Value = "Hello" }; 

也许这正是你想要的,但字符串“你好”没有在你的代码中转换为“你好”。

我还想指出的另一件事是,尽管我试图做的唯一事情就是让代码更具可读性,但我的重构似乎快了20%左右。

在我重构代码之后,我试图改进它的性能,但是我发现它很难改进。当其他人试图并行化代码时,我必须警告这一点。并行化代码并不像别人想象的那么容易。线程之间有一些同步(以'visited'集合的形式)。不要忘记写入集合不是线程安全的。使用线程安全版本或对其进行锁定可能会再次降低性能。你将不得不测试这个。

我还发现真正的性能瓶颈是所有的反射(特别是读取所有的属性值)。要真正加速这一进程的唯一方法是对每种类型的代码操作进行硬编码,或者像其他人提出的轻量级代码生成一样。然而,这是非常困难的,它是否值得麻烦是值得怀疑的。

我希望你能找到我的重构,并祝你好运,提高性能。

+0

使用我的答案中描述的代码生成技术使我的perf增加了600%。再加上代码生成代码的链接就可以了。在这种情况下不太难。 – 2010-07-10 00:13:36

+0

@Chibacity:我使用'CreateSetMethod'和'CreateGetMethod'(并缓存了它们的创建)做了一些小测试,并且比我的重构示例马上有了300%的改进。确实很好。我在想的是基于类型生成方法,而不是单个属性。这会更快,但也更难。当然,这取决于你需要多少表现来挤掉它,你愿意承受多少麻烦。 – Steven 2010-07-11 10:19:50

+0

你应该阅读我的答案。我正是那样做的。 – 2010-07-11 16:26:39

2

我没有分析下面的代码,但它在复杂结构中必须非常高效。

1)使用动态代码生成。

2)为生成的动态代理使用基于类型的缓存。

public class VisitorManager : HashSet<object> 
{ 
    delegate void Visitor(VisitorManager manager, object entity); 

    Dictionary<Type, Visitor> _visitors = new Dictionary<Type, Visitor>(); 

    void ConvertToUpperEnum(IEnumerable entity) 
    { 
    // TODO: this can be parallelized, but then we should thread-safe lock the cache 
    foreach (var obj in entity) 
     ConvertToUpper(obj); 
    } 

    public void ConvertToUpper(object entity) 
    { 
    if (entity != null && !Contains(entity)) 
    { 
     Add(entity); 

     var visitor = GetCachedVisitor(entity.GetType()); 

     if (visitor != null) 
     visitor(this, entity); 
    } 
    } 

    Type _lastType; 
    Visitor _lastVisitor; 

    Visitor GetCachedVisitor(Type type) 
    { 
    if (type == _lastType) 
     return _lastVisitor; 

    _lastType = type; 

    return _lastVisitor = GetVisitor(type); 
    } 

    Visitor GetVisitor(Type type) 
    { 
    Visitor result; 

    if (!_visitors.TryGetValue(type, out result)) 
     _visitors[type] = result = BuildVisitor(type); 

    return result; 
    } 

    static MethodInfo _toUpper = typeof(string).GetMethod("ToUpper", new Type[0]); 
    static MethodInfo _convertToUpper = typeof(VisitorManager).GetMethod("ConvertToUpper", BindingFlags.Instance | BindingFlags.Public); 
    static MethodInfo _convertToUpperEnum = typeof(VisitorManager).GetMethod("ConvertToUpperEnum", BindingFlags.Instance | BindingFlags.NonPublic); 

    Visitor BuildVisitor(Type type) 
    { 
    var visitorManager = Expression.Parameter(typeof(VisitorManager), "manager"); 
    var entityParam = Expression.Parameter(typeof(object), "entity"); 

    var entityVar = Expression.Variable(type, "e"); 
    var cast = Expression.Assign(entityVar, Expression.Convert(entityParam, type)); // T e = (T)entity; 

    var statements = new List<Expression>() { cast }; 

    foreach (var prop in type.GetProperties()) 
    { 
     // if cannot read or cannot write - ignore property 
     if (!prop.CanRead || !prop.CanWrite) continue; 

     var propType = prop.PropertyType; 

     // if property is value type - ignore property 
     if (propType.IsValueType) continue; 

     var isString = propType == typeof(string); 

     // if string type but no password in property name - ignore property 
     if (isString && !prop.Name.Contains("password")) 
     continue; 

     #region e.Prop 

     var propAccess = Expression.Property(entityVar, prop); // e.Prop 

     #endregion 

     #region T value = e.Prop 

     var value = Expression.Variable(propType, "value"); 
     var assignValue = Expression.Assign(value, propAccess); 

     #endregion 

     if (isString) 
     { 
     #region if (value != null) e.Prop = value.ToUpper(); 

     var ifThen = Expression.IfThen(Expression.NotEqual(value, Expression.Constant(null, typeof(string))), 
      Expression.Assign(propAccess, Expression.Call(value, _toUpper))); 

     #endregion 

     statements.Add(Expression.Block(new[] { value }, assignValue, ifThen)); 
     } 
     else 
     { 
     #region var i = value as IEnumerable; 

     var enumerable = Expression.Variable(typeof(IEnumerable), "i"); 

     var assignEnum = Expression.Assign(enumerable, Expression.TypeAs(value, enumerable.Type)); 

     #endregion 

     #region if (i != null) manager.ConvertToUpperEnum(i); else manager.ConvertToUpper(value); 

     var ifThenElse = Expression.IfThenElse(Expression.NotEqual(enumerable, Expression.Constant(null)), 
     Expression.Call(visitorManager, _convertToUpperEnum, enumerable), 
     Expression.Call(visitorManager, _convertToUpper, value)); 

     #endregion 

     statements.Add(Expression.Block(new[] { value, enumerable }, assignValue, assignEnum, ifThenElse)); 
     } 
    } 

    // no blocks 
    if (statements.Count <= 1) 
     return null; 

    return Expression.Lambda<Visitor>(Expression.Block(new[] { entityVar }, statements), visitorManager, entityParam).Compile(); 
    } 
} 
+0

请注意'Expression.Assign'在.NET 4.0中是新增的。 – Steven 2010-07-14 06:15:23