1

我有这个对象层次结构/图表:

人:

(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
}

我应该怎么办?

4

1 回答 1

0

正如我在评论中提到的那样,您问的是两个单独的问题,它们只是松散相关,但我会尽力解释区别并回答它们:

问题 #1:您将如何使用HasValue动态对象的可为空属性?

简短的回答是;你不能,没有拆箱。例如,您可以Guid将代码中的属性拆箱,如下所示:

((Guid?)person.Guid).HasValue

这背后的原因可以通过检查 和 的一些特征来dynamic解释Nullable

  • dynamic是一个编译时概念,在运行时不存在。所以在引擎盖下,它只是一个object.
  • 使用dynamic变量时,任何子属性和\或方法本身dynamic也是如此。
  • ANullable<>不会按原样装箱。相反,如果它是,您将获得一个空对象引用,null 或者您将获得底层类型(例如System.Guid)。

这对您的示例意味着什么与属性person一样被装箱。Guid由于该Guid属性是可以为空的,它被装箱为要么 System.Guid(如果不是null null,消除了该属性的可空性(这可能导致几个不同的运行时异常)。

另一种可能更简单的解决方案是将其与null. 例如:

person.Guid != null

问题 #2:如何将整个对象图转换为ExpandoObject?

与上一个问题相反,这个问题的解决方案可能更容易掌握(虽然它可能需要更多代码来实现)。

要递归地将整个对象图转换为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,因为从技术角度和认知角度来看,它都非常昂贵(即了解代码的作用和可能出错的地方) )。

于 2020-03-06T13:12:26.907 回答