2010-07-09 7 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); 
        } 
       } 
      } 
     } 
    } 

À l'heure actuelle, cela fonctionne très bien pour les objets avec des listes relativement petites, mais une fois que la liste des objets est plus grande, elle prend une éternité. Comment puis-je optimiser cela et également définir une limite pour une profondeur maximale.Comment optimiser cette méthode

Merci pour toute aide

+1

Pourriez-vous définir "plus grand" et "prend une éternité"? Est-il possible que ce soit récurent pour toujours ou quelque chose de similaire? –

+0

Je pense que c'est récurent sur des choses qu'il ne devrait pas. Par exemple il y a une table d'étudiant qui a une table de contact et une table d'enquête. Si je fais ConvertToUpper (enquête d'enquête), il ressemble à son enquête en cours ainsi que tous les étudiants qui ont pris l'enquête et leurs adresses. – AlteredConcept

Répondre

1

Il me semble assez maigre. La seule chose que je peux penser serait de paralléliser ceci. Si j'ai l'occasion, j'essaierai de trouver quelque chose et d'éditer ma réponse.

Voici comment limiter la profondeur.

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

Voici un blog de code qui devrait fonctionner pour appliquer la limite de la profondeur maximale que Brian Gideon mentionné ainsi que les choses parallèles un peu. Ce n'est pas parfait et pourrait être affiné un peu puisque j'ai cassé les types de valeur et les propriétés de type non-valeur en 2 requêtes 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: Très cool. Cela m'a sauvé la peine! –

1

Ce que vous pouvez faire est d'avoir un Dictionary avec un type comme les principales propriétés et pertinentes que les valeurs. Vous aurez seulement besoin de chercher une fois dans les propriétés pour celles qui vous intéressent (par l'apparence des choses IEnumerable et string) - après tout, les propriétés des types ne vont pas changer (sauf si vous en faites funky Emit choses mais je ne suis pas trop familier avec cela)

Une fois que vous avez cela, vous pouvez simplement itérer toutes les propriétés dans le Dictionary en utilisant le type d'objets comme la clé.

somehting comme ça (je ne l'ai pas fait testé mais il complile :))

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

Il y a quelques problèmes immédiats:

  1. On répète l'évaluation de l'information de propriété pour ce que je suppose que sont les mêmes types.

  2. La réflexion est relativement lente.

problème 1. peut être résolu par memoizing informations de propriété sur les types et la mise en cache de sorte qu'il ne doit pas être recalculée pour chaque type récurrent que nous voyons.

Les performances du problème 2. peuvent être améliorées en utilisant la génération de code IL et les méthodes dynamiques. J'ai saisi du code de here pour implémenter dynamiquement (et également mémorisé à partir du point 1.) des appels générés et très efficaces pour obtenir et définir des valeurs de propriété. Fondamentalement, le code IL est généré dynamiquement pour appeler et obtenir une propriété et encapsulé dans un wrapper de méthode - cela contourne toutes les étapes de réflexion (et certaines vérifications de sécurité ...). Lorsque le code suivant fait référence à "DynamicProperty", j'ai utilisé le code du lien précédent.

Cette méthode peut également être parallélisée comme suggéré par d'autres, assurez-vous simplement que le cache "visité" et le cache de propriétés calculé sont synchronisés.

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

Bien que votre question porte sur la performance du code, il y a un autre problème que les autres semblent manquer: maintenabilité. Bien que vous pensiez que ce n'est pas aussi important que le problème de performance que vous rencontrez, avoir un code plus lisible et plus facile à utiliser facilitera la résolution des problèmes.

Voici un exemple de la façon dont votre code pourrait ressembler, après quelques refactoring:

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

Bien que ce code est plus de deux fois plus longtemps que l'extrait de code, il est plus maintenable. Dans le processus de refactorisation de votre code, j'ai même trouvé un bug possible dans votre code. Ce bug est beaucoup plus difficile à repérer dans votre code. Dans votre code, vous essayez de convertir toutes les valeurs de chaîne en majuscules, mais vous ne convertissez pas les valeurs de chaîne stockées dans les propriétés de l'objet. Regardez par exemple au code suivant. Peut-être que c'est exactement ce que vous vouliez, mais la chaîne "Bonjour" n'est pas convertie en "BONJOUR" dans votre code.

Une autre chose que j'aime noter est que bien que la seule chose que j'ai essayé de faire est de rendre votre code plus lisible, mon refactoring semble environ 20% plus rapide. Après avoir refaçonné le code, j'ai essayé d'améliorer les performances de celui-ci, mais j'ai découvert qu'il est particulièrement difficile de l'améliorer. Alors que d'autres essayent de paralléliser le code, je dois en avertir. Paralléliser le code n'est pas aussi facile que d'autres pourraient vous laisser penser. Il y a une synchronisation entre les threads (sous la forme de la collection 'visited'). N'oubliez pas que l'écriture dans une collection n'est pas sûre pour les threads. L'utilisation d'une version thread-safe ou le verrouillage risque de dégrader à nouveau les performances. Vous devrez tester cela.

J'ai également découvert que le goulot d'étranglement des performances réelles est tout le reflet (en particulier la lecture de toutes les valeurs de propriété). La seule façon d'accélérer vraiment cela est de coder en dur les opérations de code pour chaque type, ou comme d'autres suggèrent la génération de code léger. Cependant, c'est assez difficile et il est douteux que cela en vaille la peine.

J'espère que vous trouverez mes refactorings utiles et je vous souhaite bonne chance avec l'amélioration des performances.

+0

L'utilisation de la technique de génération de code décrite dans ma réponse m'a donné une augmentation de 600%. Ajoutez à cela le lien vers le code de génération de code prêt à l'emploi. Pas trop dur dans ce cas. –

+0

@Chibacity: J'ai fait un petit test en utilisant 'CreateSetMethod' et' CreateGetMethod' (et en mettant en cache la création de ceux-ci) et j'ai obtenu une amélioration de 300% par rapport à mon exemple refactorisé. C'est gentil en effet. Ce à quoi je pensais était de générer des méthodes basées sur un type, au lieu d'une seule propriété. Ce serait encore plus rapide, mais c'est aussi beaucoup plus difficile. Bien sûr, cela dépend de la performance dont vous avez besoin pour en tirer parti, des problèmes que vous êtes prêt à traverser. – Steven

+0

Vous devriez lire ma réponse. Je fais exactement cela. –

2

Je n'ai pas profilé le code suivant, mais il doit être très performant sur des structures complexes.

1) Utilise la génération de code dynamique.

2) Utilise le cache basé sur le type pour les délégués dynamiques générés.

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

Veuillez noter que' Expression.Assign' est nouveau dans .NET 4.0. – Steven

Questions connexes