(번역글) .NET Internals Cookbook Part 11 - Various C# riddles
이번에도 .NET Internals Cookbook 시리즈의 11번째 글을 번역한 것입니다.
.NET Internals Cookbook Part 11 - Various C# riddles
; https://blog.adamfurmanek.pl/2019/04/27/net-internals-cookbook-part-11/
75. TimeSpan의 분해능은?
문서에 따라,
The value of a TimeSpan object is the number of ticks that equal the represented time interval. A tick is equal to 100 nanoseconds, or one ten-millionth of a second.
100 나노초 단위입니다.
76. AppDomain 간에 공유되는 것은?
다음의 정보들이 공유됩니다.
- 도메인 중립 타입의 객체들
- PropertyInfo와 같은 리플렉션 타입들
- 문자열
- 스레드
다중 도메인 간에 공유되는 객체를 일컬어 "marshal-by-bleed"라고 합니다. 이런 타입들은 특히 동기화를 위한 lock을 걸 때 더 주의를 요합니다. 서로 다른 AppDomain 간의 동기화를 할 수 있으므로 한편으로는 유용하긴 하지만 무심코 lock(typeof(Foo))와 같은 식으로 리플렉션 타입에 lock을 거는 경우 전체 AppDomain 들 간에 동기화가 걸리므로 다중 웹 애플리케이션을 호스팅하는 ASP.NET과 같은 환경에서 성능 저하를 유발할 수 있습니다.
참고로, 다음은 CrossDomainData 타입의 인스턴스를 AppDomain 간에 공유해 값을 조작하는
예제입니다.
using Endjin.Assembly.ChangeDetection.Infrastructure;
using System;
using System.Linq;
using System.Reflection;
using System.Runtime;
class Program
{
[LoaderOptimization(LoaderOptimization.MultiDomain)]
static public void Main(string[] args)
{
for (int i = 0; i < 10000; i++)
{
var other = AppDomain.CreateDomain("Test" + i.ToString(), AppDomain.CurrentDomain.Evidence, new AppDomainSetup
{
});
DomainGate gate = (DomainGate)other.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(DomainGate).FullName);
CrossDomainData data = new CrossDomainData();
data.Input = Enumerable.Range(0, 10).ToList();
DomainGate.Send(gate, data);
Console.WriteLine("Calculation in other AppDomain got: {0}", data.Aggregate);
}
}
}
class DomainGate : MarshalByRefObject
{
public void DoSomething(int gcCount, IntPtr objAddress)
{
if (gcCount != ObjectAddress.GCCount)
{
throw new NotSupportedException("During the call a GC did happen. Please try again.");
}
CrossDomainData data = (CrossDomainData)PtrConverter<Object>.ConvertFromIntPtr(objAddress);
foreach (var x in data.Input)
{
Console.WriteLine(x);
}
data.Aggregate = data.Input.Aggregate((x, y) => x + y);
}
public static void Send(DomainGate gate, object o)
{
var old = GCSettings.LatencyMode;
try
{
GCSettings.LatencyMode = GCLatencyMode.Batch; // try to keep the GC out of our stuff
var addandGCCount = ObjectAddress.GetAddress(o);
gate.DoSomething(addandGCCount.Value, addandGCCount.Key);
}
finally
{
GCSettings.LatencyMode = old;
}
}
}
Main 메서드의
LoaderOptimization.MultiDomain 옵션을 제거하면 CrossDomainData data;를 공유할 수 없어 다음과 같은 예외가 발생합니다.
Unhandled Exception: System.InvalidCastException: [A]CrossDomainData cannot be cast to [B]CrossDomainData. Type A originates from 'ConsoleApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\temp\ConsoleApp1\bin\Debug\ConsoleApp1.exe'. Type B originates from 'ConsoleApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\temp\ConsoleApp1\bin\Debug\ConsoleApp1.exe'.
at DomainGate.DoSomething(Int32 gcCount, IntPtr objAddress) in C:\temp\ConsoleApp1\Program.cs:line 54
at DomainGate.DoSomething(Int32 gcCount, IntPtr objAddress)
at DomainGate.Send(DomainGate gate, Object o) in C:\temp\ConsoleApp1\Program.cs:line 75
at Program.Main(String[] args) in C:\temp\ConsoleApp1\Program.cs:line 30
77. callback을 해제하지 않은 상태에서 Timer에 대한 별도의 참조가 없다면 삭제될까요?
다음의 예제 코드에서 확인할 수 있듯이,
using System;
using System.Threading;
public class Program
{
public static void Main(string[] args)
{
var stateTimer = new Timer(t => Console.WriteLine("Timer!"), null, 50, 50);
Thread.Sleep(70);
stateTimer = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Thread.Sleep(1500);
}
}
/* 출력 결과 (한 번만 출력)
Timer!
*/
삭제됩니다.
78. delegate {...}와 delegate() {...}의 차이점
전자의 경우는 어떤 인자라도 받을 수 있지만 후자의 경우에는 인자가 없어야만 합니다. 확인은 다음의 코드로.
using System;
namespace Program
{
public delegate void Foo();
public delegate void Bar(int x);
public class Program
{
public static void Main(string[] args)
{
Foo a = delegate { };
Bar b = delegate { };
Foo c = delegate () { };
// Error CS1593 Delegate 'Bar' does not take 0 arguments
Bar d = delegate () { };
}
}
}
79. C# 어셈블리에서 DllMain 함수를 가질 수 있을까?
사실 C/C++에서의 DllMain과 정확히 일치하는 함수를 구현할 수는 없지만 그와 유사한 역할을 하는 메서드는 IL로 구현할 수 있습니다.
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 4:0:0:0
}
.assembly 'f66978ab-21bd-416d-b1fd-2cc8d4467cef'
{
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module 'f66978ab-21bd-416d-b1fd-2cc8d4467cef.dll'
// MVID: {15C95762-8B92-4BE6-9ABD-A280C050496E}
.imagebase 0x10000000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x015B0000
.method public hidebysig specialname rtspecialname void .cctor() cil managed
{
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Module initializer!"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
}
.class public auto ansi beforefieldinit Program.Program
extends [mscorlib]System.Object
{
.method public hidebysig static void Main(string[] args) cil managed
{
//
.entrypoint
.maxstack 8
IL_0000: nop
IL_000c: ret
} // end of method Program::Main
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
//
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Program::.ctor
} // end of class Program.Program
ilasm.exe로 컴파일하고 실행하면 "Module initializer"를 출력하는 것을 볼 수 있습니다.
C:\temp> ilasm test.il /quiet
test.il(21) : warning : Non-static global method '.cctor', made static
C:\temp> test
Module initializer!
.NET Reflector 등으로 확인해 보면 <Module> 타입의 cctor로 등록된 것을 확인할 수 있습니다.
static <Module>()
{
Console.WriteLine("Module initializer!");
}
예전에 저도 Module 타입에 대해 다룬 적이 있었죠. ^^
닷넷 - <Module> 클래스의 용도
; https://www.sysnet.pe.kr/2/0/11335
80. Nullable<T> 타입을 직접 구현할 수 있을까?
Nullable 타입은 특별하게 대우를 받기 때문에 그와 동일한 타입을 C#으로 구현할 수는 없습니다. 대신 struct로 유사하게 구현할 수는 있는데요.
using System;
namespace Program
{
public class Program
{
public static void Main(string[] args)
{
Nullable<int> i = 5;
Console.WriteLine(i.GetType()); // System.Int32
OwnNullable<int> j = 6;
Console.WriteLine(j.GetType()); // OwnNullable`1[System.Int32]
i = null;
// Error CS0037 Cannot convert null to 'OwnNullable<int>' because it is a non - nullable value type
// j = null;
}
}
}
struct OwnNullable<T>
where T : struct
{
private T field;
public OwnNullable(T value)
{
field = value;
}
public static implicit operator OwnNullable<T>(T value)
{
return new OwnNullable<T>(value);
}
}
보는 바와 같이 GetType의 결과도 런타임의 배려가 있다는 것을 확인할 수 있습니다. 게다가 순수 struct이기 때문에 null 대입이 안 되는 등의 문제가 있습니다.
81. 제한된 접근 제한자가 지정된 타입을 보다 공개된 접근 제한자를 갖는 타입에 상속할 수 있을까?
일반적으로는 다음과 같이 오류가 발생하지만,
public class Foo
{
private class IBar
{
}
// Error CS0060 Inconsistent accessibility: base class 'Foo.IBar' is less accessible than class 'Foo.Bar'
//public class Bar : IBar
//{
//}
}
인터페이스로는 가능합니다.
public class Foo
{
private interface IBar
{
}
public class Bar : IBar
{
}
}
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]