2011-12-22 33 views
1

我有這種方法稱爲MatchNodes:IEnumerable<bool> MatchNodes<T>(T n1, T n2)而通過反射在c#爬行內部的對象防止堆棧溢出

這基本上會從兩個T對象的每個屬性和字段(通過反射,並且不包括從基類屬性/字段)並比較它們,將結果作爲IEnumerable的bools返回。

當它找到一個原始類型或字符串時,如果只是在它們之間返回==

當它找到從集合派生的類型時,它會迭代每個成員併爲其中的每個成員調用MatchNodes(ouch)。

當它找到任何其他類型時,它會爲每個屬性/字段調用MatchNodes

我的解決方案顯然是要求一個堆棧溢出異常,但我不知道如何使它變得更好,因爲我不知道對象有多深。

代碼(儘量不要哭請,這是醜陋的地獄)

public static IEnumerable<bool> MatchNodes<T>(T n1, T n2) 
    { 
     Func<PropertyInfo, bool> func= null; 

     if (typeof(T) == typeof(String)) 
     { 
      String str1 = n1 as String; 
      String str2 = n2 as String; 
      func = new Func<PropertyInfo, bool>((property) => str1 == str2); 
     } 
     else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(typeof(T))) 
     { 
      System.Collections.IEnumerable e1 = (System.Collections.IEnumerable)n1; 
      System.Collections.IEnumerable e2 = (System.Collections.IEnumerable)n2; 
      func = new Func<PropertyInfo, bool>((property) => 
      { 
       foreach (var v1 in e1) 
       { 
        if (e2.GetEnumerator().MoveNext()) 
        { 
         var v2 = e2.GetEnumerator().Current; 
         if (((IEnumerable<bool>)MatchNodes(v1, v2)).All(b => b == true)) 
         { 
          return false; 
         } 
        } 
        else 
        { 
         return false; 
        } 
       } 
       if (e2.GetEnumerator().MoveNext()) 
       { 
        return false; 
       } 
       else return true; 
      }); 
     } 
     else if (typeof(T).IsPrimitive || typeof(T) == typeof(Decimal)) 
     { 
      func = new Func<PropertyInfo, bool>((property) => property.GetValue(n1, null) == property.GetValue(n2, null)); 
     } 
     else 
     { 
      func = new Func<PropertyInfo, bool>((property) => 
        ((IEnumerable<bool>)MatchNodes(property.GetValue(n1, null), 
        property.GetValue(n2, null))).All(b => b == true)); 
     } 

     foreach (PropertyInfo property in typeof(T).GetProperties().Where((property) => property.DeclaringType == typeof(T))) 
     { 
      bool result =func(property); 
      yield return result; 
     } 

    } 

我正在尋找的是爬進對象,而不調用我的方法遞歸的方式。

編輯

爲了澄清,例如:

public class Class1 : RandomClassWithMoreProperties{ 
    public string Str1{get;set;} 
    public int Int1{get;set;} 
} 

public class Class2{ 
    public List<Class1> MyClassProp1 {get;set;} 
    public Class1 MyClassProp2 {get;set;} 
    public string MyStr {get;set;} 
} 

MatchNodes(n1,n2)其中n1.GetType()n2.GetType()Class2將返回true,如果:

  • MyClassProp1每個Class1對象具有相同的Str1Int1兩個對象
  • MyClassProp2具有相同的Str1Int1兩個對象
  • MyStr等於兩個對象

而且我不會從RandomClassWithMoreProperties任何性質比較。

+0

不用自己重寫方法,任何可以寫成遞歸的方法也可以寫成循環。其中的困難可能不是微不足道的,但它是一個已知的CS基礎知識,也是我自己僱用的一個。 – user978122 2011-12-22 18:17:57

+3

即使是循環,您仍需要注意對象圖中的週期。另外,遞歸調用使用'object'作爲'T'泛型參數 - 這可能不是您想要的,因爲您檢查了'DeclaringType == typeof(T)'。你究竟想要做什麼?可能有更好的方法。 – 2011-12-22 18:28:36

+0

另外,看看如果你給它兩個字符串會發生什麼:'typeof(String).GetProperties()'中的foreach屬性'它將調用'func',它是'str1 == str2'。這可能不是你想要做的。 – 2011-12-22 18:33:08

回答

1

您可以使用堆棧或隊列來存儲要比較的屬性。它會沿着這些路線:

var stack = new Stack<Tuple<object, object>>(); 

// prime the stack 
foreach (var prop in n1.GetType().GetProperties()) 
{ 
    stack.Push(Tuple.Create(prop.GetValue(n1), prop.GetValue(n2)); 
} 

while (stack.Count > 0) 
{ 
    var current = stack.Pop(); 

    // if current is promitive: compare 
    // if current is enumerable: push all elements as Tuples on the stack 
    // else: push all properties as tuples on the stack 
} 

如果使用Queue而不是Stack的你得到一個BFS而不是DFS的。你也應該跟蹤HashSet已經訪問過的節點。您還可能需要添加支票以確保n1n2的類型相同。

+0

謝謝,它解決了我的問題。我會接受答案,但我認爲我的解決方案過於複雜。我可能只是手動比較每個對象,因爲我只需要爲20〜30個班級做這件事。 – 2011-12-23 09:56:41

0

這裏的一個好方法是保持你碰到的物體的麪包屑痕跡,並在深入探索時傳遞這些物體。對於每個新對象,檢查它是否在你已經看過的對象的圖形中,如果是,則短路並保護(你已經看到了該節點)。一個堆棧可能是合適的。

通過比較一個非循環對象圖,你不太可能得到堆棧溢出 - 這是當你最終得到的東西爆炸的循環。

0

只要保持跟蹤你已經訪問過的對象,在List<object>例如(或Set<>或類似的東西)......

此外,任何遞歸可以是非遞歸的使用堆棧,你會手動控制。