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


87

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

StackTrace : C : \ temp의 ConsoleApplication2.Program.Main (String [] args)에서 System.Dynamic.UpdateDelegates.UpdateAndExecute1 [T0, TRet] (CallSite 사이트, T0 arg0)의 CallSite.Target (Closure, CallSite, Object)에서 \ Projects \ ConsoleApplication2 \ ConsoleApplication2 \ Program.cs : line 23 at System.AppDomain._nExecuteAssembly (RuntimeAssembly assembly, String [] args) at System.AppDomain.nExecuteAssembly (RuntimeAssembly assembly, String [] args) at System.AppDomain.ExecuteAssembly ( String assemblyFile, Evidence assemblySecurity, String [] args)
mehanik 2010-04-13

System.Threading.ExecutionContext.Run (ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx) at System.Threading.ThreadHelper.ThreadStart_Context (Object state) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly () at System.Threading. ExecutionContext.Run (ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart () InnerException :
mehanik apr

전체 소스 코드가있는 최종 솔루션이 있습니까?
Kiquenet 2013

답변:


116

문제는 익명 유형이 다음과 같이 생성된다는 것입니다. 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();

그래도 꽤 끔찍합니다 :(


1
고마워 Jon. 나는 어셈블리에 사적인 클래스를 사용하여 동일한 문제가 발생했습니다.
Dave Markle 2011 년

2
나는 마지막에 당신의 끔찍한 예와 같은 것을 좋아할 것입니다. 사용하려면 : dynamic props = new {Metadata = DetailModelMetadata.Create, PageTitle = "New Content", PageHeading = "Content Management"}; 명명 된 소품을 동적 ​​멤버로 추가하면 좋을 것입니다!
ProfK 2011

오픈 소스 프레임 워크 즉석 인터페이스 는 모든 동적 또는 정적 개체에 대해 작동 하는 인라인 초기화 구문 이있는 dlr로 많은 작업을 수행합니다. return Build<ExpandoObject>.NewObject(Name:"Micheal", Age: 20);
jbtule 2011-06-07

1
익명 유형을 expando로 변환하는 확장 메서드에 대한 전체 소스 코드 샘플?
Kiquenet 2013

1
@ Md.lbrahim : 기본적으로 할 수 없습니다. object제네릭 유형 에서 또는 제네릭 유형에서 수행해야하며 (클래스 여야 할 수 있습니다 ...) 실행시 유형을 확인해야합니다.
Jon Skeet 2014 년

63

[assembly: InternalsVisibleTo("YourAssemblyName")]어셈블리 내부를 표시하는 데 사용할 수 있습니다.


2
Jon의 대답은 더 완벽하지만 실제로는 합리적으로 간단한 해결 방법을 제공합니다. 감사합니다 :)
kelloti

나는 다른 포럼에서 몇 시간 동안 머리를 두 드렸지만 이것 외에는 간단한 대답을 찾지 못했습니다. 고마워 루크. 하지만 여전히 동일한 어셈블리 에서처럼 어셈블리 외부에서 동적 형식에 액세스 할 수없는 이유를 이해할 수 없습니까? .Net 에서이 제한이 왜 발생하는지 의미합니다.
Faisal Mq 2013 년

@FaisalMq 익명 클래스를 생성하는 컴파일러가이를 "내부"로 선언하기 때문입니다. 진짜 이유가 무엇인지 모릅니다.
ema

2
예,이 답변이 중요하다고 생각합니다. 작업 코드를 변경하고 싶지 않기 때문에 다른 어셈블리에서 테스트하면됩니다
PandaWood

여기에 추가해야 할 한 가지 참고 사항은이 변경 사항이 작동하려면 Visual Studio를 다시 시작해야한다는 것입니다.
Rady

11

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

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

/승리자


혼란스러워서 죄송합니다. 적절한 경우 prop1 => p1에서 코드가 업데이트되었습니다. 그래도 전체 게시물의 아이디어는 TypeDescriptor.GetProperties를 문제를 해결하기위한 옵션으로 제공하는 것이 었습니다. 어쨌든 분명했으면합니다 ...
Victor

다이내믹이 우리를 위해 이것을 할 수 없다는 것은 정말 어리석은 일입니다. 나는 정말 다이나믹을 사랑하고 정말 싫어한다.
Chris Marisic

2

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

더 깨끗한 솔루션은 다음과 같습니다.

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

이제 ExpandoObject입니다.

다음을 참조하십시오.

Microsoft.CSharp.dll

1

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

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

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


0

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

0

프로젝트에서 이미 Newtonsoft.Json을 사용하고 있거나이 목적을 위해 추가하려는 경우 Jon Skeet이 그의 답변에서 다음 과 같이 언급 하는 끔찍한 확장 메서드를 구현할 수 있습니다 .

public static class ObjectExtensions
{
    public static ExpandoObject ToExpando(this object obj)
        => JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj));
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.