[dynamic] C # ‘동적’은 다른 어셈블리에서 선언 된 익명 형식의 속성에 액세스 할 수 없습니다.

아래 코드는 class 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: ‘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



답변

문제는 익명 유형이 다음과 같이 생성된다는 것입니다. 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")]어셈블리 내부를 표시하는 데 사용할 수 있습니다.


답변

나는 similair 문제에 부딪 혔고 Jon Skeets의 대답에 다른 옵션이 있다는 대답을 추가하고 싶습니다. 내가 알아 낸 이유는 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)를 사용하면 익명 유형이 별도의 어셈블리에서 내부로 생성됨에도 불구하고 속성과 값을 가져올 수 있습니다! 물론 이것은 재귀 적으로 만들기 위해 확장하기가 매우 쉽습니다. 그리고 원한다면 확장 방법을 만드십시오.

도움이 되었기를 바랍니다!

/승리자


답변

다음은 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


답변

아래 솔루션은 내 콘솔 응용 프로그램 프로젝트에서 나를 위해 일했습니다.

이 [assembly : InternalsVisibleTo ( “YourAssemblyName”)]를 동적 개체를 반환하는 함수가있는 별도 프로젝트의 \ Properties \ AssemblyInfo.cs에 넣습니다.

“YourAssemblyName”은 호출 프로젝트의 어셈블리 이름입니다. Assembly.GetExecutingAssembly (). FullName을 통해 프로젝트를 호출하여 실행할 수 있습니다.


답변

용감한 사람들을위한 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;
    }
}