C#动态类型存在可能的bug?

5
我在使用C#中的动态变量时遇到了问题。这是在编写NancyFx路由模块时出现的,但我已经将问题简化为下面的示例。虽然在原始代码中我收到了不同的异常,但示例代码仍会抛出异常,我认为这是错误的。有人看到这里发生了什么,或者有其他人遇到过类似的问题吗?
请注意,以下帖子可能与此有关: 通过dynamic访问泛型类型的成员时引发StackOverflowException:.NET / C#框架错误? System.Dynamic bug? 代码如下:
class Program
{
    static void Main(string[] args)
    {
        var dictionary = new Dictionary<string, object>();
        dictionary.Add("number", 12);
        var result = MethodUsesExplicitDeclaration(dictionary);
        var result2 = MethodUsesImplicitDeclaration(dictionary);
    }

    static dynamic MethodUsesExplicitDeclaration(dynamic reallyDictionary)
    {
        // this works, ostensibly because the local variable is explicitly declared
        IDictionary<string, object> dictionary = CastDictionary(reallyDictionary);
        return dictionary.Get<int>("number");
    }

    static dynamic MethodUsesImplicitDeclaration(dynamic reallyDictionary)
    {
        // this throws an exception, and the only difference is 
        // that the variable declaration is implicit
        var dictionary = CastDictionary(reallyDictionary);
        return dictionary.Get<int>("number");
    }

    static IDictionary<string, object> CastDictionary(dynamic arg)
    {
        return arg as IDictionary<string, object>;
    }
}

static class Extensions
{
    public static T Get<T>(this IDictionary<string, object> dictionary, string key)
    {
        var value = dictionary[key];
        if (value is T)
            return (T)value;
        throw new InvalidOperationException();
    }
}

异常情况:Microsoft.CSharp.RuntimeBinder.RuntimeBinderException未处理 HResult=-2146233088 消息='System.Collections.Generic.Dictionary<string,object>'不包含 'Get' 的定义 来源=匿名托管动态方法程序集 堆栈跟踪: 在 CallSite.Target(Closure , CallSite , Object , String ) 在 System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1) 在 DynamicBug.Program.MethodUsesImplicitDeclaration(Object reallyDictionary) in c:\TFS\UnreleasedCode\POC\DynamicBug\DynamicBug\Program.cs:line 28 在 DynamicBug.Program.Main(String[] args) in c:\TFS\UnreleasedCode\POC\DynamicBug\DynamicBug\Program.cs:line 16 内部异常:

1
似乎这个问题已经在这里得到了回答。 - Craig W.
1
我不相信https://dev59.com/a3VC5IYBdhLWcg3wixw0回答了同样的问题。示例代码并没有尝试在声明为dynamic的变量上使用扩展方法,而是在返回接口类型的方法结果上使用它。因此,在执行扩展方法的两个版本中,应用扩展方法的类型都不是动态的。 - Jason Mowry
我可以用另一种方式来问原始问题,即何时var变量声明不应该像您分配给它的值类型那样行为? - Jason Mowry
2
Jon Skeet在引用回复的第一部分中指出:“动态查找将无法找到扩展方法。” 您会收到一个错误,指出Get不存在。 Get是一个扩展方法。 看起来它应该适用。 如果您将该行更改为return Extensions.Get<int>(dictionary, "number");,则可以按预期执行。 - Craig W.
Cédric Bignon的回答解释了为什么会发生这种情况,包括在C#规范中定义此行为的位置。 - Jarrod Dixon
1个回答

5
问题在于,当您没有将对象显式分配给 IDictionary<string,object> 声明时,该对象仍将是动态类型(请参见图像中的类型解析)。
正如Eric Lippert在https://dev59.com/yG435IYBdhLWcg3wnBd8#5313149中指出的那样,对于动态类型,扩展方法在调用站点不可用:
引用:

为了扩展Jon的答案,之所以这行不通,是因为在常规的非动态代码中,扩展方法通过对编译器已知的所有类进行完整搜索来工作,以查找具有匹配扩展方法的静态类。搜索按命名空间嵌套和每个命名空间中可用的“using”指令顺序进行。

这意味着为了正确解析动态扩展方法调用,某种方式DLR必须在运行时知道源代码中所有的命名空间嵌套和“using”指令是什么。我们没有一个机制可用于将所有这些信息编码到调用站点中。我们考虑发明这样一种机制,但决定成本太高,产生了太多的时间表风险,不值得。

我将您的代码加载到测试方法中,以显示在运行时与显式声明相比实际解析的var dictionary
显式声明:

enter image description here enter image description here

隐式声明:

enter image description here enter image description here

如果您查看 dictionary 的类型解析,则显式声明具有类型 System.Collection.Generic.IDictionary,而隐式声明具有类型dynamic{System.Collections.Generic.Dictionary}

我从未使用过dynamic关键字,除了玩弄它之外,这是很好的信息。一旦变成动态类型,难道没有将其转换回静态类型的方法吗? - TyCobb
将其分配给所需的类型或使用 as 关键字。(例如 var dict = CastDictionary(reallyDictionary) as IDictionary<string,object>) - wbennett
但是,他的CastDictionary已经返回一个IDictionary - TyCobb
像这样(Extensions.Get<int>(dictionary,"number")) - wbennett
我明白你的意思了。我错过了第一个截图,其中没有使用var,而且dictionary变成了非动态类型。非常感谢。我以为编译器会正确地从CastDictionary中推断出类型,但无论如何,.NET都将类型保持为动态类型。谢谢。 - TyCobb
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接