为啥这个 foreach 循环缺少类中的属性?

Posted

技术标签:

【中文标题】为啥这个 foreach 循环缺少类中的属性?【英文标题】: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 对象然后将它们展平成字典的根本原因是什么? 查看我的最新答案,了解我可以对什么/为什么给出的最佳解释 【参考方案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() 返回带有来自表单的实际数据的对象。抱歉,如果这有点冗长,想不出更简洁的方式来解释它。

谢谢:)

【讨论】:

以上是关于为啥这个 foreach 循环缺少类中的属性?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能访问这个数组 ForEach 循环中的数据? SwiftUI

为啥我不能在 foreach 循环中设置迭代变量的属性?

为啥我不能使用 foreach 循环更新数组中的数据?

Java中foreach为啥不能给数组赋值

为啥我的 foreach 循环与 users.json 文件中的键不匹配

如何在swiftUI中的Foreach循环上显示除一项之外的图像?