为什么我必须手动创建ExpandoObject才能正确使用dynamic关键字

本文关键字:关键字 dynamic ExpandoObject 创建 为什么 | 更新日期: 2023-09-27 17:57:50

我正在研究使用"dynamic"抛出RuntimeBinderException的问题。我也面临类似的问题:

基本上,我想在ASP.NET MVC中创建一个使用动态参数的"HTML助手",类似于许多现有助手的htmlArguments参数(下面的更多代码):

public BootstrapCell(Action<string> emitContentAction, dynamic args)

视图:

@using (grid.Cell(ViewContext.Writer.Write, new {Position = 4}))
{
     <p>zomg!</p>
}

然而,在天真的方法中,我得到了RuntimeBinderException,声明'object' does not contain a definition for 'Position',即使在调试和悬停在_args变量上时,它显然有一个Position属性。

调用者和被调用者位于不同的程序集中。为什么会出现这种问题?

(解决方案已经在同一个问题中显示:手动创建一个ExpandoObject来保存args。)

实施:

public class Cell
{
    private readonly string _tagName;
    private dynamic _args;
    private Action<string> EmitContentAction;
    public BootstrapCell(Action<string> emitContentAction, dynamic args) : DisposableBaseClass
    {
        _args = args;
        EmitContentAction = emitContentAction;
        OnContextEnter();
    }
    protected void OnContextEnter()
    {
        var sb = new StringBuilder("<");
        sb.Append(_tagName);
        if (_args.Position > 0)
        {
            sb.Append(" class='"offset");
            sb.Append(args.Position);
            sb.Append("'"");
        }
        sb.Append(">");
        EmitContentAction(sb.ToString());
    }
}

[编辑以更清楚地表明,当"显然"设置了Position属性时,我的问题就会出现。我知道,如果该属性从未在一开始就定义过,则必须引发异常。]

为什么我必须手动创建ExpandoObject才能正确使用dynamic关键字

该代码存在致命缺陷。

只要您指定了属性:,它就可以工作

void Bar()
{
    Foo(new {Position = 0});
}
void Foo(dynamic args)
{
    Console.WriteLine(args.Position);
}

它将输出0,而不会抛出RuntimeBinderException

但这种代码的目的是让调用者只指定所需的属性,而忽略其余的属性。
您正试图通过if(args.Position != null)检查此遗漏。但这并不奏效,它已经需要Position的存在。

当您查看同样支持这些匿名配置对象的ASP.NET路由API时,您会注意到参数的类型是object,而不是dynamic
使用object而不是dynamic将使API能够跨程序集边界使用。

那么它是如何工作的呢

就像在链接答案中一样,您需要手动创建属性的字典。您是使用普通的旧Dictionary<string, object>还是使用ExpandoObject是一个偏好问题
使用ExpandoObject将使代码的读写更加简单,但这不是必需的。


关于您得到的实际异常:
请注意,它告诉您在object上找不到Position属性。如果是缺少Position属性的匿名类型,则异常消息不会引用object,而是引用匿名类型。类似这样的东西:

'<>f__AnonymousType0'不包含"位置"的定义