C#“动态”无法访问在另一个程序集中声明的匿名类型的属性


87

只要我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

StackTrace:位于System.Dynamic.UpdateDelegates.UpdateAndExecute1 [T0,TRet](CallSite站点,T0 arg0)处位于System.Dynamic.UpdateDelegates.UpdateAndExecute1处,位于C:\ temp中的ConsoleApplication2.Program.Main(String [] args) \ Projects \ ConsoleApplication2 \ ConsoleApplication2 \ Program.cs:System.AppDomain._nExecuteAssembly(RuntimeAssembly程序集,String [] args)位于System.AppDomain.nExecuteAssembly(RuntimeAssembly程序集,String [] args)在System.AppDomain.ExecuteAssembly(字符串assemblyFile,证据assemblySecurity,字符串[] args)
mehanik

在Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()在System.Threading.ThreadHelper.ThreadStart_Context(对象状态)在System.Threading.ExecutionContext.Run(ExecutionContext执行上下文,ContextCallback回调,对象状态,布尔值ignoreSyncCtx)在System.Threading。在System.Threading.ThreadHelper.ThreadStart()处的ExecutionContext.Run(ExecutionContext执行
上下文

有完整源代码的最终解决方案吗?
Kiquenet

Answers:


116

我认为问题在于匿名类型生成为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
谢谢乔恩。使用恰好是程序集私有的类,我只是遇到了同样的问题。
Dave Markle

2
最后,我会喜欢像您的可怕例子那样的事情,只是不那么可怕。要使用:动态道具=新建{Metadata = DetailModelMetadata.Create,PageTitle =“新内容”,PageHeading =“内容管理”}; 并且将命名的道具添加为动态成员会很棒!
ProfK 2011年

开源框架即兴接口对dlr做了很多工作,它具有可用于任何动态或静态对象的内联初始化语法return Build<ExpandoObject>.NewObject(Name:"Micheal", Age: 20);
jbtule 2011年

1
是否有将匿名类型转换为expando的扩展方法的完整源代码示例?
Kiquenet

1
@ Md.lbrahim:基本上不能。您必须对object泛型类型或泛型类型执行此操作(可以要求它是类...),并在执行时检查类型。
乔恩·斯基特

63

您可以[assembly: InternalsVisibleTo("YourAssemblyName")]用来使程序集内部可见。


2
乔恩的答案比较完整,但这实际上为我提供了一个相当简单的解决方法。谢谢:)
kelloti

我在不同的论坛上敲了几个小时,但除了这个回答外,没有找到简单的答案。谢谢卢克。但是,我仍然不明白为什么不能像在同一程序集中那样在程序集外部访问动态类型?我的意思是为什么.Net中存在此限制。
Faisal Mq 2013年

@FaisalMq是因为生成匿名类的编译器将其声明为“内部”。不知道这是真正的原因。
ema 2013年

2
是的,我认为这个答案很重要,因为我不想更改工作代码,我只需要在另一个程序
集中

要在此处添加的一条注释是,您需要在进行此更改后重新启动Visual Studio,以使其起作用。
Rady

11

我遇到了一个类似的问题,想在Jon Skeets的回答中补充说,还有另一种选择。我发现的原因是,我意识到Asp MVC3中的许多扩展方法都使用匿名类作为输入来提供html属性(新的{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);
}

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

希望这可以帮助!

/胜利者


抱歉让您感到困惑。适当时从prop1 => p1更新代码。仍然-整篇文章的想法是提出TypeDescriptor.GetProperties作为解决问题的一种选择,希望无论如何这都是明确的……
Victor

动态无法为我们做到这一点真是愚蠢。我既爱又恨动态。
克里斯·马里西克

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);
    }

1

较干净的解决方案是:

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

现在是ExpandoObject。

请记住参考:

Microsoft.CSharp.dll

1

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

将此[assembly:InternalsVisibleTo(“ YourAssemblyName”)]放入具有返回动态对象的函数的单独项目的\ Properties \ AssemblyInfo.cs中。

“ YourAssemblyName”是调用项目的程序集名称。您可以通过在调用项目中执行Assembly.GetExecutingAssembly()。FullName来实现。


0

ToExpando扩展方法(在乔恩的答案中提到)

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;
    }
}

0

如果您已经在项目中使用Newtonsoft.Json(或者为此目的愿意添加它),则可以实现Jon Skeet在他的答案中引用的可怕扩展方法,如下所示:

public static class ObjectExtensions
{
    public static ExpandoObject ToExpando(this object obj)
        => JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj));
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.