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

Posted

技术标签:

【中文标题】C#“动态”无法访问在另一个程序集中声明的匿名类型的属性【英文标题】:C# ‘dynamic’ cannot access properties from anonymous types declared in another assembly 【发布时间】:2011-02-07 11:48:15 【问题描述】:

只要我在与Program 相同的程序集中有类ClassSameAssembly,下面的代码就可以正常工作。 但是,当我将类 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: 'object' 不包含'Name' 的定义

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: 在 CallSite.Target(Closure , CallSite , Object ) at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,Tret](CallSite site, T0 arg0) at ConsoleApplication2.Program.Main(String[ ] args) 在 C:\temp\Projects\ConsoleApplication2\ConsoleApplication2\Program.cs:第 23 行在 System.AppDomain._nExecuteAssembly(RuntimeAssembly 程序集,String[] args) 在 System.AppDomain.nExecuteAssembly(RuntimeAssembly 程序集,String[] args ) 在 System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) 在 Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() 在 System.Threading.ThreadHelper.ThreadStart_Context(Object state) 在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() InnerException: 有完整源代码的最终解决方案吗? 【参考方案1】:

我认为问题在于匿名类型生成为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;

但这也好不了多少……

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

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

这太可怕了:(

【讨论】:

谢谢乔恩。我只是在使用一个恰好是程序集私有的类时遇到了同样的问题。 我会喜欢你最后的可怕例子,只是没有那么可怕。使用:动态道具 = new Metadata = DetailModelMetadata.Create, PageTitle = "New Content", PageHeading = "Content Management";并将命名道具添加为动态成员会很棒! 开源框架impromptu interface 对dlr 做了很多工作,它有一个inline initialization syntax,适用于任何动态或静态对象。 return Build<ExpandoObject>.NewObject(Name:"Micheal", Age: 20); 任何用于将匿名类型转换为扩展方法的完整源代码示例? @Md.lbrahim:你不能,基本上。您必须在 object 或泛型类型上执行此操作(您可以要求它是一个类...)并在执行时检查类型。【参考方案2】:

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

【讨论】:

乔恩的回答更完整,但这实际上为我提供了一个相当简单的解决方法。谢谢:) 我在不同的论坛上敲了好几个小时,但除了这个之外没有找到简单的答案。谢谢卢克。但是我仍然无法理解为什么动态类型不能像在同一个程序集中那样在程序集之外访问?我的意思是为什么 .Net 中有这个限制。 @FaisalMq 这是因为生成匿名类的编译器将它们声明为“内部”。不知道哪个是真正的原因。 是的,我认为这个答案很重要,因为我不想更改工作代码,我只需要从另一个程序集测试它 这里要添加的一个注意事项是,您需要在此更改后重新启动 Visual Studio 才能使其正常工作。【参考方案3】:

我遇到了一个类似的问题,并想在 Jon Skeets 的回答中补充说还有另一种选择。我发现的原因是我意识到 Asp MVC3 中的许多扩展方法使用匿名类作为输入来提供 html 属性(new , 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 作为解决问题的选项,希望无论如何都清楚......【参考方案4】:

这是 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);
    

【讨论】:

【参考方案5】:

更清洁的解决方案是:

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

现在是 ExpandoObject。

记得参考:

Microsoft.CSharp.dll

【讨论】:

【参考方案6】:

以下解决方案在我的控制台应用程序项目中对我有用

把这个[程序集:InternalsVisibleTo("YourAssemblyName")] 在具有函数返回动态对象的单独项目的 \Properties\AssemblyInfo.cs 中。

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

【讨论】:

【参考方案7】:

勇敢者的 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;
    

【讨论】:

【参考方案8】:

如果您已经在项目中使用 Newtonsoft.Json(或者您愿意为此添加它),您可以实现 Jon Skeet 在 @ 中提到的 horrible 扩展方法987654321@这样的:

public static class ObjectExtensions

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

【讨论】:

以上是关于C#“动态”无法访问在另一个程序集中声明的匿名类型的属性的主要内容,如果未能解决你的问题,请参考以下文章

C#访问修饰符

C# 五大修饰符

C#中的匿名类型

C#中4个访问修饰符(随笔)

C#超级有用的一种类型—匿名类型

如何检查 C# 中的动态匿名类型上是不是存在属性?