성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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'>(번역글) .NET Internals Cookbook Part 4 - Type members</h1> <p> <br /> 이번에도 .NET Internals Cookbook 시리즈의 4번째 글을 번역한 것입니다.<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 4 - Type members ; <a target='tab' href='https://blog.adamfurmanek.pl/2019/03/09/net-internals-cookbook-part-4/'>https://blog.adamfurmanek.pl/2019/03/09/net-internals-cookbook-part-4/</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'>18. 인터페이스에 "static 생성자(type constructor)"를 추가할 수 있을까?</div> <br /> C#으로는 가능하지 않지만 IL 언어 수준에서는 허용이 됩니다. 예를 들어 다음과 같은 예제 코드를 만들고,<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'>interface IFoo { void DoCall(); }</span> class Program : IFoo { static void Main(string[] args) { Program pg = new Program(); } public void DoCall() { } } </pre> <br /> 빌드한 exe 결과물을 IL 코드로 다시 풀어낸 후,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > c:\temp> <span style='color: blue; font-weight: bold'>ildasm</span> ConsoleApp1.exe /out=test.il </pre> <br /> 생성된 IL 파일에서 IFoo 인터페이스 정의에 직접 IL 코드로 static 생성자를 추가합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ...[생략]... .class interface private abstract auto ansi IFoo { <span style='color: blue; font-weight: bold'>.method private hidebysig specialname rtspecialname static void .cctor() cil managed { // .maxstack 8 IL_0000: nop IL_0001: ldstr "IFoo type constructor" IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // end of method IFoo::.cctor</span> .method public hidebysig newslot abstract virtual instance void DoCall() cil managed { } // end of method IFoo::DoCall } // end of class IFoo ...[생략]... </pre> <br /> 그다음 이것을 다시 ilasm.exe로 빌드하면 정상적인 .NET 어셈블리가 생성됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > c:\temp> <span style='color: blue; font-weight: bold'>ilasm</span> test.il /out=test.exe </pre> <br /> 하지만, 이런 식으로 억지로 추가된 static 생성자가 딱히 어떤 역할을 하지는 않습니다. 왜냐하면, 인터페이스 객체가 생성되는 것은 아니므로 cctor가 호출되지 않기 때문입니다.<br /> <br /> cctor와 관련해서 또 다른 특이한 점은 구조체의 경우 기본 생성자가 호출되는 struct의 경우,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > struct Vector { <span style='color: blue; font-weight: bold'>static Vector()</span> { Console.WriteLine("Vector cctor"); } } static void Main(string[] args) { Vector v = new Vector(); // static Vector.cctor가 호출되지 않음 } </pre> <br /> static 생성자가 호출되지 않는다는 것입니다. 이는 C# 표준 문서에 명시하고 있는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# Language Specification 5.0 ; <a target='tab' href='https://www.microsoft.com/en-us/download/details.aspx?id=7029'>https://www.microsoft.com/en-us/download/details.aspx?id=7029</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 11.3.10 Static constructors Static constructors for structs follow most of the same rules as for classes. The execution of a static constructor for a struct type is triggered by the first of the following events to occur within an application domain: * A static member of the struct type is referenced. * An explicitly declared constructor of the struct type is called. <span style='color: blue; font-weight: bold'>The creation of default values (§11.3.4) of struct types does not trigger the static constructor. (An example of this is the initial value of elements in an array.)</span> </pre> <br /> 따라서 인자를 가진 생성자를 호출해야만 cctor가 호출됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > struct Vector { static Vector() { Console.WriteLine("Vector cctor"); } <span style='color: blue; font-weight: bold'>public Vector(int n)</span> { } } static void Main(string[] args) { Vector v = <span style='color: blue; font-weight: bold'>new Vector(5);</span> } </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'>19. indexer 이름을 바꿀 수 있을까?</div> <br /> 다음과 같이 .NET 1.1버전부터 추가한 IndexerName 특성을 부여해 이름 변경이 가능합니다.<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(); Console.WriteLine(foo[1]); } } class Foo { <span style='color: blue; font-weight: bold'>[System.Runtime.CompilerServices.IndexerName("TheItem")]</span> public int this[int i] { get { return i; } set { } } } </pre> <br /> 이렇게 되면 빌드할 때 C# 컴파일러는 TheItem이라는 이름의 속성으로 이를 구현합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .class private auto ansi beforefieldinit Foo extends [mscorlib]System.Object { .custom instance void [mscorlib]System.Reflection.DefaultMemberAttribute::.ctor(string) = { string('TheItem') } .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { } .property instance int32 <span style='color: blue; font-weight: bold'>TheItem</span> { .get instance int32 Foo::get_TheItem(int32) .set instance void Foo::set_TheItem(int32, int32) } } </pre> <br /> 이렇게 바꿀 수 있도록 한 이유에 대해 원 글(<a target='tab' href='https://blog.adamfurmanek.pl/2019/03/09/net-internals-cookbook-part-4/'>.NET Internals Cookbook Part 4 - Type members</a>)에서는 Indexer 구문을 지원하지 않는 다른 .NET 언어를 위해 프로퍼티를 이름으로 접근할 수 있도록...이라고 하는데 제가 알기로는 그것은 틀린 설명입니다. 왜냐하면 굳이 IndexerName 특성을 사용하지 않아도 기본적으로는 "Item"이라는 이름으로 포함이 되기 때문입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .property instance int32 Item { .get instance int32 Foo::get_Item(int32) .set instance void Foo::set_Item(int32, int32) } </pre> <br /> 이것이 문제가 되는 경우는, Item이라는 이름의 속성을 가져야 할 때 this Indexer 이름과 겹치기 때문입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > class Foo { <span style='color: blue; font-weight: bold'>public int Item { get; set; }</span> // 컴파일 에러 // Error CS0102 The type 'Foo' already contains a definition for 'Item' public int this[int i] { get { return i; } set { } } } </pre> <br /> 따라서 이럴 때 IndexerName을 이용해 이름을 다른 것으로 바꿔주면 Item 속성도 사용할 수 있고 Indexer도 사용할 수 있는 것입니다.<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'>20. 인터페이스에 정적 필드와 메서드를 정의할 수 있을까?</div> <br /> 이것 역시 18번에서 인터페이스의 cctor를 정의한 방법과 동일하게 IL 수준으로 처리할 수 있습니다. 따라서 다음의 C# 코드를 컴파일하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > interface IFoo { } class Program { static void Main(string[] args) { } } </pre> <br /> ildasm.exe로 IL 코드를 구한 다음 IFoo의 코드에 다음과 같이 정적 메서드/필드를 추가할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ...[생략]... .class interface private abstract auto ansi IFoo { <span style='color: blue; font-weight: bold'>.field private static int32 n .method public hidebysig static void Bar() cil managed</span> { // .maxstack 8 IL_0000: nop IL_0001: ldstr "Bar" IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // end of method IFoo::Bar } // end of class IFoo ...[생략]... </pre> <br /> 이를 ilasm.exe로 빌드하면 정상적인 어셈블리 파일이 생성됩니다.<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'>21. "new Foo { Bar = 1 }" 코드는 몇 개의 객체를 생성할까요?</div> <br /> 21번은 개인적으로 왜 이런 질문을 한 건지 이해가 안 됩니다. 다음과 같은 코드에서,<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 { Bar = 5 }; } } class Foo { public int Bar { get; set; } } </pre> <br /> Foo 객체가 몇 개 생성되냐는 질문인데, 당연히 어떻게 생각해도 1개인데 왜 이런 내용을 굳이 넣었는지 모르겠습니다.<br /> <br /> 암튼, C# 3.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'>22. 공변과 반공변이 배열과 제네릭에 어떻게 적용되는가?</div> <br /> 이번 절은 다음의 글로 대신합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 언어의 공변성과 반공변성 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11513'>http://www.sysnet.pe.kr/2/0/11513</a> 자바와 닷넷의 제네릭 차이점 - 중간 언어 및 공변/반공변 처리 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1581'>http://www.sysnet.pe.kr/2/0/1581</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'>23. dynamic 변수로 확장 메서드를 호출할 수 있을까?</div> <br /> dynamic이 .NET Reflection 기술의 하나이고 인스턴스에 대해 호출된다는 것을 안다면 당연히 이 대답은 "NO"임을 알 수 있을 것입니다. 설령 모르더라도 간단하게 다음의 코드로 테스트해보는 것도 가능하고.<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() { int x = 5; x.Extend(); dynamic y = x; y.Extend(); // 실행 시 예외 발생 // Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'int' does not contain a definition for 'Extend' } } static class Foo { public static void Extend(this int x) { Console.WriteLine(x); } } </pre> <br /> 확장 메서드는 C# 코드의 관점에서 마치 객체의 멤버를 호출하는 듯이 보이지만, 내부적으로는 타입의 정적 메서드로 구현이 되기 때문에 trait과 같은 다형성을 요구하는 언어적인 특성을 구현하기에는 맞지 않습니다.<br /> <br /> 참고로, 원 글(<a target='tab' href='https://blog.adamfurmanek.pl/2019/03/09/net-internals-cookbook-part-4/'>.NET Internals Cookbook Part 4 - Type members</a>)에서는 interface와 확장 메서드의 조합으로 동적 바인딩이 가능한 trait 패턴을 흉내 내지만 C# 8.0이 나오면 좀 더 자연스럽게 다룰 수 있게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 8: Default Interface Methods ; <a target='tab' href='http://www.devsanon.com/c/c-8-default-interface-methods/'>http://www.devsanon.com/c/c-8-default-interface-methods/</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> <br /><a name="tag24"></a><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>24. Equals 메서드와 == 연산자의 차이점</div> <br /> Equals 메서드는 System.Object의 가상 메서드로 런타임 시에 바인딩이 결정되지만, == 연산자는 컴파일 시에 (연산자 재정의를 하지 않는 한) ceq IL 코드로 번역이 됩니다. 기타 좀 더 자세한 사항은 다음의 글로 대신합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > == 연산자보다는 Equals 메서드의 호출이 더 권장됩니다. ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/2878'>http://www.sysnet.pe.kr/2/0/2878</a> 연산자 재정의(operator overloading)와 메서드 재정의(method overriding)의 다른 점 - 가상 함수 호출 여부 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/2877'>http://www.sysnet.pe.kr/2/0/2877</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'>25. struct에 기본 생성자(parameterless constructor)를 정의할 수 있을까?</div> <br /> struct에 기본 생성자 정의는 가능하지 않다고 다들 알고 있을 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > struct Vector { public int x; public int y; // 컴파일 에러 // error CS0568: Structs cannot contain explicit parameterless constructors public Vector() { x = 5; y = 10; } } </pre> <br /> 하지만 이것 역시 IL 코드 상으로는 정의할 수 있습니다. 일례로 다음과 같은 코드를,<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; struct Vector { public int x; public int y; } class Program { static void Main(string[] args) { Vector v = new Vector(); Console.WriteLine(v.x); } } </pre> <br /> ildasm.exe 처리로 생성된 IL 코드에 다음의 코드를 넣고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ...[생략]... .class private auto ansi beforefieldinit Point extends [mscorlib]System.Object { .field public int32 x .field public int32 y <span style='color: blue; font-weight: bold'>.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { .maxstack 8 IL_0000: nop IL_0001: ldstr "Parameterless constructor" IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // end of method Foo::.ctor</span> } // end of class Point ...[생략]... </pre> <br /> ilasm.exe를 이용하면 정상적인 어셈블리 생성이 됩니다. 하지만 정의만 된 것일 뿐 C# 코드의 new Vector() 코드에서 IL로 주입한 생성자가 호출되지는 않습니다. 왜냐하면 C# 컴파일러는 new Vector()를 "initobj"라는 IL 코드로 초기화하기 때문입니다. 따라서, 명시적으로 IL 코드 상에서도 생성자 호출을 하도록 다음과 같이 바꿔줘야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .method public hidebysig static void Main() cil managed { .entrypoint .maxstack 1 .locals init ([0] valuetype Foo one) IL_0000: nop <span style='color: blue; font-weight: bold'>IL_0001: ldloca.s one IL_0004: call instance void Foo::.ctor()</span> IL_0011: ret } </pre> <br /> 원 글(<a target='tab' href='https://blog.adamfurmanek.pl/2019/03/09/net-internals-cookbook-part-4/'>.NET Internals Cookbook Part 4 - Type members</a>)에 보면, C# 6.0부터 구조체의 기본 생성자가 지원될 계획이었지만 Activator.CreateInstance의 버그로 누락되었다고 합니다.<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'>26. "new Struct()"와 "default(Struct)"의 차이점이 있을까요?</div> <br /> 일단 원 글(<a target='tab' href='https://blog.adamfurmanek.pl/2019/03/09/net-internals-cookbook-part-4/'>.NET Internals Cookbook Part 4 - Type members</a>)에서는, new Struct는 기본 생성자를 호출하지만 default는 단순히 모든 필드를 0으로만 만든다고 하는데요. 이것이 맞는다고 하면 25번의 글에서 IL 코드에 기본 생성자 호출을 직접 추가하지 않아도 되었을 것이므로 이글 자체가 좀 원칙에 맞지 않습니다.<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;' > struct Vector { } class Program { static void Main(string[] args) { Vector v1 = <span style='color: blue; font-weight: bold'>new Vector();</span> Vector v2 = <span style='color: blue; font-weight: bold'>default(Vector);</span> } } </pre> <br /> 2개의 코드 모두 initobj로 초기화를 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 18 (0x12) .maxstack 1 .locals init ([0] valuetype Vector v1, [1] valuetype Vector v2) IL_0000: nop IL_0001: ldloca.s v1 IL_0003: <span style='color: blue; font-weight: bold'>initobj Vector</span> IL_0009: ldloca.s v2 IL_000b: <span style='color: blue; font-weight: bold'>initobj Vector</span> IL_0011: ret } // end of method Program::Main </pre> <br /> 즉, 차이점이 없습니다. 당연하지 않을까요? 일례로 제네릭 사용 시 default 구문을 사용하게 되는데 그것이 일반적인 코드에서의 new Struct와 다르다면 문제가 발생할 여지가 있습니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1438&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
6912
(왼쪽의 숫자를 입력해야 합니다.)