Как использовать отражение для элегантного доступа к вложенным объектам? - PullRequest
0 голосов
/ 10 июня 2019

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

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

Имея следующие классы:

Уровень 1:

public sealed class DynamicData
{
    [JsonProperty(propertyName: "PersonInfo")]
    public DynamicDataPersonInfo PersonInfo { get; set; }

    [JsonProperty(propertyName: "Location")]
    public DynamicDataLocation Location { get; set; }
}

Уровень 2:

public sealed class DynamicDataPersonInfo
{
    [JsonProperty("title")]
    public string Title { get; set; }

    [JsonProperty("type")]
    public string Type { get; set; }

    [JsonProperty("properties")]
    public DynamicDataPersonInfoProperties Properties { get; set; }

    [JsonProperty("required")]
    public string[] PersonInfoRequired { get; set; }
}

public sealed class DynamicDataLocation
{
    [JsonProperty(propertyName: "title")]
    public string Title { get; set; }

    [JsonProperty(propertyName: "type")]
    public string Type { get; set; }

    [JsonProperty(propertyName: "properties")]
    public DynamicDataLocationProperties Properties { get; set; }

    [JsonProperty(propertyName: "required")]
    public string[] LocationRequired { get; set; }
}

Уровень 3:

public sealed class DynamicDataLocationProperties
{
    [JsonProperty(propertyName: "BuildingNumber")]
    public DynamicDataFieldInfo BuildingNumber { get; set; }

    [JsonProperty(propertyName: "UnitNumber")]
    public DynamicDataFieldInfo UnitNumber { get; set; }

    [JsonProperty(propertyName: "StreetName")]
    public DynamicDataFieldInfo StreetName { get; set; }

    [JsonProperty(propertyName: "StreetType")]
    public DynamicDataFieldInfo StreetType { get; set; }

    [JsonProperty(propertyName: "City")]
    public DynamicDataFieldInfo City { get; set; }

    [JsonProperty(propertyName: "StateProvinceCode")]
    public DynamicDataFieldInfo StateProvinceCode { get; set; }

    [JsonProperty(propertyName: "PostalCode")]
    public DynamicDataFieldInfo PostalCode { get; set; }
}

public sealed class DynamicDataPersonInfoProperties
{
    [JsonProperty(propertyName: "FirstGivenName")]
    public DynamicDataFieldInfo FirstGivenName { get; set; }

    [JsonProperty(propertyName: "MiddleName")]
    public DynamicDataFieldInfo MiddleName { get; set; }

    [JsonProperty(propertyName: "FirstSurName")]
    public DynamicDataFieldInfo FirstSurName { get; set; }

    [JsonProperty(propertyName: "DayOfBirth")]
    public DynamicDataFieldInfo DayOfBirth { get; set; }

    [JsonProperty(propertyName: "MonthOfBirth")]
    public DynamicDataFieldInfo MonthOfBirth { get; set; }

    [JsonProperty(propertyName: "YearOfBirth")]
    public DynamicDataFieldInfo YearOfBirth { get; set; }
}

Окончательный уровень:

public sealed class DynamicDataFieldInfo
{
    [JsonProperty(propertyName: "type")]
    public string Type { get; set; }

    [JsonProperty(propertyName: "description")]
    public string Description { get; set; }

    [JsonProperty(propertyName: "label")]
    public string Label { get; set; }
}

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

private IReadOnlyList<DynamicProperty> CombineProperties(DynamicData deserializedRawData)
{
    List<DynamicProperty> combinedDynamicProperties = new List<DynamicProperty>();

    // dynamic data
    foreach (PropertyInfo propertiyofDynamicData in deserializedRawData.GetType()
        .GetProperties())
    {
        // dynamic data lower level
        foreach (PropertyInfo propertyOfDynamicDataMember in propertiyofDynamicData.GetType()
            .GetProperties()
            .Where(predicate: x => x.Name == "Properties"))
        {
            foreach (PropertyInfo propertyOfFieldInfo in propertyOfDynamicDataMember.GetType()
                .GetProperties())
            {
                DynamicDataFieldInfo dynamicDataFieldInfo = propertyOfFieldInfo.GetValue(propertyOfDynamicDataMember) as DynamicDataFieldInfo;
                combinedDynamicProperties.Add(new DynamicProperty {DynamicDataFieldInfo = dynamicDataFieldInfo, GroupType = FormHelper.GetFormGroupType(propertiyofDynamicData.Name)});
            }
        }
    }

    return combinedDynamicProperties;
}

