在c#中通过反射在对象内部爬行时防止堆栈溢出

本文关键字:栈溢出 堆栈 爬行 对象 反射 内部 | 更新日期: 2023-09-27 18:18:33

我有这个方法叫做MatchNodes: IEnumerable<bool> MatchNodes<T>(T n1, T n2)

它基本上从两个T对象(通过反射,不包括基类的属性/字段)获得每个属性和字段,并比较它们,返回结果作为一个IEnumerable of bools。

当它找到一个基本类型或字符串时,if只返回它们之间的==

当它找到从集合派生的类型时,它迭代每个成员并为每个成员调用MatchNodes(哎哟)。

当发现任何其他类型时,它为每个属性/字段调用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,如果:

  • MyClassProp1中的每个Class1对象都有相同的Str1, Int1
  • MyClassProp2对于两个对象具有相同的Str1, Int1
  • MyStr对于两个对象是相等的

我不会比较RandomClassWithMoreProperties中的任何属性

在c#中通过反射在对象内部爬行时防止堆栈溢出

可以使用堆栈或队列来存储要比较的属性。它是这样的:

 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的类型相同。

这里的一个好方法是保留您所接触过的对象的蛛皮迹,并在您深入研究时传递它。对于每个新对象,检查它是否在您已经看到的对象图中,如果是,则短路并跳出(您已经看到了该节点)。栈可能是合适的。

你不太可能通过比较一个无循环的对象图得到堆栈溢出——当你以循环结束时,事情就会爆炸。

只需跟踪您已经访问过的对象,例如在List<object>(或Set<>或类似的东西)…

此外,任何递归都可以使用您手动控制的堆栈来反递归。