反思 - 来自深层背景的 SetValue
Reflection - SetValue from deep context
我遇到了一个问题,肯定是由于我在反射过程中缺乏知识,同时试图根据 Json 文件设置“复杂”class 层次结构。
这是我的主要模型:
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();
}
}
他们每个人都有一个这样的子模型列表:
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
}
最后的模型与每个 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];
}
由于 MS Json 反序列化器不支持转换为 $type 之前的 Newtonsoft,我尝试像这样使用反射填充值(我已经删除了代码的所有空值检查可读性):
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);
}
}
}
}
因此它在层次结构中最深对象的 prop.SetValue(...)
处失败,并根据要设置的值的类型出现不同的错误。
如果它是一个引用,它会抛出一个 System.Reflection.TargetException : 'Object does not match target type' Exception
如果它是值,它会抛出一个 System.Reflection.TargetException : 'Non-static method requires a target.'
知道我在反序列化方面没有问题,如此处所示,只是我使用动态类型这一事实(我的直觉告诉我这实际上是问题所在......)
我没有添加 ConvertJsonType(...)
正文,因为它很实用而且非常简单
我对 'why' 比 'how' 更感兴趣,所以如果你能向我解释问题背后的 'theory',那将有很大帮助:)
谢谢!
PS:我知道我可以用更readable/performant的方式简化事情,但我必须通过个人学习的反思来实现它:)
System.Text.Json 命名空间也一样,我不打算为此切换回 Newtonsoft
当调用 SetValue(instance, value)
时,您应该传递 属性 应该设置的对象。
这是一个疯狂的猜测,但你可以试试这个:
prop.SetValue(concreteObj,
!isReferenceType ? convertedValue : null);
因为你要填的是concreteObj
的属性,而不是它自己的值
如果您查看对象 prop
,它的 return 值为 concreteObj.GetType().GetProperty(key);
。如果仔细观察,GetProperty
是 Type
中的一种方法,它未绑定到任何实例。所以这就是为什么你需要将对象的实例作为第一个参数传递。
我的意思是积极的:itemType.GetProperty(key)
每次迭代都会被调用,每次迭代都是相同的值,你可以把它放在循环之前。
As docs 状态 TargetException
在以下情况下抛出:
The type of obj
does not match the target type, or a property is an instance property but obj
is null
.
在 SetValue
中为 obj
传递 null
在您尝试为静态 属性 而非实例设置值时有效。 属性 类型作为参考与 属性 作为实例或静态类型无关,因此您的调用应该类似于:
prop.SetValue(concreteObj, convertedValue);
您的 item.SetValue(concreteObj, null);
看起来也不正确,因为 concreteObj
应该是此调用中的第二个参数。像这样:
item.SetValue(Names, concreteObj);
此外,如果您只需要实例属性,您可以提供 BindingFlags
以仅获取实例属性:
foreach (var category in Names.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
我还要说 category is not null
检查是多余的,所以在提供 BindingFlags
的同时,你应该完全删除 if
。
我遇到了一个问题,肯定是由于我在反射过程中缺乏知识,同时试图根据 Json 文件设置“复杂”class 层次结构。
这是我的主要模型:
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();
}
}
他们每个人都有一个这样的子模型列表:
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
}
最后的模型与每个 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];
}
由于 MS Json 反序列化器不支持转换为 $type 之前的 Newtonsoft,我尝试像这样使用反射填充值(我已经删除了代码的所有空值检查可读性):
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);
}
}
}
}
因此它在层次结构中最深对象的 prop.SetValue(...)
处失败,并根据要设置的值的类型出现不同的错误。
如果它是一个引用,它会抛出一个 System.Reflection.TargetException : 'Object does not match target type' Exception
如果它是值,它会抛出一个 System.Reflection.TargetException : 'Non-static method requires a target.'
知道我在反序列化方面没有问题,如此处所示,只是我使用动态类型这一事实(我的直觉告诉我这实际上是问题所在......)
我没有添加 ConvertJsonType(...)
正文,因为它很实用而且非常简单
我对 'why' 比 'how' 更感兴趣,所以如果你能向我解释问题背后的 'theory',那将有很大帮助:)
谢谢!
PS:我知道我可以用更readable/performant的方式简化事情,但我必须通过个人学习的反思来实现它:) System.Text.Json 命名空间也一样,我不打算为此切换回 Newtonsoft
当调用 SetValue(instance, value)
时,您应该传递 属性 应该设置的对象。
这是一个疯狂的猜测,但你可以试试这个:
prop.SetValue(concreteObj,
!isReferenceType ? convertedValue : null);
因为你要填的是concreteObj
的属性,而不是它自己的值
如果您查看对象 prop
,它的 return 值为 concreteObj.GetType().GetProperty(key);
。如果仔细观察,GetProperty
是 Type
中的一种方法,它未绑定到任何实例。所以这就是为什么你需要将对象的实例作为第一个参数传递。
我的意思是积极的:itemType.GetProperty(key)
每次迭代都会被调用,每次迭代都是相同的值,你可以把它放在循环之前。
As docs 状态 TargetException
在以下情况下抛出:
The type of
obj
does not match the target type, or a property is an instance property butobj
isnull
.
在 SetValue
中为 obj
传递 null
在您尝试为静态 属性 而非实例设置值时有效。 属性 类型作为参考与 属性 作为实例或静态类型无关,因此您的调用应该类似于:
prop.SetValue(concreteObj, convertedValue);
您的 item.SetValue(concreteObj, null);
看起来也不正确,因为 concreteObj
应该是此调用中的第二个参数。像这样:
item.SetValue(Names, concreteObj);
此外,如果您只需要实例属性,您可以提供 BindingFlags
以仅获取实例属性:
foreach (var category in Names.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
我还要说 category is not null
检查是多余的,所以在提供 BindingFlags
的同时,你应该完全删除 if
。