将匿名类型转换为动态类型

47

我有一个函数,它返回一个匿名类型,我想在我的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);
}

8
由于这是用于单元测试,您可以使用InternalsVisibleTo。请参见Anonymous Types are Internal, C# 4.0 Dynamic Beware!感谢@MarcGravell指出匿名对象为internal - TrueWill
+1 对于 InternalsVisibleTo 建议的赞同。非常好用。 - Lars-Erik
5个回答

43

匿名对象的访问权限是 internal,这意味着在声明它们的程序集之外,它们的成员非常受限。而 dynamic 能够遵循访问权限规则,因此似乎不能看到那些成员。如果调用代码位于同一程序集中,则可能会起作用。

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

简而言之:不行。


2
@gdoron 为什么会呢?毕竟它仍然是一个对象。只是它没有暴露更多的东西。ToString(),Equals(),GetHashCode()等仍然可以通过dynamic工作。它只是没有添加任何其他可见的内容。 - Marc Gravell
3
更长的回答可能是:如果你能使用InternalsVisibleTo,那么可能是可以的。请参见本线程下面我给出的答案。 - Per Lundberg

29

这篇博客有一个可行的答案: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)); - Brett Caswell
此外,似乎您不能将其用作匿名对象的扩展,必须使用DynamicExtensions.ToDynamic(data)进行调用/使用。 - Brett Caswell
注意:上面嵌套的匿名代码段似乎对PropertInfo类无效,但在PropertyDescriptor(就像代码示例中一样)中有效。很有趣。 - Brett Caswell

9

如@TrueWill和@Marc Gravell所建议的那样,他们还提到了这篇博客文章

既然这是为了单元测试,你可以使用InternalsVisibleTo。请注意:匿名类型是内部的,C# 4.0 动态要小心!感谢@MarcGravell指出匿名对象是内部的!

底线:如果您想从一个程序集共享匿名对象到另一个程序集,那么设置一个[assembly:InternalsVisibleTo("foo")] 映射即可。在OP的情况下,只需要在MVC控制器项目中设置此映射,指向测试项目。在我的具体情况下,刚好相反(因为我正在将匿名对象从测试项目传递到“生产代码”项目)。

最简单的方法就是在“其他项目”中将其强制转换为dynamic,然后像正常属性一样使用它。这确实起作用,没有任何问题。

底线:我认为Marc Gravell的答案稍微有些不正确;这显然是可以做到的
(前提是您可以修改相关项目,以便按照所需设置InternalsVisibleTo映射,并且对于任何其他原因也不存在问题)。


9

匿名类型是.NET中的常规静态类型,只是您没有为其命名(编译器会为其命名)。这就是为什么将其转换为dynamic无法正常工作的原因。但是,如果您可以控制Foo(),则可以构造并返回一个dynamic对象,而不是匿名对象,然后您的代码将正常工作。下面是应该做的事情:

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

数据是“对象”。在“对象”和“动态”之间几乎没有什么区别,除了在消费者端(这就是有趣的地方)。我不认为在这里在“对象”和“动态”之间进行切换(然后再切换回动态)会有太大的作用。 - Marc Gravell
@MarcGravell,我添加了代码以澄清我的意思是返回动态对象(实际上是“构造一个动态对象并返回它”,而不仅仅是“将返回类型更改为动态”)。感谢您的指出 - 最初的编辑确实不够清晰。 - Sergey Kalinichenko
2
这项工作的主要原因是:ExpandoObject是公共的而不是内部的(当然,它作为公共声明实现了IDynamicBlahBlahBlah接口)。然而,这引发了一个重要的问题:JSON层是否喜欢ExpandoObject?(由于使用IDictionary,它可能喜欢)。这也意味着我们不再使用匿名类型(疑问)。 - Marc Gravell

1
你可以使用NewtonSoft或Asp.net MVC库:
var data = Json.Decode(Json.Encode(_controller.Foo().Data));
var data=JsonConvert.DeserializeObject<Dictionary<string,object>>(JsonConvert.SerializeObject((_controller.Foo().Data))

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