如何使用反射以优雅的方式访问嵌套对象?

时间:2019-06-10 18:00:16

标签: c# reflection

我从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 个答案:

答案 0 :(得分:2)

因此,我尝试为您的代码创建递归形式,但是可惜的是,循环中的每个步骤之间的差异太大,并且您在最终选择的对象中引用了第一个循环。相反,这是我要进行的三个更改:

  • 将第二个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()
}