【问题标题】:Why is this foreach loop missing a property from the class?为什么这个 foreach 循环缺少类中的属性?
【发布时间】:2021-06-23 13:51:47
【问题描述】:

我基本上是在尝试使用反射将任何类展平为字典,以便我可以在 Blazor 中通用地使用和绑定它们。然后,我需要能够创建该类的一个实例并使用字典中的数据(将由组件更新)填充它。

例如

public class Order
{
    public Guid Id { get; set; }
    public Customer Customer { get; set; }
    public string Address { get; set; }
    public string Postcode { get; set; }
    public List<string> Test { get; set; }
    public List<Test> Test2 { get; set; }
}

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string FullName => $"{FirstName} {LastName}";
    public Gender Gender { get; set; }
    public List<string> Test { get; set; }
}

应该变成:

{
  "Id": "",
  "Customer.FirstName": "",
  "Customer.LastName": "",
  "Customer.Gender": "",
  "Customer.Test": "",
  "Address": "",
  "Postcode": "",
  "Test": "",
  "Test2": ""
}

由于某种原因,当我迭代 Order 类的属性时,Test2 被遗漏了。当我放置断点时,循环显示集合中的属性,它似乎只是跳过它。我以前从未见过这种情况。

代码:https://dotnetfiddle.net/g1qyVQ

我也不认为当前代码能够处理更深的嵌套深度,我希望它能够真正与任何 POCO 对象一起使用。

另外,如果有人知道一种更好的方法来做我正在尝试的事情,我很想找到一种更简单的方法。谢谢

【问题讨论】:

  • 代码需要在帖子中。它也可以与人们尝试联系起来,但问题必须独立存在。请edit将代码放入帖子中。也就是说,它有很多代码。您需要将其分解为minimal reproducible example
  • 往返数据会很困难。因为您正在展平整个对象层次结构,所以您必须考虑树中任何地方的冲突。您需要使用 POCO 对象然后将它们展平成字典的根本原因是什么?
  • 查看我的最新答案,了解我可以对什么/为什么给出的最佳解释

标签: c# .net .net-core reflection


【解决方案1】:

首先,在链接代码示例方面做得很好。如果没有这个,我会在大约三秒钟内通过这个问题。 :D

GetAllProperties() 中,您的整个循环位于一个巨大的try catch 块内,catch 将返回当前字典,而不检查异常是什么。因此,如果您没有得到您期望的一切,您可能遇到了错误。

修改catch块:

catch (Exception ex) { Console.WriteLine(ex.ToString()); return result; }

现在,你可以看到问题了:

System.ArgumentException: An item with the same key has already been added. Key: Test

您的对象有多个名为“Test”的属性,但字典中的键必须是唯一的。

总结:错误不是敌人,而是你最好的朋友。不要使用try / catch 绕过错误。如果你这样做了,你可能会变得“神秘,以前从未见过这种情况!”结果。

【讨论】:

  • 谢谢我完全错过了,我现在有一个更简单的版本,现在使用一个名为 JsonFlatten 的包来展平对象。我还决定首先创建对象的默认完全初始化版本,以便稍后我可以分配值。
【解决方案2】:

对于任何感兴趣的人,这里是我现在的位置:

https://dotnetfiddle.net/3ORKNs

using JsonFlatten;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;

namespace RecursiveClassProperties
{
    public static class Program
    {
        static void Main(string[] args)
        {
            var item = CreateDefaultItem(typeof(Order));
            Console.WriteLine(JsonSerializer.Serialize(item, new JsonSerializerOptions { WriteIndented = true }));
            var json = JsonSerializer.Serialize(item);
            var properties = JObject.Parse(json).Flatten();
            Console.WriteLine(JsonSerializer.Serialize(properties, new JsonSerializerOptions { WriteIndented = true }));
            var formProperties = properties.ToDictionary(x => x.Key, x => new FormResponse(string.Empty));
            Console.WriteLine(JsonSerializer.Serialize(formProperties, new JsonSerializerOptions { WriteIndented = true }));
        }

