【问题标题】:C# dynamic type gotchaC# 动态类型陷阱
【发布时间】:2013-02-26 14:53:16
【问题描述】:

我刚刚遇到了最奇怪的事情,我现在有点mind = 吹......

以下程序可以正常编译,但是当您运行它时,当您尝试读取Value 时会得到RuntimeBinderException。 'object' does not contain a definition for 'Value'

class Program
{
    interface IContainer
    {
        int Value { get; }
    }

    class Factory
    {
        class Empty : IContainer
        {
            public int Value
            {
                get { return 0; }
            }
        }

        static IContainer nullObj = new Empty();

        public IContainer GetContainer()
        {
            return nullObj;
        }
    }

    static void Main(string[] args)
    {
        dynamic factory = new Factory();
        dynamic container = factory.GetContainer();
        var num0 = container.Value; // WTF!? RuntimeBinderException, really?
    }
}

这是令人兴奋的部分。将嵌套类型 Factory+Empty 移到 Factory 类之外,如下所示:

class Empty : IContainer
{
    public int Value
    {
        get { return 0; }
    }
}

class Factory...

程序运行良好,有人愿意解释这是为什么吗?

编辑

在我的编码冒险中,我当然做了一些我应该首先考虑的事情。这就是为什么你看到我对私有类和内部类之间的区别有点漫不经心。这是因为我设置了 InternalsVisibleToAttribute,这使我的测试项目(在本例中消耗了这些位)的行为方式与它们的行为方式相同,这完全是设计使然,尽管从一开始就暗示了我。

阅读 Eric Lippert 的回答以获得对其余部分的良好解释。

真正让我感到警惕的是,动态绑定器考虑到了实例类型的可见性。我有大量的 JavaScript 经验,作为一名真正没有公共或私有之类的 JavaScript 程序员,我完全被可见性很重要的事实所愚弄,我的意思是毕竟,我访问这个成员就像它属于公共接口类型(我认为 dynamic 只是反射的语法糖),但动态绑定器不能做出这样的假设,除非你给它一个提示,使用简单的强制转换。

【问题讨论】:

  • 如果它是public class Empty : IContainer 怎么办,否则它不只是工厂类的私有吗?可能是为什么它找不到要绑定的实现类。
  • 让你的班级为空公开
  • 看这里:Dynamic Gotchas。
  • 我猜@Fuex 想要说明的是,在某些情况下你可以用动态做“更少”的事情。 Array.Count 说明了这一点,即使问题不同。无论如何,使用动态访问私有类的内部类是一个陷阱恕我直言。如果没有接口,它仍然会很有趣,只有 Empty 和 Value 是内部的。
  • 奇怪的行为是正确的。我不在公共汽车上的时候会写一个解释。

标签: c# dynamic types


【解决方案1】:

我想,在运行时,容器方法调用只是在私有 Empty 类中解析,这会使您的代码失败。据我所知,动态不能用于访问私有成员(或私有类的公共成员)

这应该(当然)工作:

var num0 = ((IContainer)container).Value;

在这里,Empty 类是私有的:因此您不能在声明类(工厂)之外操作 Empty 实例。这就是您的代码失败的原因。

如果 Empty 是内部的,您将能够在整个程序集中操纵它的实例,(嗯,并不是因为 Factory 是私有的)允许所有动态调用,并且您的代码可以工作。

【讨论】:

  • 确实如此,但是它究竟是如何设法找出作为(公共接口方法)的方法在某种程度上是私有的并且不能被调用?这完全没有意义。特别是因为将其设置为内部(这使其组件成为私有)不会导致同样的问题。
  • 为什么动态活页夹要区分私有和内部...:/
  • 但这是不合逻辑的——你可以通过via公开访问属性,不能通过动态解析。
  • @JohnLeidegren 看看 Fuex 评论。 Array.Count 上的示例同样不合逻辑,结论的最后一句话是对动态的很好总结;-)
  • @jbl 阅读我对 Fuex 评论的跟进。我们没有使用显式接口实现,如果我们当时改变可见性应该不会产生影响。
【解决方案2】:

C# 中“动态”的基本原则是:在运行时对表达式进行类型分析就好像运行时类型是编译时类型。那么让我们看看如果我们真的这样做会发生什么:

    dynamic num0 = ((Program.Factory.Empty)container).Value;

该程序将失败,因为Empty 不可访问。 dynamic 不允许您进行一开始就非法的分析。

但是,运行时分析器意识到了这一点,并决定稍微作弊。它会问自己“是否有可以访问的 Empty 基类?”答案显然是肯定的。所以它决定回退到基类并分析:

    dynamic num0 = ((System.Object)container).Value;

失败是因为该程序会给你一个“对象没有名为值的成员”错误。你得到的错误是什么。

动态分析从来没有说“哦,你一定是故意的”

    dynamic num0 = ((Program.IContainer)container).Value;

因为当然如果这就是你的意思,那你一开始就是这么写的。同样,dynamic 的目的是回答问题如果编译器知道运行时类型会发生什么,并且转换为接口不会给您运行时类型。

当您将Empty 移到外面时,动态运行时分析器会假装您写了:

    dynamic num0 = ((Empty)container).Value;

现在Empty 可以访问并且演员表是合法的,所以你得到了预期的结果。


更新:

可以将该代码编译成程序集,引用该程序集,如果 Empty 类型在默认情况下使其成为内部的类之外,它将起作用

我无法重现所描述的行为。让我们尝试一个小例子:

public class Factory
{
    public static Thing Create()
    {
        return new InternalThing();
    }
}
public abstract class Thing {}
internal class InternalThing : Thing
{
    public int Value {get; set;}
}

> csc /t:library bar.cs

class P
{
    static void Main ()
    {
        System.Console.WriteLine(((dynamic)(Factory.Create())).Value);
    }
}

> csc foo.cs /r:bar.dll
> foo
Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 
'Thing' does not contain a definition for 'Value'

您会看到这是如何工作的:运行时绑定程序检测到InternalThing 在外部程序集的内部,因此在 foo.exe 中无法访问。所以它回退到公共基类型Thing,它是可访问的,但没有必要的属性。

我无法重现您描述的行为,如果您可以重现它,那么您发现了一个错误。如果你有这个 bug 的小副本,我很乐意将它传递给我以前的同事。

【讨论】:

  • @jonskeet:您可能希望将此添加到您的“动态陷阱”页面。
  • ...但是为什么要区分内部和私有。在我的世界中,这两种类型(作为程序集的消费者)对我来说都是不可见的,但是当所讨论的类型是内部的时它可以工作,但如果它是嵌套的私有成员则失败。这个我不明白。
  • 在我的问题中,我可以将该代码编译成一个程序集,引用该程序集,如果 Empty 类型是嵌套的并且至少在类内部或外部(这将使,默认情况下至少是内部的)。
  • 实际上,情况变得更糟了,我无法像我刚才所说的那样用内部重现问题(这没有意义),但如果程序集是通过 CSharpCodeProvider 构建的,我可以做到班级。不知何故,在这种情况下,我似乎可以通过动态类型访问内部......
  • 哦,天哪,白痴过去了,这只发生在我的测试项目中,当然我的测试项目被标记为InternalsVisibleToAttribute。去图吧。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多