Как преобразовать весь граф объекта в ExpandoObject? - PullRequest
2 голосов
/ 04 марта 2020

У меня есть эта иерархия объектов / график:

Персона:

(Name,string)
(Age,int?)
(Guid,Guid?)
(Interests,List<Interest>)

Интерес:

(Name,string)
(IsProfession,bool?)
(RequiredSkills,List<RequiredSkill>)

Умение:

(Title,string)
(HoursToAccomplish,int?)

Итак, в общем случае экземпляр этого, представленный в JSON, будет иметь вид:

{
  "name": "John",
  "age": 38,
  "guid": null,
  "interests": [
    {
      "name": "party",
      "isProfession": false,
      "requiredSkills": []
    },
    {
      "name": "painting",
      "isProfession": true,
      "requiredSkill": [
        {
          "title": "optics",
          "hoursToAccomplish": 75
        }
      ]
    }
  ]
}

Теперь в C# я хочу привести экземпляр этого графа объекта к ExpandoObject, а затем динамически работать с этим , Я написал этот метод преобразования:

public ExpandoObject ToExpando(object @object)
{
    var properties = @object.GetType().GetProperties();
    IDictionary<string, object> expando = new ExpandoObject();
    foreach (var property in properties)
    {
        expando.Add(property.Name, property.GetValue(@object));
    }
    return (ExpandoObject)expando;
}

Он отлично работает для вложенных объектов. Но это меняет обнуляемость свойств. Таким образом, эта строка кода встречает ошибку:

// Strongly-typed:
if (person.Guid.HasValue) {
   // logic
}

// Expando object:
if (person.Guid.HasValue) { // 'System.Guid' does not contain a definition for 'HasValue'
   // logic
}

Что мне делать?

1 Ответ

0 голосов
/ 06 марта 2020

Как я уже упоминал в своем комментарии, вы задаете два отдельных вопроса, которые только слабо связаны, но я приложу все усилия, чтобы объяснить различие и ответить на них оба:

Вопрос № 1: Как бы вы использовали HasValue для обнуляемых свойств динамических c объектов?

Короткий ответ:; Вы не можете без распаковки. Например, вы можете распаковать свойство Guid в своем коде следующим образом:

((Guid?)person.Guid).HasValue

Причину этого можно объяснить, изучив некоторые характеристики dynamic и Nullable:

  • dynamic является концепцией компиляции и не существует в времени выполнения . Так что под капотом это просто object.
  • . При использовании переменной dynamic любое дочернее свойство и \ или метод также присуще dynamic.
  • A Nullable<> не упаковывается как есть. Вместо этого вы получите либо нулевую ссылку на объект, если это null , либо , вы получите базовый тип (например, System.Guid).

Что это означает для ваш пример: person упаковывается вместе со свойством Guid. Поскольку свойство Guid имеет значение NULL, оно помечается как или System.Guid (если оно не null) или null, устраняя возможность использования этого свойства ( что может привести к нескольким различным исключениям времени выполнения).

Другим, возможно, более простым решением, будет просто сравнить его с null. Например:

person.Guid != null

Вопрос № 2: Как бы вы преобразовали весь граф объектов в ExpandoObject?

В отличие от предыдущего вопроса, Решение этого вопроса, возможно, проще найти asp (хотя для его реализации может потребоваться больше кода).

Чтобы рекурсивно преобразовать весь граф объектов в ExpandoObject, вы можете просто сделать свою функцию рекурсивной. Например:

public ExpandoObject ToExpando(object @object)
{
    var properties = @object.GetType().GetProperties();
    IDictionary<string, object> expando = new ExpandoObject();
    foreach (var property in properties)
    {
        var value = GetValueOrExpandoObject(@object, property);
        expando.Add(property.Name, value);
    }
    return (ExpandoObject)expando;
}

public object GetValueOrExpandoObject(object @object, PropertyInfo property)
{
    var value = property.GetValue(@object);
    if (value == null) return null;

    var valueType = value.GetType();
    if (valueType.IsValueType || value is string) return value;

    if (value is IEnumerable enumerable) return ToExpandoCollection(enumerable);

    return ToExpando(value);
}

public IEnumerable<ExpandoObject> ToExpandoCollection(IEnumerable enumerable)
{
    var enumerator = enumerable.GetEnumerator();
    while (enumerator.MoveNext())
    {
        yield return ToExpando(enumerator.Current);
    }
}

В этом примере я (наивно) проверяю для каждого свойства, должно ли значение быть преобразовано в ExpandoObject (например, если это тип значения), и если это Позвоните по номеру ToExpando, указав значение свойства.

Обратите внимание, что это далеко не идеальное решение, но оно показывает, как быстро это может выйти из-под контроля. Чем больше типов вам нужно обрабатывать по-разному, тем сложнее это будет, поэтому, если вы в итоге реализуете что-то подобное, вы должны рассмотреть шаблон посетителя .


Наконец, я Хотелось бы отметить, что в целом я настоятельно рекомендую вам избегать , используя, если возможно, полностью dynamic, поскольку это очень дорого как с технической, так и с познавательной точки зрения (т.е. понимая, что такое код делает и что может go не так).

...