Предотвращение переполнения стека при сканировании внутри объектов посредством отражения в c # - PullRequest
1 голос
/ 22 декабря 2011

У меня есть этот метод под названием MatchNodes: IEnumerable<bool> MatchNodes<T>(T n1, T n2)

Который в основном получает каждое свойство и поле от обоих T объектов (через отражение, не включая свойства / поля из базовых классов) и сравнивает их, возвращая результат в виде IEnumerable из bools.

Когда он находит примитивный тип или строку, он просто возвращает == между ними.

Когда он находит тип, производный от коллекции, он выполняет итерацию каждого члена и вызывает 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;
        }

    }

То, на что я смотрю, - это способ ползти в объекты без рекурсивного вызова моего метода.

EDIT

Для пояснения, пример:

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, если:

  • Каждый Class1 объект внутри MyClassProp1 имеет одинаковые Str1, Int1 для обоих объектов
  • MyClassProp2 имеет одинаковые Str1, Int1 для обоих объектов
  • MyStr равно для обоих объектов

И я не буду сравнивать какие-либо свойства с RandomClassWithMoreProperties.

Ответы [ 3 ]

1 голос
/ 22 декабря 2011

Вы можете использовать стек или очередь для хранения свойств, которые вы хотите сравнить. Это идет по следующим направлениям:

 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. Вы также можете добавить проверку, чтобы убедиться, что типы n1 и n2 совпадают.

0 голосов
/ 22 декабря 2011

Просто следите за объектами, которые вы уже посетили, например, за List<object> (или Set<> или что-то в этом роде) ...

Кроме того, любую рекурсию можно выполнить без повторения сстек, который вы будете контролировать вручную.

0 голосов
/ 22 декабря 2011

Хороший подход заключается в том, чтобы сохранить следы крошечных объектов, которые вы касались, и передавать их вперед, углубляясь вглубь.Для каждого нового объекта проверьте, находится ли он на графике объектов, которые вы уже видели, и, если это так, короткое замыкание и выход из строя (вы уже видели этот узел).Стек, вероятно, уместен.

Вероятность переполнения стека маловероятна при сравнении ациклического графа объектов - когда вы заканчиваете циклами, все взрывается.

...