2010-07-09 82 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