C#'dynamic'无法访问另一个程序集中声明的匿名类型的属性

时间:2010-04-13 14:34:01

标签: dynamic c#-4.0 anonymous-types

只要我在类ClassSameAssembly的同一程序集中有类Program,下面的代码就可以正常运行。 但是当我将类ClassSameAssembly移动到单独的程序集时,会抛出RuntimeBinderException(见下文)。 有可能解决它吗?

using System;

namespace ConsoleApplication2
{
    public static class ClassSameAssembly
    {
        public static dynamic GetValues()
        {
            return new
            {
                Name = "Michael", Age = 20
            };
        }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            var d = ClassSameAssembly.GetValues();
            Console.WriteLine("{0} is {1} years old", d.Name, d.Age);
        }
    }
}

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException'对象'不包含“姓名”的定义

at CallSite.Target(Closure , CallSite , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
at ConsoleApplication2.Program.Main(String[] args) in C:\temp\Projects\ConsoleApplication2\ConsoleApplication2\Program.cs:line 23

8 个答案:

答案 0 :(得分:113)

我认为问题在于匿名类型是以internal生成的,因此活页夹并不真正“知道”它本身。

尝试使用ExpandoObject:

public static dynamic GetValues()
{
    dynamic expando = new ExpandoObject();
    expando.Name = "Michael";
    expando.Age = 20;
    return expando;
}

我知道这有点难看,但这是我现在能想到的最好的...我认为你甚至不能使用它的对象初始化器,因为虽然它被强类型为ExpandoObject编译器不知道如何处理“名称”和“年龄”。您可以能够执行此操作:

 dynamic expando = new ExpandoObject()
 {
     { "Name", "Michael" },
     { "Age", 20 }
 };
 return expando;

但那并没有好多少......

您可以潜在地编写扩展方法,通过反射将匿名类型转换为具有相同内容的expando。然后你可以写:

return new { Name = "Michael", Age = 20 }.ToExpando();

但这太可怕了:(

答案 1 :(得分:61)

您可以使用[assembly: InternalsVisibleTo("YourAssemblyName")]使装配体内部可见。

答案 2 :(得分:10)

我遇到了类似问题,并希望在Jon Skeets的答案中添加另一种选择。我发现的原因是我意识到Asp MVC3中的许多扩展方法使用匿名类作为输入来提供html属性(new {alt =“Image alt”,style =“padding-top:5px”} =>

无论如何 - 这些函数使用RouteValueDictionary类的构造函数。我自己试过,确定它有效 - 虽然只有第一级(我使用了多级结构)。所以 - 在代码中这将是:

object o = new {
    name = "theName",
    props = new {
        p1 = "prop1",
        p2 = "prop2"
    }
}
SeparateAssembly.TextFunc(o)

//In SeparateAssembly:
public void TextFunc(Object o) {
  var rvd = new RouteValueDictionary(o);

//Does not work:
Console.WriteLine(o.name);
Console.WriteLine(o.props.p1);

//DOES work!
Console.WriteLine(rvd["name"]);

//Does not work
Console.WriteLine(rvd["props"].p1);
Console.WriteLine(rvd["props"]["p1"]);

所以...这里到底发生了什么?在RouteValueDictionary中查看此代码(值〜= o以上):

foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(values))
    object obj2 = descriptor.GetValue(values);
    //"this.Add" would of course need to be adapted
    this.Add(descriptor.Name, obj2);
}

SO - 使用TypeDescriptor.GetProperties(o)我们将能够获得属性和值,尽管匿名类型在单独的程序集中被构造为内部!当然,这很容易扩展以使其递归。并根据需要制作扩展方法。

希望这有帮助!

/维克多

答案 3 :(得分:2)

这是ToExpandoObject扩展方法的基本版本,我确信它有抛光的余地。

    public static ExpandoObject ToExpandoObject(this object value)
    {
        // Throw is a helper in my project, replace with your own check(s)
        Throw<ArgumentNullException>.If(value, Predicates.IsNull, "value");

        var obj = new ExpandoObject() as IDictionary<string, object>;

        foreach (var property in value.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            obj.Add(property.Name, property.GetValue(value, null));
        }

        return obj as ExpandoObject;
    }

    [TestCase(1, "str", 10.75, 9.000989, true)]
    public void ToExpandoObjectTests(int int1, string str1, decimal dec1, double dbl1, bool bl1)
    {
        DateTime now = DateTime.Now;

        dynamic value = new {Int = int1, String = str1, Decimal = dec1, Double = dbl1, Bool = bl1, Now = now}.ToExpandoObject();

        Assert.AreEqual(int1, value.Int);
        Assert.AreEqual(str1, value.String);
        Assert.AreEqual(dec1, value.Decimal);
        Assert.AreEqual(dbl1, value.Double);
        Assert.AreEqual(bl1, value.Bool);
        Assert.AreEqual(now, value.Now);
    }

答案 4 :(得分:1)

更清洁的解决方案是:

var d = ClassSameAssembly.GetValues().ToDynamic();

现在是ExpandoObject。

请记住参考:

Microsoft.CSharp.dll

答案 5 :(得分:0)

勇敢者的ToExpando扩展方法(在Jon的回答中提到)

public static class ExtensionMethods
{
    public static ExpandoObject ToExpando(this object obj)
    {
        IDictionary<string, object> expando = new ExpandoObject();
        foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(obj))
        {
            var value = propertyDescriptor.GetValue(obj);
            expando.Add(propertyDescriptor.Name, value == null || new[]
            {
                typeof (Enum),
                typeof (String),
                typeof (Char),
                typeof (Guid),
                typeof (Boolean),
                typeof (Byte),
                typeof (Int16),
                typeof (Int32),
                typeof (Int64),
                typeof (Single),
                typeof (Double),
                typeof (Decimal),
                typeof (SByte),
                typeof (UInt16),
                typeof (UInt32),
                typeof (UInt64),
                typeof (DateTime),
                typeof (DateTimeOffset),
                typeof (TimeSpan),
            }.Any(oo => oo.IsInstanceOfType(value))
                ? value
                : value.ToExpando());
        }

        return (ExpandoObject)expando;
    }
}

答案 6 :(得分:0)

以下解决方案在我的控制台应用程序项目中为我工作

把这个[assembly:InternalsVisibleTo(&#34; YourAssemblyName&#34;)] 在具有返回动态对象的函数的单独项目的\ Properties \ AssemblyInfo.cs中。

&#34; YourAssemblyName&#34;是调用项目的程序集名称。你可以通过在调用项目中执行它来通过Assembly.GetExecutingAssembly()。FullName获得它。

答案 7 :(得分:0)

如果您已经在项目中使用Newtonsoft.Json(或者您愿意为此目的添加它),那么您可以实现Jon Skeet在{中引用的可怕的扩展方法{3}}像这样:

public static class ObjectExtensions
{
    public static ExpandoObject ToExpando(this object obj)
        => JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj));
}