레이블이 c#인 게시물을 표시합니다. 모든 게시물 표시
레이블이 c#인 게시물을 표시합니다. 모든 게시물 표시

2020년 6월 23일 화요일

C# 객체에서의 unmanged/manged resource 처리 관련 (Dispose pattern)

예전에 C# RAII(Resource Acquisition Is Initialization) 게시물에서 적기는 했었는데

C# garbage collection의 정책상 object instance가 scope을 벗어나거나 더이상 사용되지 않더라도 바로 destory되지 않으므로
- 명시적인 resource 해제를 위한 IDisposable 인터페이스를 구현하고
- using문을 사용하여 object가 관리(생성,사용,삭제)되도록 해야 한다.


아래는 IDisposable interface에 대한 내용인데 Dispose pattern을 소개하고 있다. 

IDisposable interface

Implement a Dispose method


Dispose pattern 예제 코드이다. 영어 주석을 간략히 한글로 달았음.

    public class MyResource: IDisposable
    {
        // 외부에서 전달받은 unmanaged 리소스
        private IntPtr handle;
        // 내부에서 생성하는 managed 리소스
        private Component component = new Component();
        // Dispose가 호출 되었음을 알려주는 flag
        private bool disposed = false;

        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // 리소스를 해제하기 위한 method
        // 하위 class에서 재정의 하지 못하도록 virtual이 아님
        public void Dispose()
        {
            Dispose(true);
            // object가 위 Dispose메서드 호출로 정리되지만 
            // object가 finalization queue에 포함되어 종료자/finalizer가 호출되지 않도록 
            // 아래 명령어를 호출한다.
            GC.SuppressFinalize(this);
        }

        // 아래 함수가 호출되는 경우는 두가지인데
        // 첫번째는(disposing == true) 사용자의 코드를 통해 호출되는 경우, 이 때는 managed/unmanaged 리소스를 해제해야 한다.
        // 두번째는(disposing == false) finalizer가 호출한 상황으로 다른 obejct가 어떤상황인지 모르므로 unmanaged resource만 해제해야 한다.
        protected virtual void Dispose(bool disposing)
        {
            if(!this.disposed)
            {
                if(disposing)
                {
                    // 첫번째 경우 managed 리소스 해제
                    component.Dispose();
                }
                // 첫번째, 두번째 경우 unmanaged 리소스 해제
                CloseHandle(handle);
                handle = IntPtr.Zero;
                // dispose가 호출되었음을 기록
                disposed = true;
            }
        }

        // unmanaged 리소스를 해제하기 위한 interop
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // finalization을 위한 종료자/finalizer(소멸자/destructor)
        // Dispose()가 호출되면 아래 코드는 호출되지 않는다.
        ~MyResource()
        {
            Dispose(false);
        }
    }


위 Dispose pattern을 따라 구현하면
- Dispose()를 명시적으로 호출하여 자원을 해제하게 하거나
  사용자가 Dispose() 호출을 잊어버렸을 때 자원을 해제할 수 있게 한다.
- Dispose()를 여러번 호출해도 문제가 없음
- 종료자/finalizer 가 호출 될 때 object가 Dispose가 된 상태라도 문제가 없다.

하지만 이 pattern이 자원 해제와 메모리 해제의 결합도를 가지게하여 종료자 스레드의 부담을 증가한다는 점이 있다고 한다.

함께 위 패턴을 사용함에 있어 Dispose 구현을 사용하게 하려면 Using 문이나 Try/Finally 블록을 사용하면 된다. 어차피 Using이 Try/Finally 생성처럼 동작한다고 함.

Using block has three parts: acquisition, usage, and disposal.

  • Acquisition means creating a variable and initializing it to refer to the system resource. The Using statement can acquire one or more resources, or you can acquire exactly one resource before entering the block and supply it to the Using statement. If you supply resourceexpression, you must acquire the resource before passing control to the Using statement.

  • Usage means accessing the resources and performing actions with them. The statements between Using and End Using represent the usage of the resources.

  • Disposal means calling the Dispose method on the object in resourcename. This allows the object to cleanly terminate its resources. The End Using statement disposes of the resources under the Using block's control.


