С# 'не может обращаться к свойствам анонимных типов, объявленных в другой сборке

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

Ответ 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;

но это не намного лучше...

Вы могли бы написать метод расширения для преобразования анонимного типа в expando с тем же содержимым посредством отражения. Затем вы можете написать:

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

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

Ответ 2

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

Ответ 3

Я столкнулся с проблемой similair и хотел бы добавить к Jon Skeets ответ, что есть еще один вариант. Причина, по которой я узнал, заключалась в том, что я понял, что многие методы расширения в Asp MVC3 используют анонимные классы для ввода атрибутов html (new {alt= "Image alt", style = "padding-top: 5px" } = >

В любом случае - эти функции используют конструктор класса RouteValueDictionary. Я пробовал это сам, и, конечно же, это работает - хотя и только на первом уровне (я использовал многоуровневую структуру). SO - в коде это будет:

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"]);

SO... Что здесь происходит? Заглядывание в 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), мы могли бы получить свойства и значения, несмотря на то, что анонимный тип был сконструирован как внутренний в отдельной сборке! И, конечно, это было бы довольно легко расширить, чтобы сделать его рекурсивным. И чтобы сделать метод расширения, если вы хотите.

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

/Victor

Ответ 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

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

Ответ 7

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

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

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

Ответ 8

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

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