【问题标题】:Casting Dynamic Object and Passing Into UnitOfWork and Repository Pattern. Throws Exception转换动态对象并传递给 UnitOfWork 和存储库模式。抛出异常
【发布时间】:2016-10-21 19:46:25
【问题描述】:

这是一个非常具体的问题。甚至不知道该怎么说。基本上我正在实现工作单元和存储库模式,我有一个动态对象,我将其转换为 int,但如果我使用var,它会在尝试调用该方法时抛出异常。

我试图尽可能地删除这个问题的所有琐碎变量。出于某种原因,我只看到这两种设计模式会发生这种情况。我得到的例外是Additional information: 'BlackMagic.ITacoRepo' does not contain a definition for 'DoStuff'

代码如下:

class BlackMagic
{
    static void Main(string[] args)
    {
        dynamic obj = new ExpandoObject();
        obj.I = 69;

        UnitOfWork uow = new UnitOfWork();

        int i1 = Convert.ToInt32(obj.I);
        var i2 = Convert.ToInt32(obj.I);

        if(i1.Equals(i2))
        {
            uow.TacoRepo.DoStuff(i1); // Works fine
            uow.TacoRepo.DoStuff(i2); // Throws Exception
        }
    }
}

class UnitOfWork
{
    public ITacoRepo TacoRepo { get; set; }

    public UnitOfWork()
    {
        TacoRepo = new TacoRepo();
    }
}

class Repo<T> : IRepo<T> where T : class
{
    public void DoStuff(int i)
    {
    }
}

interface IRepo<T> where T : class
{
    void DoStuff(int i);
}

class TacoRepo : Repo<Taco>, ITacoRepo
{
}

interface ITacoRepo : IRepo<Taco>
{
}

class Taco
{
}

编辑: 我试图找到答案的主要问题是,为什么在工作单元内调用DoStuff 会引发异常(在使用 repo 时)但没有得到如果 BlackMagic 类中存在 DoStuff 则抛出。

【问题讨论】:

  • 你能不能在调用DoStuff 之前插入一个断点,看看i2 的实际运行时类型是什么?
  • 发生这种情况是因为运行时使用反射来尝试确定i2 的类型。但是,由于i2 来自ExpandoObject,它的声明和反射类型为空,因此它不符合DoStuffint 要求。确实很奇怪,也许你应该避免以这种方式在这里使用动态。
  • @EdPlunkett 是的,这真的很奇怪,因为我以许多不同的方式测试了这个问题。但它似乎只发生在我使用工作单元和存储库模式时。我无法以任何其他方式复制它。
  • 虽然我不确定原因,但我至少可以告诉你,运行时绑定器是类型确定中的谁。
  • @TravisJ 我同意 Ed 的观点。您对动态对象转换提出了一个很好的观点,但是如果我将DoStuff 从回购中取出并将其放入主“BlackMagic”类然后将 i2 传入,则不会引发错误

标签: c# repository-pattern unit-of-work expandoobject dynamicobject


【解决方案1】:

这是我在 5 多年前向 Microsoft 报告的错误之一,在 dynamic 被引入后不久。据我所知,它在他们的列表中被认为是非常低的优先级,并且可能永远不会被修复。

以下是简单的复制步骤:

using System.Collections;

class C
{
    static void Main()
    {
        object[] array = { };
        IList list = new ArrayList();
        list.CopyTo(array, 0); // Works okay
        dynamic index = 0;
        list.CopyTo(array, index); // Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'System.Collections.IList' does not contain a definition for 'CopyTo'
    }
}

这里是对问题的解释。当在静态类型为接口类型的表达式上调用函数成员(方法或索引器)时,调用的至少一个参数是 dynamic 类型(这意味着完整的成员查找 -类型推断——重载解析过程被推迟到运行时,并且成为运行时绑定器而不是编译器的责任;编译器仅根据不完整的类型信息执行部分检查),并且被调用的成员被继承由其基接口之一的接口(而不是在接口本身中声明),则运行时绑定程序无法正确遍历基接口的树以找到继承的成员,并在运行时抛出异常,报告所需的未找到成员。请注意,这只是运行时绑定器的错误——编译器正确地接受了调用(但会拒绝它,例如,如果您在方法名称中输入错误)。

