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



ниже код работает хорошо, пока у меня есть класс ClassSameAssembly в той же сборке, что и класс Program.
Но когда я перемещаю класс ClassSameAssembly в отдельную сборку, a 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:tempProjectsConsoleApplication2ConsoleApplication2Program.cs:line 23
1156   9  

9 ответов:

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

Это правда ужасно :(

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

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

надеюсь, что это помогает!

/Виктор

вот рудиментарная версия метода расширения для 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);
    }

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

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

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

Не забудьте ссылку:

Microsoft.CSharp.dll

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

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

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

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

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

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

Comments

  1. Alex
    Alex 4 года назад
    <p>C# насилуют </p>