动态 List<T>.Add 抛出 RuntimeBinderException

Bri*_*n S 2 c# reflection dynamic

我正在为 OrientDB .Net 库编写一些扩展方法,在 C# 中的模型类和数据库中的图形之间进行映射(反之亦然)。这必然需要一些反射和动态编程。

以下方法用于设置表示顶点之间边的模型对象上的属性值。例如,如果顶点 A 通过边 C 链接到多个顶点 B,则模型 A 可能具有 type 属性,List<B>而模型 B 将具有 type 属性A(对于一对多关系)。

private static void SetLinkedProperty(
    ABaseModel parent, ABaseModel child, string className)
{
    PropertyInfo[] properties = parent.GetType()
        .GetProperties(BindingFlags.Public |
                       BindingFlags.Instance |
                       BindingFlags.SetProperty |
                       BindingFlags.GetProperty);
    PropertyInfo propertySingle = properties
        .Where(prop => IsModelProperty(prop, className)).SingleOrDefault();
    PropertyInfo propertyCollection = properties
        .Where(prop => IsModelCollectionProperty(prop, className)).SingleOrDefault();
    if (propertySingle != null)
    {
        propertySingle.SetValue(parent, child);
    }
    if (propertyCollection != null)
    {
        dynamic propertyValue = propertyCollection.GetValue(parent);
        if (propertyValue == null)
        {
            Type listOfT = typeof(List<>).MakeGenericType(
                propertyCollection.PropertyType.GenericTypeArguments[0]);
            IEnumerable collection = (IEnumerable)Activator.CreateInstance(listOfT);
            propertyValue = collection;
            propertyCollection.SetValue(parent, collection);
        }

        propertyValue.Add(child);
    }
}
Run Code Online (Sandbox Code Playgroud)

模型中的属性可以具有为它们提供别名的属性,以帮助在 DB 和 C# 类之间进行映射,因此IsModelPropertyIsModelCollectionProperty检查该别名以及属性类型是否为可枚举类型。

然而,当我运行我的代码时,我得到了RuntimeBinderException一行propertyValue.Add(child)

OrientTest.exe 中发生类型为“Microsoft.CSharp.RuntimeBinder.RuntimeBinderException”的未处理异常

附加信息:“System.Collections.Generic.List.Add(OrientTest.Participant)”的最佳重载方法匹配有一些无效参数

在异常点:

  • parent 是一个实例 OrientTest.Employer
  • child 是一个实例 OrientTest.Participant
  • className 是“EmployerParticipant”(在数据库中将 Employer 和 Participant 顶点链接在一起的边类的名称)
  • properties 包含 7 个元素,每个元素对应于 Employer
  • propertySinglenull
  • propertyCollection 代表财产 List<Participant> Participants
  • propertyValue 是一个实例 List<Participant>

我不明白为什么List<Participant>#Add(Participant)有无效的论点,但dynamic经常做奇怪的事情。

Jer*_*ert 5

重载解析失败,因为类型childABaseModel,不是OrientTest.Participant。它在运行时的值恰好是方法期望的类型并不重要。考虑到名称RuntimeBinder,这似乎违反直觉,但它是有道理的:重载解析规则虽然在运行时应用,但与 C# 在编译时使用的规则相同(因为dynamic它实际上是一个普通的旧 C# 对象)。偏离这一点会导致更多的惊喜。

当然,如果您编写自己的DynamicObject实现,您可以覆盖或规避此行为,因此这不是一般限制dynamic——它只是意味着您不能(ab)使用dynamic这种方式在运行时进行方法解析。

在这种情况下,如果您知道属性始终是List<T>some的类型,则有一个简单的修复方法T,因为List<T>implementsIList接受任何旧的object(带有可能的运行时异常):

IList propertyValue = (IList) propertyCollection.GetValue(parent);
...
propertyValue.Add(child);
Run Code Online (Sandbox Code Playgroud)

如果您不知道这是一个列表,则必须硬着头皮Add动态调用该方法:

object propertyValue = propertyCollection.GetValue(parent);
...
propertyValue.GetType().GetMethod("Add").Invoke(propertyValue, child);
Run Code Online (Sandbox Code Playgroud)

如果对象有多种.Add()方法,而您想要使用“最正确”的方法,这反过来会失败。我假设我们不需要涵盖那个特殊情况。

实际上还有第三种方法,这在这里有点矫枉过正,但在其他情况下可能很有用,那就是使参数本身dynamic成为强制解析在运行时做“正确的事情”(对于“正确”的某些值):

propertyValue.Add((dynamic) child);
Run Code Online (Sandbox Code Playgroud)