만약 부모 클래스를 상속하는 자식 클래스가 있다면 자식 클래스에서는 다음과 같은 패턴을 구현해야 한다고 함.

Subclasses should implement the disposable pattern as follows:

  • They must override Dispose(Boolean) and call the base class Dispose(Boolean) implementation.

  • They can provide a finalizer if needed. The finalizer must call Dispose(false).


아래 예제에서는 종료자/finalizer는 구현하지 않는 자식클래스 예제임.

class DerivedClass : BaseClass
{
   bool disposed = false;
   SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true);

   protected override void Dispose(bool disposing)
   {
      if (disposed)
         return;

      if (disposing) {
         handle.Dispose();
         // 다른 managed 리소스를 해제
      }

      // unmanaged 리소스를 해제

      disposed = true;
      // 부모 클래스의 Dispose(bool)를 호출
      base.Dispose(disposing);
   }
}


그리고 아래 책을 보면

C# 6.0 완벽 가이드 깊고 넓게 알려주는 레퍼런스 북 [ 전2권 ]
조셉 앨버허리, 벤 앨버허리 공저 / 류광 역 | 인사이트(insight) 

종료자/finalizer에 대한 언급이 있다.

객체에 종료자/finalizer가 있다면 인스턴스의 메모리 해제 시 종료자/finalizer가 호출된다. 
. 종료자/finalizer가 없다면 객체는 garbage collection 시 바로 삭제되지만
  종료자/finalizer가 있다면 별도의 대기열에 저장하여 관리하며 종료자/finalizer 호출 전까지 객체가 유지된다. 
. 종료자 때문에 메모리할당과 garbage collection이 느려진다. (종료자 객체들의 관리 및 처리)
. 종료자는 객체와 객체가 참조하는 객체의 수명을 필요 이상으로 늘린다.
. 종료자의 호출 순서를 예측할 수 없고 시점을 제어할 수 없다.
. 종료자안에서 코드 실행이 차단되면 다른 객체의 종료자가 호출되지 못한다.
. app이 제대로 종료되지 않는다면 종료자들이 호출되지 않을 수 있다.

그래서 더더욱 resource 해제를 위해서라면 위 Dispose pattern을 구현하는것이 맞겠다.


마지막으로 Garbage collection에 대해 설명하는 공식 문서

2020년 1월 22일 수요일

Galaxy Watch용 Tizen xamarin app에서 Entry에 의한 화면 깨짐 발생 시

Galaxy Watch app을 Xamarin.forms를 이용해서 C#으로 만들 수 있음.
https://samsung.github.io/Tizen.CircularUI/guide/Quickstart.html
기존에는 어려운 EFL 기반 app이나 web app을 만들어야 했는데 이제는 C#으로도 개발할 수 있음.

암튼 Watch 용 app을 하나 만들어 보는 중에
입력을 위한 Page가 필요하여 Entry를 추가하게 되었음.

하지만 페이지에서 Entry를 선택하면 Virtual Keyboard가 떠서
Page layout 밀려 올라가 이상하게 보이는 현상이 발생했다.

관련하여 다른 옵션이 없는지 확인 중 Tizen에서 제공하는 CircularUI가 있고
그 중에 PopupEntry를 제공하는 것을 확인하였음.

https://samsung.github.io/Tizen.CircularUI/guide/PopupEntry.html

Popup Entry는 일반적으로 Galaxy Watch app에서 사용되는
전체화면을 가리는 popup으로 입력창과 키보드가 떠서
기존 Page layout에는 영향을 주지 않고 입력할 수 있게 해준다.





참고로 안드로이드에서는 다음과 같이 처리하고 있음.
https://www.milestre.nl/blog/blogitem/milestre-blog/2019/01/08/to-avoid-soft-keyboard-overlap

//To avoid soft keyboard statusbar overlap            Xamarin.Forms.Application.Current.On().UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Resize);

2018년 3월 6일 화요일

C# Task 대충 정리

