#c# #dynamic #reflection #.net-5 #system.text.json
#c# #динамический #отражение #.net-5 #system.text.json
Вопрос:
Я столкнулся с проблемой, несомненно, из-за недостатка знаний в процессе отражения, при попытке установить «сложную» иерархию классов на основе файлов Json.
Вот моя основная модель :
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 amp;amp; !(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 amp;amp; 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(...)
тело, поскольку оно функционально и действительно просто
Меня больше интересует «почему», чем «как», поэтому, если вы можете объяснить мне «теорию» проблемы, это очень помогло бы 🙂
Спасибо!
PS: Я знаю, что могу упростить вещи более читаемым / производительным способом, но я должен достичь этого с помощью reflection для личного обучения 🙂 То же самое для System.Text.Пространство имен Json, я не собираюсь для этого возвращаться к Newtonsoft
Ответ №1:
При вызове SetValue(instance, value)
вы должны передать объект, свойство которого должно быть установлено.
Это дикое предположение, но вы могли бы попробовать это:
prop.SetValue(concreteObj,
!isReferenceType ? convertedValue : null);
Потому что вы хотите заполнить свойства concreteObj
, а не само значение.
Если вы посмотрите на объект prop
, это было возвращаемое значение concreteObj.GetType().GetProperty(key);
. Если вы посмотрите на это близко, GetProperty
это метод, Type
который не привязан ни к одному экземпляру. Вот почему вам нужно передать экземпляр объекта в качестве первого параметра.
Я имею в виду это в положительном смысле: itemType.GetProperty(key)
вызывается на каждой итерации, это будет одно и то же значение на каждой итерации, вы можете привести его к циклу.
Комментарии:
1. Вроде того же ответа, что и Guru, так что это действительно было неправильное использование setValue . Я просто нахожу его объяснение более привлекательным для меня, но это всего лишь вопрос стиля, оба были правильным решением, спасибо 🙂 Что касается вашего
itemType.GetProperty(key)
комментария, как я мог бы получить его перед циклом в качестве его цели, чтобы проверить, правильно ли текущий ключ из моего объекта json, который я использую, присутствует в моем классе модели? Я знаю, что это дорого, но у меня нет простой идеи сократить его использование2. Только мой ответ был перед его. По крайней мере, вы его нашли.
Ответ №2:
Поскольку состояние документов TargetException
выдается, когда:
Тип
obj
не соответствует целевому типу, или свойство является свойством экземпляра, ноobj
являетсяnull
.
Передача null
для obj
in SetValue
допустима, когда вы пытаетесь установить значение для статического свойства, а не для экземпляра. Тип свойства, являющийся ссылочным, не имеет ничего общего с тем, что свойство является экземпляром или статическим, поэтому ваш вызов должен выглядеть примерно так:
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
полностью удалить.
Комментарии:
1. Это действительно было неправильно понято в отношении функции setValue, извините за фиктивную ошибку:/ Спасибо за использование BindingFlags, это более элегантно. Была проблема с
item.SetValue(concreteObj, null);
тем, как вы сказали, но мне нужно было установить класс category вместо Names . Поскольку у меня еще не было никаких ссылок на него, мне пришлось использовать GetValue (еще один вызов отражения …) для имен. Спасибо за объяснение и советы 🙂