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

发布于 2024-12-22 21:41:06 字数 3456 浏览 2 评论 0原文

我有一个名为 MatchNodes 的方法: IEnumerable; MatchNodes(T n1, T n2)

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

当它找到原始类型或字符串时,如果只是返回它们之间的 ==

当它找到从集合派生的类型时,它会迭代每个成员并为每个成员调用 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 将返回 true:

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

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

I have this method called MatchNodes: IEnumerable<bool> MatchNodes<T>(T n1, T n2)

Which basically gets every property and field from both T objects (via reflection, and not including properties/fields from base classes) and compares them, returning the result as a IEnumerable of bools.

When it finds a primitive type or string, if just returns the == between them.

When it finds a type derived from a collection, it iterates each member and calls MatchNodes for each of them (ouch).

When it finds any other type, it calls MatchNodes for each property/field.

My solution is obviously asking for a stack overflow exception, but I don't have a clue on how make it better, because I have no idea how deep the objects will go.

Code (try not to cry please, it's ugly as hell):

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

    }

What I'm looking at is a way to crawl into the objects without calling my method recursively.

EDIT

To clarify, example:

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) where n1.GetType() and n2.GetType() are Class2 would return true if:

  • Every Class1 object inside MyClassProp1 has the same Str1,Int1 for both objects
  • MyClassProp2 has the same Str1,Int1 for both objects
  • MyStr is equal for both objects

And I won't compare any properties from RandomClassWithMoreProperties.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

情话已封尘 2024-12-29 21:41:06

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

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

You can use a stack or queue to store the properties you want to compare. It goes along these lines:

 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
 }

If you use a Queue instead of a Stack you get a BFS instead of a DFS. Also you should probably keep track of already visited nodes in a HashSet. You also might want to add a check to make sure the types of n1 and n2 are the same.

蓝梦月影 2024-12-29 21:41:06

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

通过比较非循环对象图,您不太可能出现堆栈溢出 - 当您最终遇到循环时,事情就会崩溃。

A good approach here is to keep a breadcrumb trail of objects that you've touched, and passing that forward as you delve deeper. For each new object, check to see whether it is in the graph of objects that you have already seen, and if it is, short circuit and bail out (you've already seen that node). A stack is probably appropriate.

You are not likely to get stack overflows by comparing an acyclic object graph- it's when you end up with loops that things blow up.

深府石板幽径 2024-12-29 21:41:06

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

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

Just keep track of the objects you already visited, in a List<object> for example (or Set<> or anything like that)...

Also, any recursion can be un-recursed using the stack that you'll control manually.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文