아래 내용은 아래 책들을 참고하였습니다. 자세한 내용은 아래 책을 참고해 주세요.














Thread

- 필요 자료 전달 가능
- 하지만 결과값을 받기 위해서는 main thread를 block 시켜야 함.
- 예외 발생 시 전파 어려움.

Task

- Thread 보다 높은 수준의 abstraction
- 하나의 concurrent operation을 대표함.
- 일반적으로 main thread가 아닌 thread pool thread에서 비동기적으로 실행 
- Continuation을 통해 Task를 조합할 수 있음.
- TaskComplectionSource와 함께 callback 방식으로 활용가능
- Thread 없이 I/O 한정 연산을 기다리는 것이 가능함.


https://msdn.microsoft.com/ko-kr/library/system.threading.tasks.task(v=vs.110).aspx

using System; using System.Threading; using System.Threading.Tasks; class Example { static void Main() { Action<object> action = (object obj) => { Console.WriteLine("Task={0}, obj={1}, Thread={2}", Task.CurrentId, obj, Thread.CurrentThread.ManagedThreadId); }; // Create a task but do not start it. Task t1 = new Task(action, "alpha"); // Construct a started task Task t2 = Task.Factory.StartNew(action, "beta"); // Block the main thread to demonstrate that t2 is executing t2.Wait(); // Launch t1 t1.Start(); Console.WriteLine("t1 has been launched. (Main Thread={0})", Thread.CurrentThread.ManagedThreadId); // Wait for the task to finish. t1.Wait(); // Construct a started task using Task.Run. String taskData = "delta"; Task t3 = Task.Run( () => {Console.WriteLine("Task={0}, obj={1}, Thread={2}", Task.CurrentId, taskData, Thread.CurrentThread.ManagedThreadId); }); // Wait for the task to finish. t3.Wait(); // Construct an unstarted task Task t4 = new Task(action, "gamma"); // Run it synchronously t4.RunSynchronously(); // Although the task was run synchronously, it is a good practice // to wait for it in the event exceptions were thrown by the task. t4.Wait(); } } // The example displays output like the following: // Task=1, obj=beta, Thread=3 // t1 has been launched. (Main Thread=1) // Task=2, obj=alpha, Thread=4 // Task=3, obj=delta, Thread=3 // Task=4, obj=gamma, Thread=1

- t1, t2, t3, t4 각각의 방법으로 Task를 만들어서 사용하는 예제
 : t1, t2, t4 는 action으로 정의된 delegate를 실행하는 task
 : t3는 lamda로 정의한 내용을 실행하는 task
- t4의 경우 동기적으로 실행되어 main thread(1번)에서 실행된다.
 : 일반적으로 main (application) thread에서 동기적으로 실행하면 화면이 멈추므로 잘 사용하지 않음.

using System; using System.Threading.Tasks; public class Example { public static async Task Main() { await Task.Run( () => { // Just loop. int ctr = 0; for (ctr = 0; ctr <= 1000000; ctr++) {} Console.WriteLine("Finished {0} loop iterations", ctr); } ); } } // The example displays the following output: // Finished 1000001 loop iterations


- Task.Run()을 사용하여 별도 설정 없이 task를 실행할 수 있음.
- Task.Wait()을 호출하여 task가  끝날때까지 기다릴 수 있고 호출 thread는 block 됨
- Task<TResult> generic type으로 반환값을 지정할 수 있음.

- Task.GetAwaiter()를 사용하여 대기자(awaiter)객체를 반환 받을 수 있고 Awaiter.OnCompleted() 지정하여 실행 후 처리 가능.
 . awaiter.GetResult();는 리턴값이 없을 경우 void임. void라도 불러야 할 필요가 있는게 Task 내에서 exception이 발생할 경우 GetResult()가 exception을 발생 시킴.
 . synchronous context를 전달할 필요가 없을 경우 task.ConfigureAwait(false).GetAwaiter()를 사용하면 전달하지 않음.


TaskCompletionSource

https://msdn.microsoft.com/ko-kr/library/dd449174(v=vs.110).aspx
https://michaelridland.com/xamarin/taskcompletionsource-xamarin-beautiful-async/

- 적절한 시기에 실행할 수 있는 Task를 생성할 수 있다.
- 좀 말이 어려운데 간단하게 말하면 Task를 한번 감싸는 객체이고 Task 실행을 적절하게 조절할 수 있음.
   결과를 .SetResult(), .SetCanceled(), .SetException()를 사용하여 caller에게 전달할 수 있음.


using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; class TCSDemo { // Demonstrated features: // TaskCompletionSource ctor() // TaskCompletionSource.SetResult() // TaskCompletionSource.SetException() // Task.Result // Expected results: // The attempt to get t1.Result blocks for ~1000ms until tcs1 gets signaled. 15 is printed out. // The attempt to get t2.Result blocks for ~1000ms until tcs2 gets signaled. An exception is printed out. static void Main() { TaskCompletionSource<int> tcs1 = new TaskCompletionSource<int>(); Task<int> t1 = tcs1.Task; // Start a background task that will complete tcs1.Task Task.Factory.StartNew(() => { Thread.Sleep(1000); tcs1.SetResult(15); }); // The attempt to get the result of t1 blocks the current thread until the completion source gets signaled. // It should be a wait of ~1000 ms. Stopwatch sw = Stopwatch.StartNew(); int result = t1.Result; sw.Stop(); Console.WriteLine("(ElapsedTime={0}): t1.Result={1} (expected 15) ", sw.ElapsedMilliseconds, result); // ------------------------------------------------------------------ // Alternatively, an exception can be manually set on a TaskCompletionSource.Task TaskCompletionSource<int> tcs2 = new TaskCompletionSource<int>(); Task<int> t2 = tcs2.Task; // Start a background Task that will complete tcs2.Task with an exception Task.Factory.StartNew(() => { Thread.Sleep(1000); tcs2.SetException(new InvalidOperationException("SIMULATED EXCEPTION")); }); // The attempt to get the result of t2 blocks the current thread until the completion source gets signaled with either a result or an exception. // In either case it should be a wait of ~1000 ms. sw = Stopwatch.StartNew(); try { result = t2.Result; Console.WriteLine("t2.Result succeeded. THIS WAS NOT EXPECTED."); } catch (AggregateException e) { Console.Write("(ElapsedTime={0}): ", sw.ElapsedMilliseconds); Console.WriteLine("The following exceptions have been thrown by t2.Result: (THIS WAS EXPECTED)"); for (int j = 0; j < e.InnerExceptions.Count; j++) { Console.WriteLine("\n-------------------------------------------------\n{0}", e.InnerExceptions[j].ToString()); } } } }

- tcs1은 t1 task의 결과로 1초 뒤 15값을 t1.Result로 전달함.
- tcs2와 같이 task에서 발생된 exception도 task의 결과로 전달 가능하다.


-------------------------------------------------------------------------------------------------------------------------------

멀티 쓰레드를 사용하는 근본적인 이유들
- Main application thread를 차단(block)하지 않기 위해
- I/O를 완료할 때까지 기다리지 않고 다른 작업을 함께 처리
- 동시 처리해야하는 작업들의 수행


Synchronous operation : 수행 후에 실행의 흐름을 caller에게 반환하는 연산
Asynchronous operation : 할 일을 다 마치기 전에 실행의 흐름을 caller에게 반환한다.


비동기 프로그래밍
- 오래 실행되는 함수는 asynchronous operation 형태로 작성
- 예전에는 동기적인 함수를 작성하고 이 함수를 thread에서 실행했던 것과는 달리 함수 내부에서 동시성을 시작함.
 . thread에 묶이지 않고 동시성을 구현할 수 있어 효율적임.
 . worker thread를 관리가 줄어들고 thread 안정성이 향상될 수 있음.
 . thread에서 분리되어 처리되던 기존 방식에 비해 세밀하게 동시성을 처리할 수 있음.


. Synchronous operation

int DoLongWork(int start)
        {
            // Do Long Work
            return start;
        }

        void DisplayResult()
        {
            for (int i = 0; i < 10; i++)
                Console.WriteLine($"Long work { i } started, result = { DoLongWork(i) }");

            Console.WriteLine("Done");
        }

(결과)
Long work 0 started, result = 0
Long work 1 started, result = 1
Long work 2 started, result = 2
Long work 3 started, result = 3
Long work 4 started, result = 4
Long work 5 started, result = 5
Long work 6 started, result = 6
Long work 7 started, result = 7
Long work 8 started, result = 8
Long work 9 started, result = 9


. Asynchronous operation

Task<int> DoLongWorkAsync(int start)
        {
            return Task.Run(() =>
            {
                // Do Long Work
                return start;
            });
        }

        void DisplayResult2()
        {
            for (int i = 0; i < 10; i++)
            {
                var awaiter = DoLongWorkAsync(i).GetAwaiter();
                awaiter.OnCompleted(() =>
                {
                    Console.WriteLine($"Long work { i } started, result = { DoLongWork(i) }");

                });
            }
            Console.WriteLine("Done");
        }


DoLongWorkAsync() 10개가 모두 병렬로 실행됨.

(결과)
Done
Long work 10 started, result = 10
Long work 10 started, result = 10
Long work 10 started, result = 10
Long work 10 started, result = 10
Long work 10 started, result = 10
Long work 10 started, result = 10
Long work 10 started, result = 10
Long work 10 started, result = 10
Long work 10 started, result = 10

Done이 표시되고 Long work들이 표시 될 수 있음.
그리고 캡쳐링된 i가 10인 상태에서 모두 실행되므로 i를 지역변수로 캡쳐링 하면 정상적으로 나옴.

void DisplayResult2()
        {
            for (int i = 0; i < 10; i++)
            {
                int captured = i;
                var awaiter = DoLongWorkAsync(i).GetAwaiter();
                awaiter.OnCompleted(() =>
                {
                    Console.WriteLine($"Long work { captured } started, result = { DoLongWork(captured) }");

(결과)
Long work 1 started, result = 1
Long work 0 started, result = 0
Done
Long work 3 started, result = 3
Long work 2 started, result = 2
Long work 4 started, result = 4
Long work 5 started, result = 5
Long work 7 started, result = 7
Long work 6 started, result = 6
Long work 8 started, result = 8
Long work 9 started, result = 9


연속적으로 진행하고자 한다면

public void DisplayResult3()
        {
            DisplayResultFrom(0);
        }

        void DisplayResultFrom(int i)
        {
            var awaiter = DoLongWorkAsync(i).GetAwaiter();
            awaiter.OnCompleted(() =>
            {
                Console.WriteLine($"Long work { i } started, result = { DoLongWork(i) }");

                if (i++ < 10)
                    DisplayResultFrom(i);
                else
                    Console.WriteLine("Done");
            });
        }

DisplayResult 자체를 비동기화하여
DisplayResult에서 작업 완료 시 신호를 보내는 작업 객체를 돌려주게 하려면

Task DisplayResultAsync4()
        {
            var display = new LongWorkDisplay();
            display.DisplayResultFrom(0);
            return display.Task;
        }

        public class LongWorkDisplay
        {
            TaskCompletionSource<object> _tcs = new TaskCompletionSource<object>();
            public Task Task { get => _tcs.Task; }

            public void DisplayResultFrom(int i)
            {
                var awaiter = DoLongWorkAsync(i).GetAwaiter();
                awaiter.OnCompleted(() =>
                {
                    Console.WriteLine($"Long work { i } started, result = { awaiter.GetResult() }");

                    if (i++ < 10)
                        DisplayResultFrom(i);
                    else
                    {
                        Console.WriteLine("Done");
                        _tcs.SetResult(null);
                    }
                });
            }
        }


하지만 C# 비동기 함수 기능을 사용하면 다음과 같음.

        public async Task DisplayResultAsync5()
        {
            for (int i = 0; i < 10; i++)
                Console.WriteLine($"Long work { i } started, result = { await DoLongWorkAsync(i) }");

            Console.WriteLine("Done");
        }


예외 전달
async void ButtonClick(object sender, EventArgs args)
{
    await Task.Delay(1000);
    throw new Exception("exception");
}



이 때 ButtonClick 실행은 await문을 넘어간 후 메세지 루프로 돌아오므로
얼마 지나서 발생한 예외는 메세지 루프의 catch 블록에 잡히지 않는다고 함.

하지만 synchronous context가 있는 경우 AsyncvoidMethodBuilder는
void async 함수에서 미처리 예외를 잡아서동기화 문맥에 전달한다.

비동기 함수에서 바로 처리가 가능한 경우 result를 받을 수 있다면
다음과 같이 처리할 수 있다고 함.

var awaiter = GetWebPageAsync().GetAwaiter();
if (awaiter.IsCompleted)
    Console.WriteLine(awaiter.GetResult());
else
    awaiter.OnCompleted(() => ...);


-------------------------------------------------------------------------------------------------------------------------------

async 및 await를 사용한 비동기 프로그래밍  

비동기 프로그래밍의 필요성과 예제 시나리오를 지원하기 위해 동기적, 비동기적 프로그램을 예시를 들어 설명하고 있어 개념이 이해 안된다면 한번 읽어보세요.

2018년 2월 4일 일요일

C# Optional parameter & default value.



이전 포스팅에서 나온 얘기인데 좀 더 봐야할 필요가 있어 정리함.

C# API를 만드는 입장에서는 Optional parameter를 사용할 때 주의 해야 한다고 함.


1. "Default value의 변경" 과 관련해서
"C# 7.0 in a Nutshell"을 보면

Optional parameters의 default value는 빌드시 default value를 포함시켜 빌드 한다고 함. 예를 들어

public Car(string name, bool isSedan = false가 있고
var myCar = new Car("AAA"); 일 경우 
빌드 시 isSedan 값이 false로 지정되어 빌드 된다고 함.

나중에 Car의 signature의 isSedan optional parameter가 true로 바뀌어도 
기존에 컴파일 된 API client에서는 어차피 false값을 사용하고 있으므로
API 변경과는 상관이 없는 것임.

결국 변경된 API의 optional parameter의 defulat value를 적용하기 위해 모든 API client들이 재컴파일 되어야 함.


2. "Optional parameter가 있는 API가 Overload 될 때" 특이한점이 있는데

위 링크를 참조하면 Overload 된 methods들을 선택할 때 
아래와 같은 느낌?으로 호출할 대상을 선택한다고 함.
Optional parameter가 없는 함수 중 동일한 parameter 개수의 함수를 최우선으로 선택.
> 없다면 optional parameter를 가진 함수 중에서 일치하는 함수를 선택한다.
> 하지만, API들의 parameter 중 default value를 가지고 있지 않는 개수가 동일한 경우 같은 함수로 취급되어 호출 할 대상을 찾지 못해 에러가 발생한다고 한다.

public void Drive(string destination, int speed = 30) 이 있고
API client에서 Drive("Seoul")로 사용하고 있는 상황에서
public void Drive(string destination, int speed = 30, bool byBus = false) 이 추가되며 API가 업데이트 된다면

컴파일 되지 않은 기존 API client에서 호출하는 Drive("Seoul") 실행 시 에러가 발생한다.
사실상 두 Drive API가 모두 Optional parameter를 적용하면 같은 함수나 마찬가지이므로 runtime 시 어떤 API를 호출해야 할지 몰라 에러가 발생하는 것임.

public void Drive(string destination, int speed = 30)
public void Drive(string destination, int speed = 30, bool byBus = false) 

하지만 아래와 같이 optional parameter가 없는 함수가 있다면 우선적으로 해당 API를 호출하게 되므로 에러는 발생하지 않는다.

public void Drive(string destination)
public void Drive(string destination, int speed = 30)
public void Drive(string destination, int speed = 30bool byBus = false) 

2018년 1월 18일 목요일

C# API Design #3, Implementing

새해 강의를 찾다가 아래 강의를 듣게 됨.
https://www.udemy.com/api-in-csharp/learn/v4/overview

API design에 대한 책은 많지만 C#에 대해서 특화된 내용이 없어서 좀 아쉽던 차에
이런 강의를 보게 됨. 하지만 강의 내용은 평범하다.

보다가 기억에 남는 것을 간단히 정리함.
하지만 강의를 들어야 모든 내용을 이해할 수 있으니 필요하면 구입해서 듣는 것도 방법임.


[Designing and Implementing Types and their Members]


. struct, class
struct와 class의 차이에 대해서 설명을 하는데
struct는 value type, class는 reference type이라는 것,
struct를 property, readonly, 일반 변수로 사용할 때 조심해야 할 것은
property의 getter나 readonly는 변수를 복사(value type)해서 전달하므로 변수를 변경해도 실제 변수에 영향을 미치지는 않는다고함.
그래서 struct를 사용하려는 객체는 불변값을 가지는

컴파일러는 property(get)에 대해 대충 아래처럼 코드를 생성함

private Mutable ☆ ☆ ☆; // private backing field
              // 컴파일러에서 생성, 코드에서 접근 불가

public Mutable get_PropertyMutable()   // get; 컴파일러 생성
{
return ☆ ☆ ☆;
}

get_PropertyMutable은 풀어서 보면 대략 이런 느낌?

private Mutable ☆ ☆ ☆; 
public Mutable get_PropertyMutable()
{
Mutable temp = ☆ ☆ ☆;
return temp;
}


. Abstract class는 default 구현을 두고 필요한 구현만 상속 class에 맡김
Interface는 상속 class의 형태(prototype)를 강제하고 모든 구현을 맡김
 : Interface는 API client 입장에서 보면 API 사용 시 유연하고
API developer 입장에서 보면 API 구현 시 융통성이 없음.
 : Abstract class는 API client 입장에서 보면 API 사용 시 융통성이 없고
API developer 입장에서 보면 API 구현이 유연함.


. Abstract class에는 constructor를 protect로 만들어라. 실제로 Instance 생성이 불가한 abstract class에 public constructor는 말이 안됨.
https://docs.microsoft.com/ko-kr/dotnet/standard/design-guidelines/abstract-class


. Constructor에서 virtual method를 호출하지 마라.
실제 상속된 class의 virtual method에서 내부의 변수를 참조할 경우 부모의 constructor가 수행되는 중에는 자식의 constructor가 호출되지 않는 상태라 변수가 초기화가 안되어 있을 수 있다.


. Optional parameter 사용하는 API를 만들 시 아래 article을 꼭 참조하라.
: https://haacked.com/archive/2010/08/10/versioning-issues-with-optional-arguments.aspx
http://codebetter.com/2011/01/11/c-in-depth-optional-parameters-and-named-arguments-2/

. Optional parameters는 빌드 된 IL이 default value를 포함하게 함
 : library에서 default value를 사용하는 API 사용 시 library와 API client 코드가 모두 재 빌드 되어야 default value가 변경 됨. ㅜㅜ

. Overload 된 methods들을 선택할 때
optional parameter가 없는 함수 중 동일한 parameter 개수의 함수를 선택.
없다면 optional parameter를 가진 함수 중에서 일치하는 함수를 선택한다.
이 때 API들의 parameter 중 default value를 가지고 있지 않는 개수가 동일한 경우 같은 함수로 취급된다.


.Constructor의 경우 간략한 작업을 하도록 하고
 생성 시 parameter 부족이나 초기화에 문제가 발생한다면 exception을 발생 시키는 것도 방법임. 하지만 static constructors에서 exception을 발생시키면 노답.


. Finalize를 구현하면
Finalize될 object들을 관리하는 overhead 발생하고
Finalize 시점을 예측할 수 없어 과연 resource 해제여부도 불투명함.
그래서 unmanaged resource에 대해
IDisposable을 사용하고 IDisposable 사용 시 class 상속을 허용하지 않는 것이 좋지 않을까?
https://msdn.microsoft.com/ko-kr/library/system.idisposable(v=vs.110).aspx