        private static object CreateFormItem(Type type, Dictionary<string, FormResponse> formProperties, object result = null)
        {
            result = CreateDefaultItem(type);
            return result;
        }

        private static object CreateDefaultItem(Type type, object result = null, object nested = null, bool isBase = false)
        {
            void SetProperty(PropertyInfo property, object instance)
            {
                if (property.PropertyType == typeof(string)) property.SetValue(instance, string.Empty);
                if (property.PropertyType.IsEnum) property.SetValue(instance, 0);
                if (property.PropertyType == typeof(Guid)) property.SetValue(instance, Guid.Empty);
            }

            if (result is null)
            {
                result = Activator.CreateInstance(type);
                isBase = true;
            }

            var properties = type.GetProperties();

            foreach (var property in properties)
            {
                if (!Attribute.IsDefined(property, typeof(FormIgnoreAttribute)) && property.GetSetMethod() is not null)
                {
                    if (property.PropertyType == typeof(string) || property.PropertyType.IsEnum || property.PropertyType == typeof(Guid))
                    {
                        if (isBase) SetProperty(property, result);
                        else if (nested is not null && nested.GetType() is not IList && !nested.GetType().IsGenericType) SetProperty(property, nested);
                    }
                    else
                    {
                        var _nested = default(object);
                        if (isBase)
                        {
                            property.SetValue(result, Activator.CreateInstance(property.PropertyType));
                            _nested = property.GetValue(result);
                        }
                        if (nested is not null)
                        {
                            property.SetValue(nested, Activator.CreateInstance(property.PropertyType));
                            _nested = property.GetValue(nested);
                        }
                        CreateDefaultItem(property.PropertyType, result, _nested);
                    }
                }
            }

            return result;
        }
    }

    [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
    public class FormIgnoreAttribute : Attribute { }

    public class FormResponse
    {
        public FormResponse(string value) => Value = value;
        public string Value { get; set; }
    }

    public class Order
    {
        public Guid Id { get; set; }
        public Customer Customer { get; set; }
        public string Address { get; set; }
        public string Postcode { get; set; }
        public Test Test { get; set; }
        public List<Gender> Genders { get; set; }
        public List<string> Tests { get; set; }
    }

    public enum Gender
    {
        Male,
        Female
    }

    public class Test
    {
        public string Value { get; set; }
        public List<Gender> Genders { get; set; }
        public List<string> Tests { get; set; }
    }

    public class Customer
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string FullName => $"{FirstName} {LastName}";
        public Gender Gender { get; set; }
        public Test Test { get; set; }
        public List<Gender> Genders { get; set; }
        public List<string> Tests { get; set; }
    }
}

我的想法是我可以为 formProperties 赋值,将它传递给 CreateFormItem() 并返回一个填充的对象。我这样做的原因是因为我有一个 Blazor 组件 Table,它有一个 typeparam TItem,对于那些不熟悉 Blazor 的人来说,基本上可以将其视为 Table&lt;TItem&gt;。然后为表格提供一个可以渲染的对象列表。

以这种方式展平对象既可以让我轻松地在表中显示类的所有属性和子属性,但最重要的是绑定“新项目”表单的输入,该表单会将新对象返回给外部的委托组件(回到正常的 .NET 中)提交到创建控制器(将其放入数据库中)。拥有 Dictionary 的原因很重要,因为使用泛型类型,您无法将表单的输入绑定到“模型”。但是,您可以将输入绑定到类的字符串属性,即使它不是字符串。因此是 FormResponse.Value。

接下来我需要让 CreateFormItem() 返回带有来自表单的实际数据的对象。抱歉,如果这有点冗长,想不出更简洁的方式来解释它。

谢谢:)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-03
    • 2016-11-30
    • 1970-01-01
    • 2019-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多