How to optimize this method

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

At the moment, it works great for objects with relatively small lists, but as soon as the list of objects becomes larger, it takes forever. How to optimize this, as well as set a limit for maximum depth.

Thanks for any help

+3
source share
6 answers

I have not commented on the following code, but it should be very effective in complex structures.

1) Uses dynamic code generation.

2) Uses type cache for generated dynamic delegates.

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();
  }
}
+2
source

. , , . , - .

.

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

, , 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);
                }
            });
        }
+1

, Dictionary . , ( IEnumerable string). , , ( " , )

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 IEnumerable
                {
                    foreach (object value in (IEnumerable)propertyValue)
                    {
                        ConvertToUpper(value, visited);
                    }
                }
            }
        }
    }

>

+1

:

  • , , .

  • .

1. , , .

2. IL . , ( memoised 1.) . IL- - ( ...). "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;
}                
+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% .

, , , , . , . , . ( "" ). , . - . .

I also found that the real bottleneck in performance is all reflection (especially reading all property values). The only way to really speed it up is by hard coding the code operations for each type, or, like others, they suggested easy code generation. However, it is rather complicated, and it is doubtful whether it is worth worrying.

I hope you find my refactoring useful and wish you good luck in improving your performance.

+1
source

Source: https://habr.com/ru/post/1753910/


All Articles