성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
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'>(번역글) .NET Internals Cookbook Part 3 - Initialization tricks</h1> <p> 이번에도 .NET Internals Cookbook 시리즈의 3번째 글을 번역한 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET Internals Cookbook Part 3 - Initialization tricks ; <a target='tab' href='https://blog.adamfurmanek.pl/2019/03/02/net-internals-cookbook-part-3/'>https://blog.adamfurmanek.pl/2019/03/02/net-internals-cookbook-part-3/</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>13. 변수의 값이 true이면서 동시에 false가 될 수 있을까?</div> <br /> 우선 일반적인 논리 식이라면 그런 경우가 없을 것입니다. 하지만 객체의 상태를 true/false로 평가할 수 있도록 op_False, op_True 연산자를 재정의했을 때 이런 트릭이 가능합니다.<br /> <br /> 예를 들어 전기 스위치가 있을 때, 스위치의 객체 상태를 true/false로 평가하기 위해 다음과 같이 연산자를 재정의할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > class Switch { public string Name { get; private set; } public bool On { get; private set; } public Switch(string name, bool on) { Name = name; On = on; } <span style='color: blue; font-weight: bold'>public static bool operator true(Switch t)</span> { Console.WriteLine($"{t.Name} op.True"); <span style='color: blue; font-weight: bold'>return t.On;</span> } <span style='color: blue; font-weight: bold'>public static bool operator false(Switch t)</span> { Console.WriteLine($"{t.Name} op.False"); <span style='color: blue; font-weight: bold'>return t.On == false;</span> } } </pre> <br /> 이렇게 코딩하면 Switch 인스턴스를 조건식에 그대로 사용할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Switch foo1 = new Switch("A", true); Switch foo2 = new Switch("B", false); if (<span style='color: blue; font-weight: bold'>foo1</span>) { Console.WriteLine($"{foo1.Name} ON"); } if (<span style='color: blue; font-weight: bold'>foo2</span>) { Console.WriteLine($"{foo2.Name} ON"); } /* A op.True A ON B op.True */ </pre> <br /> 위의 결과로, C# 컴파일러는 if 문에서 객체를 평가하는 경우 op_True와 op_False 메서드 중에 op_True를 선택해 호출해 주는 것을 알 수 있습니다. 즉, 위의 코드는 사실상 다음과 같이 바뀌어 컴파일되는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > if (<span style='color: blue; font-weight: bold'>Switch::op_True(foo1)</span>) { Console.WriteLine($"{foo1.Name} ON"); } if (<span style='color: blue; font-weight: bold'>Switch::op_True(foo2)</span>) { Console.WriteLine($"{foo2.Name} ON"); } </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;' > if (foo1 && foo2) { Console.WriteLine("If"); } </pre> <br /> C# 컴파일러는 다음과 같은 식으로 && 평가를 전개합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // && 논리 곱의 short-circuit 평가이므로 첫 번째 변수가 false인지를 우선 검사 if (Switch::op_False(foo1)) { if (Switch::op_True(foo1)) // 피연산자가 2개인 && 평가에서 왜? foo2에 대한 op_True 검사를 생략할까? { Console.WriteLine("If"); } } else // foo1이 true인 상태라면 당연히 두 번째 변수의 상태를 평가 { Foo bitwised = Switch::op_BitwiseAnd(foo1, foo2); if (Switch::op_True(bitwised)) { Console.WriteLine("If"); } } </pre> <br /> 개인적인 이해 차원에서는, 이건 좀 아니라는 생각이 듭니다. 첫 번째 변수가 false 상태라면 굳이 그 변수에 대해 op_True 검사를 할 필요가 없습니다. 설령, 확실하게 하기 위해 검증 차원에서 필요하다고 해도 당연히 두 번째 변수에 대한 평가를 추가해야 하는데 그 과정이 없습니다.<br /> <br /> 또 한가지 재미있는 것은, 첫 번째 변수의 평가가 true라고 된 경우 두 번째 변수에 대해 곧바로 op_True 평가를 하는 것이 아니라 첫 번째 변수와 두 번째 변수의 비트 곱(&) 연산자를 호출해 하나의 결과로 합친 다음 그것에 대해 op_True 평가를 한다는 것입니다. 이 때문에 객체에 대해 && 논리곱을 하려면 비트 곱(&) 연산자도 함께 재정의해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>public static Switch operator &(Switch lhs, Switch rhs)</span> { Console.WriteLine($"{lhs.Name} & {rhs.Name}"); if (lhs.On == false) { return lhs; } return rhs; } </pre> <br /> 이 정도 알았으면 이제 질문에 대한 답을 할 수 있습니다. 즉, true/false에 대한 연산자 재정의에서 모두 옳다고 반환하면 그 객체는 true이면서 false인 상태를 갖는 것과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static bool operator true(Switch t) { Console.WriteLine($"{t.Name} op.True"); <span style='color: blue; font-weight: bold'>return true;</span> } public static bool operator false(Switch t) { Console.WriteLine($"{t.Name} op.False"); <span style='color: blue; font-weight: bold'>return true;</span> } </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;' > if (foo1 && foo2) { Console.WriteLine("If"); } </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;' > A op.False A op.True If </pre> <br /> 참고로, 논리합 연산자(||)라면 첫 번째 피연산자에 대한 평가를 short-circuit 검사를 위해 op_True를 호출합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>14. 생성자 호출 없이 객체 생성을 할 수 있을까?</div> <br /> FormatterServices 타입의 GetUninitializedObject 메서드를 호출해 객체 생성을 할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Runtime.Serialization; public class Program { public static void Main() { Foo o = FormatterServices.GetUninitializedObject(typeof(Foo)) as Foo; Console.WriteLine(o.n); // 출력 결과: 0 } } class Foo { public int n = 5; // C# 컴파일러는 필드 초기화 코드를 생성자에 넣음. Foo() { Console.WriteLine("Constructor"); } } </pre> <br /> 당연히 생성자를 호출하지 않고 객체가 소유한 필드의 메모리만큼 할당만 한 후에 객체 주소를 반환하기 때문에 필드 n의 값은 0이 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>15. 생성자를 여러 번 호출할 수 있을까?</div> <br /> ctor도 메서드이기 때문에 Reflection을 이용해 임의 호출이 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; public class Program { public static void Main() { Foo foo = new Foo(); var ctor = foo.GetType().GetConstructor(new Type[0]); ctor.Invoke(foo, new object[0]); ctor.Invoke(foo, new object[0]); ctor.Invoke(foo, new object[0]); } } class Foo { public int n = 5; public Foo() { Console.WriteLine("Constructor!"); } } /* 출력 결과 Constructor! Constructor! Constructor! Constructor! */ </pre> <br /> 따라서 14번 문제와 합치면 객체 생성 과정을 다음과 같이 수작업으로 재구성할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Runtime.Serialization; public class Program { public static void Main() { var ctor = typeof(Foo).GetConstructor(new Type[0]); Foo o = FormatterServices.GetUninitializedObject(typeof(Foo)) as Foo; ctor.Invoke(o, new object[0]); Console.WriteLine(o.n); // 출력 결과: 5 } } </pre> <br /> <hr style='width: 50%' /><br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>16. 생성자에서 null 반환이 가능할까?</div> <br /> ContextBoundObject를 상속받아 생성자 호출을 가로채면 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Runtime.Remoting.Proxies; namespace ConsoleApp { class Program { public static void Main() { var instance = new Foo(); <span style='color: blue; font-weight: bold'>Console.WriteLine(instance == null);</span> } } class FooProxyAttribute : ProxyAttribute { public override MarshalByRefObject <span style='color: blue; font-weight: bold'>CreateInstance</span>(Type serverType) { Console.WriteLine("Custom new"); <span style='color: blue; font-weight: bold'>return null;</span> } } [FooProxy] class Foo : ContextBoundObject { } } </pre> <br /> 여기서 재미있는 것은, "var instance"의 null 여부입니다. .NET Framework의 경우 instance는 null이지만, <a target='tab' href='https://blog.adamfurmanek.pl/2019/03/02/net-internals-cookbook-part-3/'>.NET Internals Cookbook Part 3 ? Initialization tricks</a> 글에서 링크한 <a target='tab' href='https://tio.run/##dY/BTsMwDIbvfQprp@5AH4CKw1YJCQkEKkgcEIc0NSOoTarYKaumPXtxu8I6GD7EifX/8f9putCk@z6QsRt47IixTqP5K8mDZVNjkmPtWObJg3dbg5RGkVU1UqM0QuYsuQpXTRPtIpDSlSICkW68qsfJYT5UE4rKaCBWLK11poQ7ZWy8/FEctUO1yoOxIrey6QosfsK1c/EyPVFNEZJnbxhvjcX46BFTqKqZYR8dzllW@XIg61bM3hSBES7hdHAew7XovSlRGDy9q2rd5fh2X3ygZsg8KsabKUf81DUIhF4cw/U/3r8kiywQu3pAX/zC9sjB25HvDN7YX77RXk9xBVBWMW557YItp8w78e37/gs'>Mono 런타임</a>에서는 null이 아닙니다.<br /> <br /> 아마도 이것은, .NET Framework의 런타임은 null이 반환된 경우 ContextBoundObject로 인한 Proxy 객체 자체를 null 처리해서 반환하는 반면, Mono의 경우에는 대상 객체는 null을 반환하지만 그 null을 가리키는 Proxy 객체를 반환하기 때문인 것으로 보입니다. 참고로, 이때의 Proxy 객체 타입은 System.Runtime.Remoting.Proxies.__TransparentProxy라는 식의 이름을 갖습니다.<br /> <br /> <hr style='width: 50%' /><br /> <a name='17'></a> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>17. 인터페이스 타입의 인스턴스를 생성할 수 있을까?</div> <br /> 인터페이스는 new할 수 없지만, .NET Framework이 기존 COM 객체와의 연동을 위해 마련한 편의 장치를 이용하면 생성할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Runtime.InteropServices; public class Program { public static void Main() { IFoo obj = <span style='color: blue; font-weight: bold'>new IFoo</span>("abc"); Console.WriteLine(obj.Message); } } class Foo : IFoo { readonly string name; public Foo(string name) { this.name = name; } string IFoo.Message { get { return "Hello from " + name; } } } [ComImport, <span style='color: blue; font-weight: bold'>CoClass(typeof(Foo))</span>] [Guid("d60908eb-fd5a-4d3c-9392-8646fcd1edce")] interface IFoo { string Message { get; } } </pre> <br /> 사실 엄밀히 말하면, C# 코드 상으로는 new IFoo를 하기 때문에 인터페이스를 생성하는 것처럼 보이지만 내부적으로 C# 컴파일러는 해당 인터페이스에 지정된 CoClass 특성에 지정된 타입을 생성하는 것으로 치환하므로,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static void Main() { IFoo obj = <span style='color: blue; font-weight: bold'>new Foo</span>("abc"); Console.WriteLine(obj.Message); } </pre> <br /> 인터페이스를 생성한다고 볼 수는 없습니다. 그냥 겉으로만 그렇게 보이는 것에 불과합니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1437&boardid=331301885'>첨부 파일은 이 글의 예제 프로젝트를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
2014
(왼쪽의 숫자를 입력해야 합니다.)