Как выполнить итерацию объекта C #, ища все экземпляры определенного типа, чтобы создать отдельный список этих экземпляров? - PullRequest
4 голосов
/ 01 марта 2012

У меня есть потребность, которая немного похожа на этот вопрос , за исключением того, что она требует более глубокого изучения исходного объекта.

Вот пример кода:

public class Target {};

public class Analyzed
{
    public Target EasyOne { get; set; }
    public IList<Target> ABitMoreTricky { get; set; }
    public IList<Tuple<string, Target>> Nightmare { get; set; }
}

Из экземпляра Analyzed я хочу извлечь все Target экземпляров.

Чтобы облегчить исследование, мы можем предположить следующее:

  1. Исследуйте только свойства.
  2. Не существует бесконечного эталонного цикла.

Пока что EasyOne ... просто, но я ищу какую-то стратегию, чтобы все экземпляры Target были потеряны в более хитрых структурах.

Ответы [ 4 ]

11 голосов
/ 01 марта 2012

Как насчет чего-то такого:

    public List<T> FindAllInstances<T>(object value) where T : class
    {

        HashSet<object> exploredObjects = new HashSet<object>();
        List<T> found = new List<T>();

        FindAllInstances(value, exploredObjects, found);

        return found;
    }

    private void FindAllInstances<T>(object value, HashSet<object> exploredObjects, List<T> found) where T : class
    {
        if (value == null)
            return;

        if (exploredObjects.Contains(value))
            return;

        exploredObjects.Add(value);

        IEnumerable enumerable = value as IEnumerable;

        if (enumerable != null)
        {
            foreach(object item in enumerable)
            {
                FindAllInstances<T>(item, exploredObjects, found);
            }
        }
        else
        {
            T possibleMatch = value as T;

            if (possibleMatch != null)
            {
                found.Add(possibleMatch);
            }

            Type type = value.GetType();

            PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetProperty);

            foreach(PropertyInfo property in properties)
            {
                object propertyValue = property.GetValue(value, null);

                FindAllInstances<T>(propertyValue, exploredObjects, found);
            }

        }

    private void TestIt()
    {
        Analyzed analyzed = new Analyzed()
        {
            EasyOne = new Target(),
            ABitMoreTricky = new List<Target>() { new Target() },
            Nightmare = new List<Tuple<string, Target>>() { new Tuple<string, Target>("", new Target()) }
        };

        List<Target> found = FindAllInstances<Target>(analyzed);

        MessageBox.Show(found.Count.ToString());
    }
1 голос
/ 01 марта 2012

Вы можете пойти по пути отражения и иметь особый подход ко всем контейнерам, которые вы знаете (IEnumerable, IDictionary, все кортежи и кто знает, что еще), или вы можете реально реализовать то, что @Adrian Iftode в шутку сказал в комментарии.

Не думаю, что вы действительно хотите сериализовать в XML, а затем анализировать его. Он будет работать, но он потребует, чтобы все ваши объекты были XML-сериализуемыми, что, если я не ошибаюсь, требует, чтобы все сериализованные данные были общедоступными.

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

0 голосов
/ 01 марта 2012

Dim tTargets () = {Новый список (цели) из {Новая цель ("Джо"), Новый Target ("Bob")}, New Target ("Veronica"), New Tuple (Of String, Target, DateTime, Target) («Кортеж», New Target («Генри»), DateTime.Now, New Target ( "Диакон"))}

Sub ShowMeTheTargets(ByVal tRoot As Object, ByVal tLevel As Int32)

        Dim tCount As Int64 = 0
        Dim tCountName As String = "Length"

        If Nothing Is tRoot Then
            Exit Sub
        End If

        If tRoot.GetType Is GetType(Target) Then
            RTB.AppendText("Found: " & CType(tRoot, Target).Name & vbCrLf)
            '
            '   Assume Target is not a Target container.
            '
            Exit Sub
        End If

        If LEVEL_MAX = tLevel Then
            '
            '   We don't want to scan this object graph any deeper
            '
            Exit Sub
        End If

        If (Nothing Is tRoot.GetType.GetInterface("IEnumerable")) Then
            For Each tProperty As PropertyInfo In tRoot.GetType.GetProperties
                ShowMeTheTargets(tProperty.GetValue(tRoot, Nothing), tLevel + 1)
            Next
        Else
            For Each tObject As Object In tRoot
                ShowMeTheTargets(tObject, tLevel + 1)
            Next
            RTB.AppendText(tCount & vbCrLf)
        End If
    End Sub
0 голосов
/ 01 марта 2012

Вы можете сделать это с помощью отражения. Необходимо решить две задачи:

  1. Получить все свойства типа. Type.GetProperties() сделает это.

  2. Определите, имеет ли тип свойства тип Target или универсальный тип с Target в качестве аргумента типа. Вы можете использовать Type.IsGenericType, чтобы проверить, является ли тип универсальным, и Type.GetGenericArguments, чтобы получить фактические аргументы типа. Если совпадение найдено, вы должны восстановить этот универсальный тип, начиная с 1, и выполнить сопоставление, описанное в 2.

Таким образом, с помощью рефлексии и рекурсии в универсальных типах можно делать то, что вы хотите.

...