简体   繁体   English

反射 - 来自深层上下文的 SetValue

[英]Reflection - SetValue from deep context

I am facing an issue, surely due to my lack of knowledge in the reflection process, while trying to set a "complex" class hierarchy based on Json files.我正面临一个问题,这肯定是由于我在反射过程中缺乏知识,同时尝试基于 Json 文件设置“复杂” class 层次结构。

Here are my main model:这是我的主要 model:

public class Names
{
    public Weapons Weapons { get; set; }
    public Armors Armors { get; set; }
    public Utilities Utilities { get; set; }
    public Names()
    {
        Weapons = new Weapons();
        Armors = new Armors();
        Utilities = new Utilities();
    }
}

Each of them having a list of sub-model like this:他们每个人都有一个像这样的子模型列表:

public class Weapons
{
    public BattleAxe BattleAxe { get; set; } = new BattleAxe();
    public Bomb_Missile Bomb_Missile { get; set; } = new Bomb_Missile();
    // etc... Around 20 to 25
}

And finally the ended model which is the exact equivalent of each json files but may have very different properties:最后是结束的 model ,它与每个 json 文件完全相同,但可能具有非常不同的属性:

public class BattleAxe
{
    public string[] Normal { get; set; } = new string[0];
    public string[] DescriptiveAdjective { get; set; } = new string[0];
    public string[] Material { get; set; } = new string[0];
    public string[] Type { get; set; } = new string[0];
    public string[] Title { get; set; } = new string[0];
    public string[] Of { get; set; } = new string[0];
    public string[] NormalForTitle { get; set; } = new string[0];
}

Since the MS Json deserializer does not support the conversion to a $type as Newtonsoft before, I tried to populate the values using reflection too like this (I've removed all the null-check for code readability):由于 MS Json 反序列化器以前不支持像 Newtonsoft 那样转换为 $type,因此我也尝试像这样使用反射填充值(我已经删除了所有代码可读性的 null 检查):

public static void Load()
{
    Names = new Names();
    foreach (var category in Names.GetType().GetProperties())
    {
        if (category is not null && !(category.GetGetMethod()?.IsStatic ?? false))
        {
            var categoryType = category.PropertyType;
            foreach (var item in category.PropertyType.GetProperties())
            {
                var itemType = item.PropertyType;
                var subTypeData = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(File.ReadAllText($"./Assets/Names/{categoryType.Name}/{itemType.Name}.json"));
                var concreteObj = Activator.CreateInstance(itemType);
                foreach (var key in subTypeData.Keys)
                {
                    if (itemType.GetProperty(key) is not null && concreteObj is not null)
                    {
                        var prop = concreteObj.GetType().GetProperty(key);
                        var convertedValue = ConvertJsonType(subTypeData[key], subTypeData[key].ValueKind, out var isReferenceType);
                        // It fails here
                        prop.SetValue(
                            isReferenceType ? convertedValue : null,
                            !isReferenceType ? convertedValue : null
                        );
                    }
                }
                item.SetValue(concreteObj, null);
            }
        }
    }
}

So it fails at the prop.SetValue(...) of the deepest object in the hierarchy with a different error depending on the type of value to set.因此,它在层次结构中最深的 object 的prop.SetValue(...)处失败,并根据要设置的值的类型出现不同的错误。 If it is a reference, it throws a System.Reflection.TargetException : 'Object does not match target type' Exception And if it is value, it throw a System.Reflection.TargetException : 'Non-static method requires a target.'如果是引用,则抛出System.Reflection.TargetException : 'Object does not match target type' Exception如果是值,则抛出System.Reflection.TargetException : 'Non-static method requires a target.' Knowing that I do not have problems around the deserialization as shown here, only the fact that I use a dynamic type (and my instinct tells me it is actually the problem...)知道我在反序列化方面没有问题,如此处所示,只有我使用动态类型的事实(我的直觉告诉我这实际上是问题......) 调试

I do not add the ConvertJsonType(...) body as it is functional and really simple我不添加ConvertJsonType(...)主体,因为它功能强大且非常简单

I am more interested in the 'why' than the 'how' so if you can explain me the 'theory' behind the problem, that would help quite a lot:)我对“为什么”比“如何”更感兴趣,所以如果你能向我解释问题背后的“理论”,那将有很大帮助:)

Thank you!谢谢!

PS: I know I can simplify the things in a more readable/performant way but I must achieve it with reflection for personal learning:) Same for the System.Text.Json namespace, I do not intend to switch back to Newtonsoft for that PS:我知道我可以以更具可读性/性能的方式简化事情,但我必须通过反思来实现它以供个人学习:) System.Text.Json 命名空间相同,我不打算为此切换回 Newtonsoft

When calling SetValue(instance, value) you should pass the object which property should be set.调用SetValue(instance, value)时,您应该传递 object 应该设置哪个属性。

It's a wild guess, but you could try this:这是一个疯狂的猜测,但你可以试试这个:

prop.SetValue(concreteObj,
              !isReferenceType ? convertedValue : null);

Because you want to fill the properties of concreteObj , not the value it self.因为您要填充concreteObj对象的属性,而不是其自身的值。

If you look at the object prop it was a return value of concreteObj.GetType().GetProperty(key);如果您查看 object prop ,它是concreteObj.GetType().GetProperty(key); . . If you look at it close, The GetProperty is a method from Type which isn't bound to any instance.如果你仔细看, GetProperty是来自Type的方法,它没有绑定到任何实例。 So that's why you need to pass the instance of the object as the first parameter.这就是为什么您需要将 object 的实例作为第一个参数传递的原因。


I mean this in a positive way: The itemType.GetProperty(key) is called every iteration, it will be the same value each iteration, you could bring it before the loop.我的意思是积极的: itemType.GetProperty(key)每次迭代都会被调用,每次迭代都会是相同的值,你可以把它放在循环之前。

As docs state TargetException is thrown when:作为文档state TargetException在以下情况下抛出:

The type of obj does not match the target type, or a property is an instance property but obj is null . obj的类型与目标类型不匹配,或者属性是实例属性但objnull

Passing null for obj in SetValue is valid when you are trying to set value for static property, not an instance one.当您尝试为 static 属性而不是实例设置值时,在SetValue中为obj传递null是有效的。 Property type being a reference one has nothing to do with property being instance or static one so your call should look something like:作为参考的属性类型与作为实例或 static 的属性无关,因此您的调用应类似于:

prop.SetValue(concreteObj, convertedValue);

Also your item.SetValue(concreteObj, null);还有你的item.SetValue(concreteObj, null); does not look right cause concreteObj should be second argument in this call.看起来不正确,因为concreteObj应该是此调用中的第二个参数。 Something like this:像这样的东西:

item.SetValue(Names, concreteObj);

Also if you want only instance properties you can provide BindingFlags to get only instance properties:此外,如果您只需要实例属性,您可以提供BindingFlags以仅获取实例属性:

foreach (var category in Names.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))

Also I would say that category is not null check is redundant so in pair with providing BindingFlags you should remove the if completely.另外我会说category is not null检查是多余的,因此在提供BindingFlags的同时,您应该完全删除if

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM