问题描述
我刚刚遇到了最奇怪的事情,我现在有点头脑=爆炸...
I just ran into the strangest thing and I'm a bit mind = blown at the moment...
下面的程序编译得很好,但是当您运行它时,当您尝试读取 Value
时,您会得到一个 RuntimeBinderException
.'object' 不包含 'Value' 的定义
The following program compiles fine but when you run it you get a RuntimeBinderException
when you try to read Value
. '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
类之外,如下所示:
Here's the mind blowing part. Move the nested type Factory+Empty
outside of the Factory
class, like so:
class Empty : IContainer
{
public int Value
{
get { return 0; }
}
}
class Factory...
而且程序运行得很好,有人愿意解释这是为什么吗?
And the program runs just fine, anyone care to explain why that is?
在我的编码冒险中,我当然做了一些我应该首先考虑的事情.这就是为什么你会看到我对 class private 和 internal 之间的区别漫不经心.这是因为我设置了 InternalsVisibleToAttribute
,它使我的测试项目(在本例中消耗了比特)按照他们的方式运行,这完全是设计的,尽管从一开始就暗指我.
In my adventure of coding I of course did something I should have thought about first. That's why you see me rambling a bit about the difference between class private and internal. This was because I had set the InternalsVisibleToAttribute
which made my test project (which was consuming the bits in this instance) behave the way they did, which was all by design, although alluding me from the start.
阅读 Eric Lippert 的回答,以获得对其余部分的一个很好的解释.
Read Eric Lippert's answer for a good explanation of the rest.
真正让我感到警惕的是,动态绑定器考虑了实例类型的可见性.我有很多 JavaScript 经验,作为一个 JavaScript 程序员,在那里真的没有公共或私有之类的东西,我完全被可见性很重要的事实所迷惑,我的意思是毕竟,我正在访问这个成员,好像它是公共接口类型(我认为动态只是反射的语法糖)但动态绑定器不能做出这样的假设,除非你给它一个提示,使用简单的转换.
What caught me really of guard was that the dynamic binder takes the visibility of the type of the instance in mind. I have a lot of JavaScript experience and as a JavaScript programmer where there really isn't such a thing as public or private, I was completely fooled by the fact that the visibility mattered, I mean after all, I was accessing this member as if it was of the public interface type (I thought dynamic was simply syntactic sugar for reflection) but the dynamic binder cannot make such an assumption unless you give it a hint, using a simple cast.
推荐答案
C# 中动态"的基本原则是:在运行时对表达式进行类型分析就好像运行时类型是编译时一样输入.那么让我们看看如果我们真的这样做会发生什么:
The fundamental principle of "dynamic" in C# is: at runtime do the type analysis of the expression as though the runtime type had been the compile time type. So let's see what would happen if we actually did that:
dynamic num0 = ((Program.Factory.Empty)container).Value;
那个程序会失败,因为 Empty
不可访问.dynamic
不允许您进行一开始就非法的分析.
That program would fail because Empty
is not accessible. dynamic
will not allow you to do an analysis that would have been illegal in the first place.
然而,运行时分析器意识到这一点并决定作弊.它自问是否有可访问的 Empty 基类?"答案显然是肯定的.所以它决定回退到基类并分析:
However, the runtime analyzer realizes this and decides to cheat a little. It asks itself "is there a base class of Empty that is accessible?" and the answer is obviously yes. So it decides to fall back to the base class and analyzes:
dynamic num0 = ((System.Object)container).Value;
失败是因为该程序会给您一个对象没有名为 Value 的成员"错误.这是您遇到的错误.
Which fails because that program would give you an "object doesn't have a member called Value" error. Which is the error you are getting.
动态分析从不说哦,你一定是这个意思"
The dynamic analysis never says "oh, you must have meant"
dynamic num0 = ((Program.IContainer)container).Value;
当然是因为如果这就是你的意思,那这就是你一开始就会写的.同样,dynamic
的目的是回答问题如果编译器知道运行时类型会发生什么,并且转换到接口不会给你运行时类型.
because of course if that's what you had meant, that's what you would have written in the first place. Again, the purpose of dynamic
is to answer the question what would have happened had the compiler known the runtime type, and casting to an interface doesn't give you the runtime type.
当您将 Empty
移到外面时,动态运行时分析器会假装您写道:
When you move Empty
outside then the dynamic runtime analyzer pretends that you wrote:
dynamic num0 = ((Empty)container).Value;
现在 Empty
可以访问并且强制转换是合法的,所以你得到了预期的结果.
And now Empty
is accessible and the cast is legal, so you get the expected result.
更新:
可以将该代码编译成一个程序集,引用该程序集,如果 Empty 类型在类之外,默认情况下将使其成为内部类型,它将起作用
我无法重现所描述的行为.让我们尝试一个小例子:
I am unable to reproduce the described behaviour. Let's try a little example:
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
,它是可访问的,但没有必要的属性.
And you see how this works: the runtime binder has detected that InternalThing
is internal to the foreign assembly, and therefore is inaccessible in foo.exe. So it falls back to the public base type, Thing
, which is accessible but does not have the necessary property.
我无法重现您描述的行为,如果您可以重现它,那么您就发现了一个错误.如果您有该错误的小副本,我很乐意将其传递给我以前的同事.
I'm unable to reproduce the behaviour you describe, and if you can reproduce it then you've found a bug. If you have a small repro of the bug I am happy to pass it along to my former colleagues.
这篇关于C# 动态类型问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!