将匿名类型转换为动态

Posted

技术标签:

【中文标题】将匿名类型转换为动态【英文标题】:Casting anonymous type to dynamic 【发布时间】:2012-01-17 21:42:05 【问题描述】:

我有一个返回匿名类型的函数,我想在我的 MVC 控制器中对其进行测试。

public JsonResult Foo()

    var data = new
                  
                      details = "something",
                      more = "More"
                  ;
    return Json(data);

我想验证从 Foo 函数获得的数据,我现在正在做的是获取数据类型并通过反射获取它的属性值。

[Test]
public void TestOne()

    var data = _controller.Foo().Data;
    var details = data.GetType().GetProperty("details").GetValue(data, null);
    var more = data.GetType().GetProperty("more").GetValue(data, null);

    Assert.AreEquals("something", details);
    Assert.AreEquals("More", more);

有没有类似这样的简单方法来检查匿名属性?

[Test]
public void TestTwo()

    var data = (dynamic) _controller.Foo().Data;
    var details = data.details; // RunTimeBinderException object does not contain definition for details
    var more = data.more;

    Assert.AreEquals("something", details);
    Assert.AreEquals("More", more);

【问题讨论】:

由于这是用于单元测试,您可以使用InternalsVisibleTo。见Anonymous Types are Internal, C# 4.0 Dynamic Beware! 感谢@MarcGravell 指出匿名对象是internal +1 表示 InternalsVisibleTo 建议。像魅力一样工作。 【参考方案1】:

匿名对象是internal,这意味着它们的成员在声明它们的程序集之外非常受限。 dynamic 尊重可访问性,因此假装无法看到这些成员。如果调用站点在同一个程序集中,我希望它会起作用。

您的反射代码尊重 member 可访问性,但绕过了类型的可访问性 - 因此它可以工作。

简而言之:没有。

【讨论】:

@gdoron 为什么会这样?毕竟,它仍然是一个对象。它只是没有暴露太多其他东西。 ToString()、Equals()、GetHashCode() 等仍将通过动态方式工作。它只是没有添加任何其他可见的东西。 更长的答案:也许是的,如果你可以使用InternalsVisibleTo。在此线程中进一步查看我的答案。【参考方案2】:

这个博客有一个有效的答案:http://blog.jorgef.net/2011/06/converting-any-object-to-dynamic.html - 谢谢@Jorge-Fioranelli。

public static class DynamicExtensions 
    public static dynamic ToDynamic(this object value) 
        IDictionary<string, object> expando = new ExpandoObject();

        foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(value.GetType()))
            expando.Add(property.Name, property.GetValue(value));

        return expando as ExpandoObject;
    

【讨论】:

这对我正在做的一些编码练习很有用,但应该修改它以处理也是匿名的属性。 bool isNotPrimitiveOrString = !(property.PropertyType.IsPrimitive || Equals(property.PropertyType, typeof(string))); expando.Add(property.Name, (isNotPrimitiveOrString) ? property.GetValue(value).ToDynamic() : property.GetValue(value)); 另外,您似乎不能将其用作匿名对象的扩展,必须是 DynamicExtensions.ToDynamic(data) 调用/使用。 注意:上述嵌套匿名的 sn-p 似乎不适用于 PropertInfo 类,但适用于 PropertyDescriptor(如代码示例中所示)。很有趣。【参考方案3】:

匿名类型是 .NET 中的常规静态类型,只是您没有给它命名(但是编译器会命名)。这就是为什么将其转换为 dynamic 不起作用的原因。但是,如果您可以控制Foo(),则可以构造并返回dynamic 对象而不是匿名对象,然后您的代码就可以工作了。这应该可以解决问题:

dynamic JsonResult Foo() 
    dynamic data = new ExpandoObject();
    data.details = "something";
    data.mode = "More";
    return Json(data);

【讨论】:

.Data 是“对象”。 “对象”和“动态”之间几乎没有区别,除了在消费者(这是它变得有趣的地方)。我不相信“对象”和“动态”之间的变化(然后回到动态)在这里会起到很大的作用。 @MarcGravell 我添加了代码来澄清我所说的返回动态的意思(我实际上的意思是“构造一个动态对象并返回它”,而不仅仅是“将返回类型更改为动态的”)。感谢您接受它 - 最初的编辑确实不清楚。 这项工作的主要内容是:ExpandoObject 是公共的而不是内部的(当然,IDynamicBlahBlahBlah 接口作为公共声明实现)。然而,它提示了一个重要的问题:JSON 层像 ExpandoObject 吗? (由于 IDictionary 的使用,它可能)。这也意味着我们不再使用匿名类型(问题)【参考方案4】:

正如@TrueWill 和@Marc Gravell 所建议的那样,他们也提到了this blog post

由于这是用于单元测试,您可以使用 InternalsVisibleTo。请参阅匿名类型是内部的,C# 4.0 动态 当心!感谢@MarcGravell 指出匿名对象是内部的!

底线:如果您想将匿名对象从一个程序集共享到另一个程序集,请设置[assembly: InternalsVisibleTo("foo")] 映射。在 OP 的情况下,这将是在 MVC 控制器项目中设置它的问题,参考 测试项目。在我的具体情况下,反过来(因为我将一个匿名对象从我的测试项目传递到“生产代码”项目)。

“其他项目”中能够使用它的最简单方法肯定是将其转换为dynamic,然后像正常一样使用属性。它确实有效,没有任何问题。

所以,底线:我觉得 Marc Gravell 的回答有点不正确;这显然可以做到 (如果您可以修改相关项目,因此您可以相应地设置 InternalsVisibleTo 映射,这不会因任何其他原因造成问题)。

【讨论】:

【参考方案5】:

您可以使用 NewtonSoft 或 Asp.net MVC 库:

var data = Json.Decode(Json.Encode(_controller.Foo().Data));

var data=JsonConvert.DeserializeObject&lt;Dictionary&lt;string,object&gt;&gt;(JsonConvert.SerializeObject((_controller.Foo().Data))

【讨论】:

以上是关于将匿名类型转换为动态的主要内容,如果未能解决你的问题,请参考以下文章

匿名托管的 DynamicMethods 程序集:无法将类型“字符串”隐式转换为“整数”

在 C# 中将匿名类型转换为键/值数组?

转换为匿名类型

动态创建匿名类型? [复制]

无法将匿名类型从“System.Linq.IQueryable”转换为“System.Data.Entity.Core.Objects.ObjectQuery”

C# 是否可以将 动态或匿名类型 转成 强类型 ?