성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - 클래스 안에 구조체를 포함하는 경우 발생하는 dynamic 키워드의 부작용</h1> <p> <br /> 재미있는 질문입니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > dynamic 변수 할당은 도대체 어디에???? ; <a target='tab' href='http://www.sysnet.pe.kr/3/0/1076'>http://www.sysnet.pe.kr/3/0/1076</a> </pre> <br /> 문제의 코드를 단순화하면 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public <span style='color: blue; font-weight: bold'>struct People</span> { public int Age; } public class Casting { <span style='color: blue; font-weight: bold'>public People People;</span> } static void Main(string[] args) { <span style='color: blue; font-weight: bold'>dynamic casting = new Casting();</span> casting.People.Age = 19; Console.WriteLine(casting.People.Age); // 0이 출력됨 } </pre> <br /> 위의 상황에서 struct People 정의를 class People로 하면 정상적으로 출력이 됩니다. 그런 걸로 볼 때, 문제의 원인은 boxing/unboxing으로 인한 부작용이 맞는 것 같습니다.<br /> <br /> 문제를 좀 더 구체화시켜 볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { dynamic casting = new Casting(); <span style='color: blue; font-weight: bold'> People p1 = (People)casting.People; People p2 = (People)casting.People;</span> p1.Age = 5; p2.Age = 10; Console.WriteLine(p1.Age); // 출력값: 5 Console.WriteLine(p2.Age); // 출력값: 10 } </pre> <br /> 보시는 것처럼, casting.People로 값을 받아올 때마다 별도의 struct 인스턴스가 생기는 것을 확인할 수 있습니다. 확실하게 Object ID를 다음과 같이 구해볼 수도 있습니다.<br /> <br /> dynamic casting = new Casting();<br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > System.Runtime.Serialization.ObjectIDGenerator obj = new System.Runtime.Serialization.ObjectIDGenerator(); bool firstTime; long id1 = obj.GetId(casting.People, out firstTime); long id2 = obj.GetId(casting.People, out firstTime); Console.WriteLine(id1); // 출력값: 1 Console.WriteLine(id2); // 출력값: 2 </pre> <br /> 분명히, casting.People를 호출할 때마다 생성되는 인스턴스의 ObjectID 값이 다릅니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이에 대한 구체적인 원인을 파악하려면 .NET Reflector를 이용해보면 됩니다. 생성되는 코드를 단순화하기 위해 다음과 같이 예제 코드를 만들고 빌드한 다음,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { dynamic casting = new Casting(); People p1 = (People)casting.People; } </pre> <br /> .NET Reflector에서 확인해 보면 CallSite 코드 호출로 바뀌는 것을 볼 수 있습니다. CallSite는 public이기 때문에 우리가 직접 호출해도 무방한데요. 그래서 최종적으로 코드를 다음과 같이 바꾸는 것도 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { object casting = new Casting(); CallSite<Func<CallSite, object, People>> site1 = CallSite<Func<CallSite, object, People>>.Create( Binder.Convert(CSharpBinderFlags.ConvertExplicit, typeof(People), typeof(Program))); CallSite<Func<CallSite, object, object>> site2 = CallSite<Func<CallSite, object, object>>.Create( Binder.GetMember(CSharpBinderFlags.None, "People", typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) })); <span style='color: blue; font-weight: bold'>People p1 = site1.Target.Invoke(site1, site2.Target.Invoke(site2, casting));</span> } </pre> <br /> 실제로 dynamic 키워드는 C# 컴파일러에 의해서 위와 유사하게 변경이 됩니다.<br /> <br /> 복잡하긴 해도, 천천히 뜯어보면 이해못할 것도 없습니다. ^^<br /> <br /> 우선, site1.Target.Invoke 먼저 살펴볼까요? CallSite.Target 을 .NET Reflector로 살펴보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class CallSite<T> : CallSite where T: class { ...[생략]... [__DynamicallyInvokable] public T Target; } </pre> <br /> T generic 개체를 반환합니다. 즉, CallSite<T>에 지정된 Func<CallSite, object, People> 인스턴스가 되는 것입니다. Func 개체는 .NET에서 delegate로 정의되어 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089"), __DynamicallyInvokable] public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2); </pre> <br /> 반환 타입이 TResult로 되어 있고, 이것은 Func 정의에서 3번째 Generic 타입으로 지정된 것입니다. 아하... 그럼 답이 나왔군요.<br /> <br /> CallSite 정의에서 실제로 "People" 멤버값을 반환하는 것은 site2입니다. site2의 반환값은 아래와 같이 object로 지정되어 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CallSite<Func<<span style='color: blue; font-weight: bold'>CallSite, object, object</span>>> site2 = CallSite<Func<CallSite, object, object>>.Create( Binder.GetMember(CSharpBinderFlags.None, "People", typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) })); </pre> <br /> 따라서, site2.Target.Invoke의 결과값은 object가 되는 것입니다. 이 때문에, class 내부에 정의된 Value Type들은 모두 boxing이 되어 Heap에 복사되고 이후의 변경들은 모두 그 Heap에 복사된 개체를 대상으로 이루어지는 것과 마찬가지입니다.<br /> <br /> 정리해 보면, 다음의 코드는 결국,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > dynamic casting = new Casting(); casting.People.Age = 19; </pre> <br /> 아래와 같이 코딩하는 것과 마찬가지 효과를 갖습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Casting cast = new Casting(); cast.People.Age = 6; object objPeople = cast.People; // boxing으로 인해 heap에 복사됨 People heapPeople = (People)objPeople; // heap에 있는 인스턴스가 unboxing 된 것임. heapPeople.Age = 10; Console.WriteLine(cast.People.Age); // 출력값: 6 Console.WriteLine(heapPeople.Age); // 출력값: 10 </pre> <br /> <hr style='width: 50%' /><br /> <br /> 그런데, 왜? 다음의 결과는 제대로 동작하는지에 대해서 묻고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > dynamic onePeople = new People(); onePeople.Age = 20; Console.WriteLine(onePeople.Age); </pre> <br /> 아쉽게도 위의 것은 '제대로 동작하는 것처럼 보일 뿐' 실제로 의도했던 결과는 아닙니다. 예를 들어, 위의 코드를 풀어서 문제를 확인해 보면 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Casting cast = new Casting(); cast.People.Age = 10; dynamic onePeople = cast.People; onePeople.Age = 15; Console.WriteLine(cast.People.Age); // 출력값: 10 Console.WriteLine(onePeople.Age); // 출력값: 15 </pre> <br /> 즉, onePeople.Age는 다시 boxing 되어 반환된 Heap 개체의 값을 다루는 것일 뿐, 원본 cast 개체에 담겨진 인스턴스를 대상으로 하는 것은 아닙니다.<br /> <br /> 사실, 문제가 되었던 코드도 위와 같은 식으로 변경해 주면 마찬가지로 보존된 값이 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > dynamic casting = new Casting(); casting.People.Age = 19; Console.WriteLine(casting.People.Age); // 0이 출력됨 ==> dynamic casting = new Casting(); <span style='color: blue; font-weight: bold'>People p1 = (People)casting.People;</span> p1.Age = 19; Console.WriteLine(p1.Age); // 출력값: 19 </pre> <br /> <br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
5677
(왼쪽의 숫자를 입력해야 합니다.)