[英]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 butobj
isnull
.obj
的类型与目标类型不匹配,或者属性是实例属性但obj
是null
。
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.