【问题标题】: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);
}

【问题讨论】:

标签: c# .net asp.net-mvc unit-testing anonymous-types


【解决方案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))

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-01
      • 2020-04-18
      • 2012-01-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多