一种可能的解决方法:将您调用成员的表达式强制转换为实际声明您尝试调用的成员的基本接口。例如,上述重现步骤中的程序可以修复如下:

using System.Collections;

class C
{
    static void Main()
    {
        object[] array = { };
        IList list = new ArrayList();
        list.CopyTo(array, 0); // Works okay
        dynamic index = 0;
        ((ICollection) list).CopyTo(array, index); // Works okay
    }
}

或者,如果可能,通过将 dynamic 类型的参数转换为被调用成员签名中指定的类型来完全摆脱动态调度。

using System.Collections;

class C
{
    static void Main()
    {
        object[] array = { };
        IList list = new ArrayList();
        list.CopyTo(array, 0); // Works okay
        dynamic index = 0;
        list.CopyTo(array, (int) index); // Works okay
    }
}

不幸的是,如果您真的希望在运行时发生重载解决方案,那么这两种解决方法可能都没有帮助,并且在可能的候选者中,既有接口声明的成员,也有由它继承的成员。在这种情况下,您可能需要发明一些临时解决方案,或者大幅重构您的程序。

【讨论】:

  • 所以本质上你是说,由于接口ITacoRepo 没有显式地实现接口IRepo&lt;Taco&gt;DoStuff 方法,它不知道还能去哪里看?它只是在看ITacoRepo?那么在传递动态时接口实现接口的技术问题?
  • 接口不能实现其他接口,只能继承。他们可以重新声明继承的抽象成员,但这并不构成实现。当然,运行时接口类型的变量(如果不是 null)将包含对某个具体类型的对象的引用,该对象具有接口的所有成员的实现,在其中声明(或重新声明)并继承.但是重载解析必须基于接收者的静态已知类型,即接口类型。运行时绑定器无法正确收集继承的候选者。
  • 不过,在运行时,当您在 i2 上调用 GetType 时,它会告诉您它是一个 Int32。为什么它仍然会导致同样的问题?如果您将其保留为dynamic,我明白为什么会这样,但如果问题发生在运行时,我不明白为什么它不会接受int
  • 变量i2被声明为var,这意味着它的编译时类型是从它的初始化器中推断出来的。它的初始化器包含dynamic类型的子表达式obj,所以初始化器的类型也是dynamici2的类型也是如此(就像你显式声明它为dynamic一样) .这意味着任何使用i2 作为参数的方法调用都必须在运行时动态解析,使用它引用的对象的运行时类型进行适用性检查和重载解析。运行时绑定器中的错误阻止它找到适用的继承方法。
【解决方案2】:

看起来 RuntimeBinder 没有遍历继承层次结构,所以它只在直接接口 ITacoRepo 中查找 DoStuff 的定义。

如果你让UnitOfWork使用IRepo&lt;Taco&gt;而不是ITacoRepo,它就能找到方法定义。

【讨论】:

  • 为什么var i2 是这样,int i1 不是这样?
  • @TravisJ 因为 RuntimeBinder 不参与与 i1 的调用。这就是编译时间。
  • 那么为什么如果我创建一个本地的IRepo&lt;Taco&gt; r = new TacoRepo() 然后调用r.DoStuff(i2) 我不会得到错误?
  • @THEStephenStanton 如果将r 定义为ITacoRepo 会发生什么,就像UnitOfWork 中的定义一样?我认为问题在于您使用的类型,而不是变量所在的位置。
  • @KendallFrey 所以你提出了一个我尝试过的有趣观点(如果我理解你的话)。如果我只有TacoRepoITacoRepo,我将签名DoStuff 移动到ITacoRepo,然后在'TacoRepo' 中实现它,然后执行'ITacoRepo r = new TacoRepo()' 然后执行'r .DoStuff(i2)',也不例外。这就是为什么它很奇怪,只有在我使用这种设计模式时才会发生
猜你喜欢
  • 1970-01-01
  • 2019-03-29
  • 1970-01-01
  • 2015-09-25
  • 2020-04-15
  • 2013-02-11
  • 1970-01-01
  • 1970-01-01
  • 2016-07-21
相关资源
最近更新 更多