动态列表.添加抛出运行时绑定器异常

本文关键字:运行时 绑定 异常 添加 列表 动态 | 更新日期: 2023-09-27 18:32:13

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

以下方法用于设置模型对象上表示顶点之间边的属性值。例如,如果顶点 A 链接到具有边 C 的多个顶点 B,则模型 A 可能具有类型 List<B> 的属性,而模型 B 将具有类型 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);
    }
}

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

但是,当我运行代码时,我在propertyValue.Add(child)行得到RuntimeBinderException

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

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

在异常点:

  • parentOrientTest.Employer的一个实例
  • childOrientTest.Participant的一个实例
  • className为"雇主参与者"(在数据库中将雇主和参与者折点链接在一起的边类的名称)
  • properties包含 7 个元素,每个元素对应于 Employer 中的一个属性
  • propertySingle null
  • propertyCollection表示属性List<Participant> Participants
  • propertyValueList<Participant>的一个实例

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

动态列表<T>.添加抛出运行时绑定器异常

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

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

在这种情况下,如果您知道该属性对于某些T始终是类型 List<T>,则有一个简单的修复,因为List<T>实现了接受任何旧objectIList(可能存在运行时异常):

IList propertyValue = (IList) propertyCollection.GetValue(parent);
...
propertyValue.Add(child);

如果你不知道这是一个列表,你必须咬紧牙关,动态调用Add方法:

object propertyValue = propertyCollection.GetValue(parent);
...
propertyValue.GetType().GetMethod("Add").Invoke(propertyValue, child);

如果对象有多个.Add()方法,而您希望使用"最正确"的方法,则此操作又会失败。我假设我们不需要涵盖这种特殊情况。

实际上还有第三种方法,在这里是矫枉过正的,但在其他情况下可能很有用,那就是使参数本身dynamic因此解决方案被迫在运行时执行"正确的事情"(对于"正确"的某些值):

propertyValue.Add((dynamic) child);