C # «динамический» не может получить доступ к свойствам от анонимных типов, объявленных в другой сборке - PullRequest
85 голосов
/ 13 апреля 2010

Код ниже работает хорошо, пока у меня есть класс 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 ]

113 голосов
/ 13 апреля 2010

Я полагаю, что проблема в том, что анонимный тип генерируется как 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();

Это довольно ужасно, хотя: (

61 голосов
/ 18 августа 2010

Вы можете использовать [assembly: InternalsVisibleTo("YourAssemblyName")], чтобы сделать ваши внутренние компоненты видимыми.

10 голосов
/ 04 января 2011

Я столкнулся с проблемой similair и хотел бы добавить к ответу Джона Скитса, что есть другой вариант. Причина, по которой я узнал, заключалась в том, что я понял, что многие методы расширения в 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);
}

ТАК - используя TypeDescriptor.GetProperties (o), мы сможем получить свойства и значения, несмотря на то, что анонимный тип создается как внутренний в отдельной сборке! И, конечно, это было бы довольно легко расширить, чтобы сделать его рекурсивным. И сделать метод расширения, если вы хотите.

Надеюсь, это поможет!

/ Victor

2 голосов
/ 19 декабря 2014

Вот элементарная версия метода расширения для 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 голос
/ 11 октября 2016

Более чистое решение было бы:

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

Который теперь является ExpandoObject.

Не забывайте ссылаться:

Microsoft.CSharp.dll
0 голосов
/ 26 октября 2017

Если вы уже используете Newtonsoft.Json в своем проекте (или вы хотите добавить его для этой цели), вы можете реализовать этот ужасный метод расширения, на который ссылается Джон Скит в его ответ вот так:

public static class ObjectExtensions
{
    public static ExpandoObject ToExpando(this object obj)
        => JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj));
}
0 голосов
/ 19 мая 2017

Ниже решение работало для меня в моих проектах консольного приложения

Поместите это [сборка: InternalsVisibleTo ("YourAssemblyName")] в \ Properties \ AssemblyInfo.cs отдельного проекта с функцией, возвращающей динамический объект.

"YourAssemblyName" - это имя сборки вызывающего проекта. Вы можете получить это через Assembly.GetExecutingAssembly (). FullName, выполнив его в вызывающем проекте.

0 голосов
/ 26 мая 2016

Метод расширения 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;
    }
}
...