Есть ли способ, которым я могу избежать этого ужасного зацикливания?

1 Ответ

2 голосов
/ 10 июня 2019

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

  • Измените второй foreach на функцию простого поиска, потому что вы ищете единственное свойство под названием «Свойства»
  • Исправьте объектссылка в GetValue (ранее ссылалась на PropertyInfo, но должна ссылаться на реальный объект)
  • Оценивать FormGroupType только один раз для свойства верхнего уровня

Изменения будут выглядеть следующим образом:

private IReadOnlyList<DynamicProperty> CombineProperties(DynamicData deserializedRawData)
{
    List<DynamicProperty> combinedDynamicProperties = new List<DynamicProperty>();

    // dynamic data
    foreach (PropertyInfo propertiyofDynamicData in deserializedRawData.GetType()
        .GetProperties())
    {
        object formGroup = propertiyofDynamicData.GetValue(deserializedRawData);
        var formGroupType = FormHelper.GetFormGroupType(propertiyofDynamicData.Name);

        // dynamic data lower level
        PropertyInfo propertyOfDynamicDataMember = propertiyofDynamicData.GetType().GetProperty("Properties");
        object propertiesGroup = propertyOfDynamicDataMember.GetValue(formGroup);

        foreach (PropertyInfo propertyOfFieldInfo in propertyOfDynamicDataMember.GetType()
            .GetProperties())
        {
            if (propertyOfFieldInfo.GetValue(propertiesGroup) is DynamicDataFieldInfo dynamicDataFieldInfo)
                combinedDynamicProperties.Add(new DynamicProperty {DynamicDataFieldInfo = dynamicDataFieldInfo, GroupType = formGroupType });
        }
    }

    return combinedDynamicProperties;
}

Редактировать: Если подумать, мы можем сжать его в довольно аккуратное выражение LINQ

private IReadOnlyList<DynamicProperty> CombineProperties(DynamicData deserializedRawData)
{
    return (from formGroupProp in deserializedRawData.GetType().GetProperties()
            let formGroup = formGroupProp.GetValue(deserializedRawData)
            let formGroupType = FormHelper.GetFormGroupType(formGroupProp.Name)
            let properties = formGroupProp.GetType().GetProperty("Properties").GetValue(formGroup)
            from dProp in properties.GetType().GetProperties()
            let fieldInfo = dProp.GetValue(properties) as DynamicDataFieldInfo
            where fieldInfo is DynamicDataFieldInfo
            select new DynamicProperty { DynamicDataFieldInfo = fieldInfo, GroupType = formGroupType }).ToList()
}

Редактировать 2: Добавив отражениерасширения, которые мы можем еще больше упростить для рассматриваемого метода: (Примечание: поместите их в пространство имен, предназначенное только для особых ситуаций, подобных этому, иначе они будут загрязнять автозаполнение для любого объекта)

static class ReflectionExtensions
{
    public static IEnumerable<object> AllProperties(this object root)
    {
        return root.GetType().GetProperties().Select((p) => p.GetValue(root));
    }
    public static IEnumerable<T> AllProperties<T>(this object root)
    {
        return root.GetType().GetProperties().Where((p) => p.GetType() == typeof(T)).Select((p) => p.GetValue(root));
    }
    public static object Property(this object root, string propName)
    {
        return root.GetType().GetProperty(propName)?.GetValue(root);
    }
}
private IReadOnlyList<DynamicProperty> CombineProperties(DynamicData deserializedRawData)
{
    return (from formGroup in deserializedRawData.AllProperties()
            let formGroupType = FormHelper.GetFormGroupType(formGroup.GetType().Name)
            let properties = formGroup.Property("Properties")
            from fieldInfo in properties?.AllProperties<DynamicDataFieldInfo>() ?? Enumerable.Empty<PropertyInfo>()
            select new DynamicProperty { DynamicDataFieldInfo = fieldInfo, GroupType = formGroupType }).ToList()
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...