如何优化此方法

时间:2010-07-09 16:26:37

标签: reflection refactoring c#-4.0

 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);
                    }
                }
            }
        }
    }

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

感谢您的帮助

6 个答案:

答案 0 :(得分: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();
  }
}

答案 1 :(得分: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);
}

答案 2 :(得分:1)

这是一个代码博客,可以应用Brian Gideon提到的Max Depth限制以及稍微平行的事情。它不完美,可以稍微改进一下,因为我将值类型和非值类型属性分解为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);
                }
            });
        }

答案 3 :(得分: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);
                    }
                }
            }
        }
    }

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); } } } } }

答案 4 :(得分:1)

有几个直接问题:

  1. 对我所假设的属性信息进行了多次评估。

  2. 反思相对缓慢。

  3. 问题1.可以通过记忆关于类型的属性信息并将其缓存来解决,这样就不必为我们看到的每种重复类型重新计算。

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

    此方法也可以按其他人的建议进行并行化,只需确保“已访问”缓存和计算属性缓存已同步。

    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;
    }                
    

答案 5 :(得分: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();
    }
}

虽然此代码的长度是代码段的两倍多,但它更易于维护。在重构代码的过程中,我甚至在代码中发现了一个可能的错误。在您的代码中发现此错误要困难得多。在您的代码中,您尝试将所有字符串值转换为大写,但不转换存储在对象属性中的字符串值。请查看以下代码。

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

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

也许这正是您想要的,但字符串“Hello”在您的代码中未转换为“HELLO”。

我还要注意的另一件事是,虽然我唯一想做的就是让你的代码更具可读性,但我的重构速度似乎要快20%。

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

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

我希望你发现我的重构很有用,祝你好运,提高性能。