2015년 3월 12일 목요일

뇌자극 C# 5.0 - 19 강

-쓰레드와 태스크-

19.1 프로세스와 스레드


프로세스 = 실행 파일이 실행되어 메모리에 적재된 인스턴스
스레드 = OS기 CPU 시간을 할당하는 기본 단위

프로세스가 밧줄이라면 스레드는 밧줄을 이루는 실이라고 할 수 있다.
프로세스는 최소한 한 개의 스레드를 가지고 있다

멀티 스레드의 장점과 단점


장점
(1) 사용자 대화형 프로그램에서 응답성을 높일 수 있다.
(2) 멀티 프로세스에 비해 멀티 스레드 방식이 자원 공유가 쉽다
(3) 이미 프로세스에 할당된 메모리와 자원을 그대로 사용해서 경제성이 좋다.

단점
(1) 구현하기가 매우 까다롭다.
(2) 테스트도 하기 힘들다
(3) 디버깅도 힘들다
(4) 자식 스레드 중 하나에 문제가 생기면 전체 프로세스가 문제가 생긴다.
(5) 너무 많이 사용하면 오히려 성능이 저하된다.

19.1.1 스레드 시작하기


(1)Thread 의 인스턴스를 생성한다. 생성자의 매개 변수로 스레드가 실행할 메소드를 매개 변수로 넘긴다

(2) Thread.start() 메소드를 호출하여 스레드를 시작한다

(3) Thread.Join() 메소드를 호출하여 스레드가 끝날 때까지 기다린다.

19.1.2 스레드 임의로 종료시키기


Thread 객체의 Abort() 메소드를 호출해주면 된다.
하지만 Abort() 메소드를 호출한다고 해서 동작하던 스레드가 즉시 종료된다는 보장이 없다.
예외가 있으면 예외처리까지 다 하고 나서야 종료가 되기 때문에 시간이 얼마나 걸릴지 모른다.
책에서는 Thread.Abort()를 사용하지 말라고 권고하고 있다(그럼 알려주질 말지 -_-)

19.1.3 스레드의 일생과 상태 변화


Unstarted - 스레드 객체를 생성한뒤 Start() 메소드가 호출되기 전 상태
Runing - 스레드가 시작하여 동작 중인 상태
Suspended - 스레드의 일시 중단 상태
WaitSleepJoin - 스레드가 블록(Block)된 상태
Aborted - 스레드가 취소된 상태
Stopped - 중지된 스레드의 상태
Background - 스레드가 백그라운드로 동작하고 있는 상태

Thread 객체의 ThreadState 필드를 통해 상태를 확인할 때는 반드시 비트 연산을 이용해야 한다.
ThreadState 열거형이 여러 상태를 동시에 나타낼 수 있도록 만들어져 있기 때문

19.1.4 스레드를 임의로 멈추는 또 하나의 방법 : 인터럽트


스레드를 강제로 종료시켜야 하는 경우에는
Thread.Interrupt()메소드를 사용하는 것이 안전하다

이유는 스레드의 상태가 WaitsleepJoin 상태면 바로 중지 시키지만
이 상태가 아닌 경우에는 스레드를 지켜보다가
WaitSleepJoin 상태가 되면 그때야 중단 시킨다

이런 특징 때문에 프로그래머는 최소한 코드가 "절대로 중단 되면 안 되는"
작업을 하고 있을 때 중단되지 않는 다는 보장을 받을 수 있다.

19.1.5 스레드 간의 동기화


동기화란? 스레드들이 순서를 갖춰 자원을 사용하게 하는 것을 "동기화"라고 한다.

스레드 동기화에서 가장 중요한 점은
"자원을 한번에 하나의 스레드가 사용하도록 보장"하는 것이다.

Lock 키워드로 동기화 하기 
예제)
class Counter
{
   public int count = 0;
   private readonly object thisLock = new object();

   public void Increase()
   {
      lock( thisLock )
      {
        count = count + 1;
      }
   }
}

//...
MyClass obj = new MyClass();
Thread t1 = new Thread(new ThreadStart(obj.DoSomething);
Thread t2 = new Thread(new ThreadStart(obj.DoSomething);
Thread t3 = new Thread(new ThreadStart(obj.DoSomething);

t1.Start();
t2.Start();
t3.Start();

t1.Join();
t2.Join();
t3.Join();

lock 키워드의 매개 변수로 사용하는 객체는 참조형이면 어느 것이든 쓸 수 있지만
외부 코드에서도 접근할 수 있는 다음 세가지는 절대 사용해선 안된다.

this : 클래스의 인스턴스는 클래스의 내부뿐만 아니라 외부에서 자주 사용된다.
Type 형식 : typeof 연산자나 object 클래스로부터 물려받은 GetType() 메소드는 Type 형식의 인스턴스를 반환한다.
string 형식 : 외부에서 접근 가능하기에 절대로 써선 안된다.

Monitor 클래스로 동기화 하기

Monitor 클래스는 스레드 동기화에 사용하는 몇 가지 정적 메소드를 제공한다

Monitor.Enter()/Monitor.Exit() 를 사용한 예제

public void Increase()
{
   int loopCount = 1000;
   while(loopCount--> 0)
   {
      Monitor.Enter(thisLock);
      try
      {
         count++;
      }
      finally
      {
         Monitor.Exit(thisLock);
      }
   }
}

Monitor.Wait()와 Monitor.Pulse()로 하는 저수준 동기화 예제

이 두 메소드는 반드시 look 블록 안에서 호출해야 한다.

(1) 클래스 안에 다음과 같이 동기화 객체 필드를 선언한다.
readonly object thisLock = new object();

(2) 스레드를 WaitSleepJoin 상태로 바꿔 블록시킬 조건을 결정할 필드를 선언
bool lockedCount = false;

(3) 스레드를 블록시키고 싶은 곳에서는 lock 블록 안에서 2번에서 선언한 필드를 검사하여 Monitor.Wait()를 호출한다.

lock(thisLock)
{
   while(lockedCount == true)
       Monitor.Wait(thisLock);
   //....
}

(4) 3번 과정에서 선언한 코드는 count가 0보다 크거나 lockedCount 가 true면 해당 스레드는 블록된다. 이렇게 블록되어 있던 스레드를 깨워나면 작업을 해야 한다.
가장 먼저 2번 과정에서 선언한 lockedCount의 값을 true로 변경한다.
이렇게 해두면 다른 스레드가 이 코드에 접근할 때 3번 과정에서 선언해둔 블로킹 코드에 걸려 같은 코드를 실행할 수 없게 된다.
작업을 마치면 lockedCount 의 값을 다시 false로 바꾼 뒤 Monitor.Pulse()를 호출한다.
그럼 Waiting Queue에 대기하고 있던 다른 스레드가 깨어나서 false로 바뀐 lockedCount를 보고 작업을 수행한다.

lock(thisLock)
{
   while(lockedCount == true)
      Monitor.Wait(thisLock);
   lockedCount = true;
   count++;
   lockedCount = false;
 
   Monitor.Pulse(thisLock);
}

19.2 Task와 Task<TResult>, 그리고 Parallel


비동기 코드를 작성할 수 있도록 하는 도구와 장치

19.2.1 System.Threading.Tasks.Task 클래스


동기 코드 = 메소드를 호출한 뒤에 실행이 완전히 종료되어야만 다음 메소드를 호출 가능

비동기 코드 = 메소드를 호출한 뒤에 메소드의 종료를 기다리지 않고 바로 다음 코드를 실행하는 것.

Task 클래스는 인스턴스를 생성할 때 Action 델리게이트를 넘겨받는다.
반환형을 갖지 않는 메소드와 익명 메소드, 무명 함수 등을 넘겨받는다.

(예제)
Action someAction= () =>
{
   Thread.Sleep(1000);
   Console.WriteLine("Printed asynchronously.");
};

Task myTask = new Task(someAction);
myTask.Start(); //생성자에서 넘겨받은 무명 함수를 비동기로 호출한다.

Console.WriteLine("Printed synchronously.");

myTask.Wait(); //myTask 비동기 호출이 완료될 때까지 기다린다.

19.2.2 코드의 비동기 실행 결과를 주는 Task<TResult> 클래스


(예제)
var myTask = Task<List<int>>.Run(
   ()=>
   {
      Tread.Sleep(1000);
      List<int> list = new List<int>();
      list.Add(3);
      list.Add(4);
      list.Add(5);
      return list;
   }
);

var myList = new List<int>();
myList.Add(0);
myList.Add(1);
myList.Add(2);

myTask.Wait();
myList.AddRange(myTask.Result.ToArray());

19.2.3 손쉬운 병렬 처리를 가능케 하는 Parallel 클래스


void SomeMethod(int i)
{
   Console.WriteLins(i);
}
//...
Parallel.For(0, 100, SomeMethod);

위 코드는 SomeMethod()를 병렬로 호출하면서
0~100 사이의 정수를 메소드의 매개변수로 넘긴다.

SomeMethod() 메소드를 병렬로 호출할 때
몇 개의 스레드를 사용할지는 Parallel 클래스가 내부적으로 판단하여 최적화 한다.

19.3 async 한정자와 await 연산자로 만드는 비동기 코드


async 한정자는 메소드, 이벤트 처리기, 테스크, 람다식 등을 수식함으로서
C# 컴파일러가 이들을 호출하는 코드를 만날 때
호출 결과를 기다리지 않고 바로 다음 코드로 이동하도록 실행 코드를 생성하게 한다.

(1)async로 한정하 void 형식 메소드는 await 연산자가 없어도 비동기로 실행된다.
(2)async로 한정한 Task 또는 Task<TResult>를 반환하는 메소드/태스크/람다식은
   await 연산자를 만나는 곳에 호출자에게 제어를 돌려주며,
   await 연산자가 없는 경우 동기로 실행된다.

2015년 3월 10일 화요일

뇌자극 C# 5.0 - 18 강

-파일 다루기-

18.1 파일 정보와 디렉토리 정보 다루기


파일 = 컴퓨터 저장 매체에 기록되는 데이터의 묶음
디렉토리 = 파일이 위치하는 주소

파일과 디렉토리 정보를 손쉽게 다루도록
System.IO 네임스페이스를 사용하고 다음과 같은 클래스를 제공한다

File = 파일의 생성, 복사, 삭제, 이동, 조회를 처리하는 정적 메소드를 제공한다
FileInfo = File 클래스와 하는 일은 동일하지만 정적 메소드 대신 인스턴스 메소드를 제공
Directory = 디렉토리의 생성,삭제,이동,조회를 처리하는 정적 메소드를 제공한다
DirectoryInfo = Directory 클래스와 하는 일은 동일하지만 인스턴스 메소드를 제공한다.

-File 클래스/ FileInfo 클래스 사용예제-

-File 클래스-

생성 - FileStream fs = File.Create("a.dat");
복사 - File.Copy("a.dat","b.dat");
삭제 - File.Delete("a.dat");
이동 - File.Move("a.dat", "b.dat");
존재 여부 확인 - if(File.Exists("a.dat"))
속성 조회 - Console.WriteLine(File.GetAttributes("a.dat"));

-FileInfo 클래스-

생성 - FlieInfo file = new FileInfo("a.dat");
        FileStream fs = File.Create();

복사 - FileInfo src = new FileInfo("a.dat");
        FileInfo dst = src.CopyTo("b.dat");

삭제 - FileInfo file = new FileInfo("a.dat");
        file.Delete();

이동 - FileInfo file = new FileInfo("a.dat");
        file.MoveTo("b.dat");

존재 여부 확인 - FileInfo file = new FileInfo("a.dat");
                    if(file.Exists())

속성 조회 - FileInfo file = new FileInfo("a.dat");
              Console.WriteLine(file.Attributes);

-Directory/DirectoryInfo 클래스 사용예제-

-Directory 클래스-

생성 - Directory dir = Directory.CreateDirectory("a");
삭제 - Directory.Delete("a");
이동 - Directory.Move("a","b");
존재 여부 확인 - if(Directory.Exists("a.dat");
속성 조회 - Console.WriteLine(Directory.GetAttributes("a");
하위 디렉토리 조회 -string[] dirs = Directory.GetDirectories("a");
하위 파일 조회 - string[] files = Directory.GetFiles("a");

-DirectoryInfo 클래스-

생성 - DirectoryInfo dir = new DirectoryInfo("a");
        dir.Create();
삭제 - DirectoryInfo dir = new DirectoryInfo("a");
        dir.Delete();
이동 - DirectoryInfo dir = new DirectoryInfo("a");
        dir.MoveTo("b");
존재 여부 확인 - Directory dir = new DirectoryInfo("a");
                   if(dir.Exists)
속성 조회 - DirectoryInfo dir  = new DirectoryInfo("a");
             Console.WriteLine(dir.Attributes);
하위 디렉토리 조회 - DirectoryInfo dir = new DirectoryInfo("a");
                        DirectoryInfo[] dirs = dir.GetDirectories();
하위 파일 조회 - DirectoryInfo dir = new DirectoryInfo("a");
                   FileInfo[] files = dir.GetFiles();

18.2 파일을 읽고 쓰기 위해 알아야 할 것들


먼저 스트림의 개념을 알아야 한다.
스트림은 데이터가 흐르는 통로를 뜻한다.
메미로에서 하드디스크로 데이터를 옮길 때(파일 쓰기)는
먼저 스트림을 만들어 둘 사이를 연결하고
메모리에 있는 데이터를 바이트 단위로 하드디스크로 옮겨 넣는다.

스트림은 데이터의 흐름이기 때문에 스트림을 이용하여
파일을 다룰 때는 처음부터 끝까지
순서대로 읽고 쓰는 것이 보통이다.(이것을 순차 접근 방식이라고 한다).

하지만 하드디스크는 암과 헤드를 움직어
디스크의 어떤 위치에 기록된 데이터라도 즉시, 찾아갈 수 있다.
이렇게 임의의 주소에 있는 데이터에 접근하는 것을 임의 접근 방식이라고 한다.

스트림을 쓰기 위해서는 System.IO.Stream 클래스를 사용해야 한다.
스트림 클래스는 그 자체로 입력/출력 스트림 역할을 모두 할 수 있고,
순차/임의 접근 방식 모두를 지원한다.

단, 스트림 클래스는 추상 클래스이기 때문에 인스턴스를 직접 만들어 사용할 수는 없고,
이 클래스로부터 파생된 클래스를 이용해야 한다.

파일을 쓰는데는 다음과 같은 과정이 필요하다

1) 파일 스트림 생성
2) 값을 byte 배열로 변환
3) 변환한 byte 배열을 파일 스트림을 통해 파일에 기록
4) 파일 스트림 닫기

파일 읽는 과정도 비슷하다

1) 파일 스트림 생성
2) 값의 길이 만큼 데이터를 읽어 변수에 저장
3) BitConverter를 이용하여 변수에 담겨 있는 값을 Long 형으로 변환
4) 파일 스트림 닫기

18.3 이진 데이터 처리를 위한 BinaryWriter/BinaryReader


FileStream 클래스는 파일 처리를 위한 모든 것을 가지고는 있지만 사용하기가 불편하다.
데이터 저장할 때는 반드시 byte 형식 또는 byte의 배열 형식으로
변환해야 한다는 문제가 있다.
파일로부터 데이터를 읽을 때도 마찬가지다.
이런 불편함을 해소하기 위해 BinaryWriter/BinaryReader 가 만들어졌다.

(예제 - BinaryWriter)

BinaryWriter bw = new BinaryWriter( new FileStream("a.dat", FileMode.Create) );

bw.Write(32);
bw.Write("Good Morning");
bw.Write(3.14);

bw.Close();

1)BinaryWriter 의 생성자를 호출 하면서 FileStream 의 인스턴스를 매개 변수로 넘긴다.
2)인스턴스에서 Write() 메소드를 호출하고 데이터를 넣는다.
3)스트림을 닫아준다.

(예제 - BinaryReader)

BinaryReader br = new BinaryReader( new FileStream("a.dat", FileMode.Open) );

int a = br.ReadInt32();
string b = br.ReadString();
double c = br.ReadDoble();
br.Close();

1) BinaryReader 의 생성자를 호출하면서 FileStream 의 인스턴스를 매개 변수로 넘긴다.
2) BinaryReader는 읽을 데이터의 형식별로 Readxxx() 메소드(xxx는 읽을 데이터 형식의 이름)를 제공한다.
3) 스트림을 닫는다.

18.4 텍스트 파일 처리를 위한 StreamWriter/StreamReader


텍스트 파일 처리 역시 비슷하다.

(예제 - StreamWriter)

StreamWriter sw = new StreamWriter( new FileStream("a.dat", FileMode.Create));

sw.Write(32);
sw.WriteLine("Good Morning!");
sw.WriteLine(3.14);

sw.Close();

(예제 - StreamReader)

StreamReader sr = new StreamReader( new FileStream("a.dat", FileMode.Open) );

while(sr.EndOfStream == false)
{
    Console.WriteLine(sr.ReadLine());
}

sr.Close();

18.5 객체 직렬화하기


BinaryWriter/Reader 와 StreamWriter/Reader 은
기본 데이터 형식을 스트림에 쓰고 읽을 수는 있지만

프로그래머가 직접 정의한 클래스나 구조체 같은
복합 데이터 형식은 지원하지 않는다.

복합 데이터 형식을 쉽게 스트림에 읽기/쓰기를 할 수 있게 하는 것이 직렬화 이다.

직렬화란?
객체의 상태를 메모리나 저장 장치에 저장 가능한 0과 1의 순서로 바꾸는 것이다.
C#에서는 객체를 직렬화 하는 방법이 쉽다.
그저 [Serializable]를 클래스 선언부 앞에 붙여주면,
그 클래스는 메모리나 저장장치에 저장이 가능한 형식이 된다.

(예제 - 직렬화)

[Serializable]
class MyClass
{
   //...
}

Stream ws = new FileStream("a.dat", FileMode.Create);

//BinaryFormatter는 객체를 직렬화 하거나 역직렬화 하는 클래스
BinaryFormatter serializer = new BinaryFormatter();

MyClass obj = new MyClass();

serializer.Serialize(ws, obj); //직렬화
ws.Close();

(예제 - 역직렬화)

Stream rs = new FileStream("a.dat", FileMode.Open);
BinaryFormatter deserializer = new BinaryFormatter();

MyClass obj = (MyClass)deserializer.Deserialize(rs);  //역직렬화
rs.Close();

만약 직렬화 하고 싶지 않은 필드가 있다면
그 필드에만 NonSerialzed 애트리뷰트로 수식하면 된다.
이렇게 하면 필드의 상태는 직렬화 할 때도 제외되고, 역직렬화 할 때도 복원되지 않는다.

(예제 - NonSerialzed)

[Serializable]
class MyClass
{
   public int myField1;
   public int myField2;
 
   [NonSerialized]
   pulblic int myField3; //이 필드만 직렬화 되지 않는다.

   public int myField4; //이 필드는 직렬화 된다
}


뇌자극 C# 5.0 - 17강

-dynamic 형식-


17.1 dynamic 형식 소개


dynamic 형식도 int, string처럼 또 하나의 데이터 형식이다.
형식 검사가 컴파일할 때 이루어지는 다른 형식과는 달리
실행할 때 이루어진다는 점이 dynamic 형식의 특징이다.

class MyClass
{
   public void FuncAAA()
   {   어쩌구 저쩌구    }
}

class MainApp
{
   static void Main(string[] args)
   {
      MyClass obj = new MyClass();
      obj.FuncAAA();
      obj.FuncBBB();    // 이부분이 에러가 남
   }
}

위의 코드는 컴파일 자체가 되질 않는다.
없는 메소드를 선언 했으니 당연히 에러가 난다. 하지만 dynamic 형식을 쓰면...

class MainApp
{
   static void Main(string[] args)
   {
      dynamic obj = new MyClass();
      obj.FuncAAA();
      obj.FuncBBB(); //dynamic 형식으로 선언된 obj는 일단 컴파일 형식 검사는 피함
   }
}

형식 검사를 컴파일 할 때에서 실행할 때로 미루겠다는 것이지, 하지 않는 것은 아니다.

17.2 COM 과 .NET 사이의 상호 운용성을 위한 dynamin 형식


COM 이란?

마이크로소프트의 소프트웨어 컴포넌트 규격을 말한다.

하지만 닷넷이 나오면서 COM과 C#이 서로 문제가 생겼다.

(1) COM은 메소드가 결과를 반환할 때 실제 형식이 아닌 object 형식으로 반환한다.
(2) COM은 오버로딩을 지원하지 않는다. 대신 메소드와 선택적 매개 변수와 기본값 매개 변수를 지원한다.

이 두가지를 C# 4.0에서 dynamic 형식의 도입을 통해 (1)을 해결하고,
메소드의 선택적 매개 변수와 기본 값 매개 변수 도입을 통해 (2)문제를 해결했다.

뇌자극 C# 5.0 - 16강

-리플렉션과 애트리뷰트-

16.1 리플렉션

객체를 엑스레이처럼 객체의 형식 정보를 들여다보는 기능이다.
이 기능을 이용하면 프로그램 실행 중에
객체의 형식 이름, 프로퍼티 목록, 필드, 이벤트 목록까지 모두 열어 볼 수 있다.

16.1.1 Object.GetType() 메소드와 Type 클래스


Object는 모든 데이터 형식의 조상이다.
모든 데이터 형식은 Object 형식이 갖고 있는 다음 메소드를 물려받아 갖고 있다는 뜻
Equals(), GetHashCode(), GetType(), ReferenceEquals(), ToString()

위 다섯개 메소드 중 GetType()이 객체의 형식 정보를 반환하는 기능을 한다.
GetType() 메소드는 Type형식의 결과를 반환한다.
Type형식은 닷넷에서 사용되는 데이터 형식의 모든 정보를 담고 있다.

메소드                        반환형식             설명
GetConstructors()           ConstructorInfo()   모든 생성자 목록을 반환한다
GetEvents()                  EventInfo()          이벤트 목록을 반환한다
GetFields()                   FieldInfo()           필드 목록을 반환한다
GetGenericArguments()     Type()               매개 변수 목록을 반환한다
GetInterfaces()              Type()               인터페이스 목록을 반환한다
GetMembers()               MemberInfo()       멤버 목록을 반환한다
GetMethods()                MethodInfo()       메소드 목록을 반환한다
GetNestedTypes()           Type()               내장 형식 목록을 반환한다
GetProperties()              PropertyInfo()       프로퍼티 목록을 반환한다

16.1.2 리플렉션을 이용해서 객체 생성하고 이용하기


리플렉션을 이용해서 동적으로 인스턴스를 만들기 위해서는
System.Activator 클래스의 도움이 필요하다

인스턴스를 만들고자 하는 형식의 Type 객체를 매개 변수로 넘기면,
Activator.CreateInstance() 메소드는 입력받은 형식의 인스턴스를 생성하여 반환한다.

object a = Activator.CreateInstance(typeof(int));

일반화를 지원하는 버전의 CreateInstance()메소드도 있다.

List<int> list = Activator.CreateInstance<List<int>>();

16.1.3 형식 내보내기


동적으로 새로운 형식을 만드는 작업은
System.Reflection.Emit 네임스페이스에 있는 클래스를 통해서 이루어진다.

이 클래스를 쓰는 요령은 다음 순서와 같다

1)AssemblyBuilder 을 이용해서 동적으로 어셈블리를 만든다.

2)ModuleBuilder 를 이용해서 1)에서 생성한 어셈블리 안에 모듈을 만들어 넣는다.

3)TypeBuilder 를 2)에서 생성한 모듈 안에 클래스(형식)을 만들어 넣는다.

4) 3)을 생성한 클래스 안에 메소드나 프로퍼티를 만들어 넣는다.

5) 4)에서 생성한 것이 메소드라면,
   ILGenerator를 이용해서 메소드 안에 CPU가 실행할 IL 명령들을 넣는다.

16.2 애트리뷰트

애트리뷰트(Attribute)는 코드에 대한 부가 정보를 기록하고 읽을 수 있는 기능이다.
애프트뷰트가 주석과 다른 점은 주석은 사람이 읽고 쓰는 정보라면,
애트리뷰트는 사람이 작성하고 컴퓨터가 읽는다는 것이다.

애트리뷰트를 이용해서 클래스나 구조체, 메솓, 프로퍼티 등에 데이터를 기록해두면 
이 정보를 C# 컴파일러나 C#으로 작성된 프로그램이 이 정보를 읽어서 사용할 수 있다.

애트리뷰트나 리플렉션을 통해 얻는 정보들을 메타데이터라고 한다.

16.2.1 애트리뷰트 사용하기


[애트리뷰트_이름( 애트리뷰트_매개_변수 )]
public void MyMethod()
{
    //....
}

예제)
class MyClass
{
   [Obsolete("OldMethod는 폐기 되었습니다. NewMethod()를 이용하세요.")]
   public void OldMethod()
   {
      Console.WriteLine("옛날 버전");
   }
   public void NewMethod()
   {
      Console.WriteLine("최신 버전");
   }
}

예제를 OldMethod()를 사용하고 컴파일 하면 경고 메세지가 IDE에 나타나게 된다.
자연스럽게 최신버전을 쓰게 프로그래머에게 알려줄 수 있다.

16.2.2 호출자 정보 애트리뷰트


C#5.0 버전부터 호출자 정보라는 애트리뷰트가 도입되었다.
호출자 정보는 메소드의 매개 변수에 사용되며, 
메소드의 호출자 이름, 호출자 메소드가 정의 되어 있는 소스 파일 경로, 
소스 파일 내의 행 번호를 알 수 있다.

응용프로그램의 이벤트를 로그 파일이나 화면에 출력하면 
그 이벤트가 어떤 코드에서 일어 났는지 알 수 있다.

3가지 호출자 정보 애트리뷰트를 설명한다.

CallerMemberNameAttribute : 현재 메소드를 호출한 메소드 또는 프로퍼티의 이름을 나타냄

CallerFilePathAttribute : 현재 메소드가 호출된 소스 파일 경로를 나타낸다. 이때 경로는 소소 코드를 컴파일 할 때의 전체 경로를 나타낸다.

CallerLineNumberAttribute : 현재 메소드가 호출된 소스 파일 내의 행 번호를 나타낸다.


2015년 3월 9일 월요일

뇌자극 C# 5.0 - 15강

-LINQ-


15.1 데이터!!데이터!!데이터!!


LINQ는 C# 언어에 통합된 데이터 질의 기능이다.
질의란 데이터에 대해 물어본다는 말이다.
기본적인 질문은 다음과 같다

From : 어떤 데이터 집합에서 찾을 것인가?
Where : 어떤 값의 데이터를 찾을 것인가?
Select : 어떤 항목을 추출할 것인가?

List<Profile> profiles = new List<Profile>();
foreach (Profile profile in arrProfile)
{
    if(profile.Height < 175)
         profiles.Add(profile);
}
profiles.Sort(
     (profile1, profile2) =>
     {
        return profile1.Height - profile2.Height;
     }
);
foreach(var profile in profiles)
    Console.WriteLine("{0}, {1}", profile.Name, profile.Height);

위의 코드를 LINQ 로 바꾸면 아래와 같이 된다.

var profiles = from profile in arrProfile //arrProfile 안에 있는 각 데이터로부터
               where profile.Height < 175  //Height가 175 미만인 객체만 골라
               orderby profile.Height   //키순으로 정렬하여
               select profile;             //profile 객체를 추출한다.
foreach (var profile in profiles)
     Console.WriteLine("{0}, {1}", profile.Name, profile.Height);

15.2 LINQ의 기본 : from, where, orderby, select


15.2.1 from

모든 LINQ 쿼리식은 반드시 from 절로 시작한다.
쿼리식의 대상이 될 데이터 원본과 데이터 원본 안에 들어 있는 
각 요소 데이터를 나타내는 범위 변수를 from절에서 지정해 줘야 한다.
이 때 from의 데이터 원본은 아무 형식이나 사용할 수 없고,
IEnumerable<T> 인터페이스를 상속하는 형식이어야 한다.

배열이나 컬렉션 객체들은 IEnumerable<T>를 상속하기 때문에 
이들은 모두 from 절의 데이터 원본으로 사용할수 있다.

from 절을 이용해서 데이터 원본으로 부터 범위 변수를 뽑아낸 후에,
LINQ가 제공하는 수십가지 연산을 이용해서 데이터를 가공 및 추출할 수 있다.

15.2.2 where

where는 필터 역할을 하는 연산자다.
from이 데이터 원본으로부터 뽑아내면 
where 연산자로 조건을 걸어 부합하는 데이터만 걸러낸다.

var profiles = from profile in arrProfile //arrProfile 안에 있는 각 데이터로부터
               where profile.Height < 175  //Height가 175 미만인 객체만 골라
               orderby profile.Height   //키순으로 정렬하여
               select profile;             //profile 객체를 추출한다.

15.2.3 orderby


orderby 는 데이터의 정렬을 수행하는 연산자다.
기본적으로 오름차순으로 데이터를 정렬하지만,
오름차순으로 명시하기 위해서는 ascending 키워드로 명시하면 된다.

var profiles = from profile in arrProfile
               where profile.Height < 175
               orderby profile.Height ascending   //키순 오름차순으로 정렬
               select profile;          

내림차순으로 명시하기 위해서는 descending 키워드로 명시하면 된다

var profiles = from profile in arrProfile
               where profile.Height < 175
               orderby profile.Height  descending //키순 내림차순으로 정렬
               select profile;          

15.2.4 select

select 절은 최종 결과를 추출하는 쿼리식이다.

var profiles = from profile in arrProfile
               where profile.Height < 175
               orderby profile.Height
               select profile.Name;

위와 같이 Name만 추출하면 profiles 에는 string 형식으로 컴파일된다.
또한, select 문은 무명형식을 이용해서 다음과 같이 새로운 형식을 즉성에서 만들수 있다.

var profiles = from profile in arrProfile
               where profile.Height < 175
               orderby profile.Height
               select new {Name=profile, name, InchHeight=profile.Height*0.393};

15.3 여러 개의 데이터 원본에 질의하기


LINQ 쿼리식은 데이터 원본에 접근하기 위해 from절을 사용한다
여러개의 데이터 원본에 접근하려면 from문을 중첩해서 사용하면 된다.

예)

var classes = from c in arrClass    //첫번째 데이터 원본
                  from s in c.Score  //두번째 데이터 원본
                  where s < 60
               select new {c.Name, Lowest = s}; 


15.4 group by 로 데이터 분류하기


데이터를 그룹화 해주는 질의문이다.

group A by B into C

A에는 from 절에서 뽑아낸 범위 변수를, 
B에는 분류 기준을, 
C에는 그룹 변수를 위치 시키면 된다.

예)
var listProfile = from profile in arrProfile
                 group profile by profile.Height < 175 into g
                 select new {GroupKey = g.key, Profiles = 0};

15.5 두 데이터 원본을 연결하는 join


join 은 두 데이터 원본을 연결하는 연산이다.
각 데이터 원본에서 특정 필드의 값을 비교하여 
일치하는 데이터 끼리 연결을 수행한다.

15.5.1 내부조인


내부조인은 교집합과 비슷하다. 
두 데이터 원본 사이에서 일치하는 데이터들만 연결한 후 반환한다.
기준 데이터 a는 from절에서 뽑아낸 범위 변수이고, 
연결 대상 데이터 b는 join 절에서 뽑아낸 변수다. 
join 절의 on 키워드조인 조건을 수반한다
on 절의 조인 조건은 동등만 허용한다.
동등은 == 대신 equals  키워드를 사용한다.

from a in A
join b in B on a.xxx equals b.yyy

15.5.2 외부 조인


외부 조인은 기본적으로 내부 조인과 비슷하지만, 
기준이 되는 데이터 원본은 모두 다 포함된다는 점이 다르다.

예제)

var listProfile = 
   from profile in arrProfile
   join product in arrProduct on profile.Name equals product.Star into ps
   from product in ps.DefaultIfEmpty(new Product(){Title="그런거 없음"})
   select new
   {
      Name = profile.Name,
      Work = product.Title,
      Height = profile.Height
   };

15.6 LINQ의 비밀과 LINQ 표준 연산자


LINQ에는 많은 연산자가 있으나 여기에 다 쓰기엔 너무 힘들다 -_-;
그래서 msdn를 찾아볼 것을 추천한다.

뇌자극 C# 5.0 - 14강

-람다식-

14.1 처음 만드는 람다식

람다식은 익명 메소드를 만들기 위해 사용한다.
람다식으로 만드는 익명 메소드는 무명 함수라는 이름으로 부른다.

람다식 선언하는 형식

매개변수목록 => 식

=>는 입력 연산자 이다. 이 연산자의 역할은 그저 매개 변수를 전달하는 것 뿐이다.
람다식에서는 =>를 중심으로 왼편에는 매개변수가, 오른편에는 식이 위치한다.

예제)
//익명 메소드를 만들려면 델리게이트가 필요하다
delegate int Calculate(int a, int b);
...
...
static void Main(string[] atgs)
{
   Calculate calc = (int a, int b) => a + b; //익명 메소드를 람다식으로 만든 것
}

C#컴파일러는 위 코드를 한층 더 간결하게 만들 수 있도록 "형식유추"라는 기능을 제공한다.

static void Main(string[] atgs)
{
   Calculate calc = (a, b) => a + b; //익명 메소드를 람다식으로 만든 것
}

14.2 문 형식의 람다식

문 형식의 람다식은 => 연산자의 오른편에 식 대신 {와 }로 둘려싸인 코드블록이 위치한다.
형식은 다음과 같다.

(매개_변수_목록) => {
                           문장1;
                           문장2;
                           ....
                        }

문형식의 람다식에는 반환형식이 없는 무명함수를 만들 수 있다.

delegate void DoSomething();
//....
static void Main(string[] args)
{
   DoSomething DoIt = (  ) =>
          {
              Console.WriteLine("뭔가를");
              Console.WriteLine("출력해보자.");
              Console.WriteLine("이렇게!");
          }; //문장 형식의 람다식은 {}로 둘러싼다.
   DoIt();
}

14.3 Func와 Action으로 더 간편하게 무명 함수 만들기


익명 메소드와 무명 함수는 코드를 더 간결하게 만들어주는 요소다.
Func 델리게이트는 결과를 반환하는 메소드를 참조하기 위해
Action 델리게이트는 결과를 반환하지 않는 메소드를 참조한다.

Func 델리게이트의 형식 매개 변수 중 가장 마지막에 있는 것이 반환 형식이다.

예제 - 입력 매개 변수가 없는 버전)

Func<int> func1 = () => 10;  //입력 매개변수는 없으며, 무조건 10을 반환
Console.WriteLine(func1());  //10 출력

예제 - 입력 매개 변수가 하나 있는 버전)

Func<int, int> func2 = (x) => x*2;  //입력 매개변수는 int 형식 하나, 반환 형식도 int
Console.WriteLine(func2(3));  //6 출력

예제 - 입력 매개 변수가 두 개 있는 버전)

//입력 매개 변수는 int형식 둘, 반환 형식은 int
Func<int, int, int> func3 = (x, y) => x+y;  
Console.WriteLine(func3(2, 3));  //5 출력

Action 델리게이트

Action 델리게이트는 Func 델리게이트와 거의 같다.
차이점은 Action 델리게이트는 반환 형식이 없다는 것 뿐이다.

Action 델리게이트의 형식 매개 변수는 모두 입력 매개 변수를 위해 선언되어 있다.

예제-매개 변수가 아무 것도 없는 Action 사용)

Action act1 = () => Console.WriteLine("Action()");
act1(); 

예제 - 매개 변수가 하나뿐인 버전)

int result = 0;

//람다식 밖에서 선언한 result에 x*x의 결과를 저장
Action<int> act2 = (x) => result = x * x; 

act2(3);
Console.WriteLine("result : {0}", result); //9 를 출력

예제 - 매개 변수가 두 개인 Action<T1, T2> 델리게이트의 사용)

Action<double, double> act3 = (x,y) =>
{
   double pi = x/y;
   Console.WriteLine("Action<T1, T2>({0}, {1}) :{2}", x,y,pi);
};

act3 (22.0, 7.0);

뇌자극 C# 5.0 - 13강

-델리게이트와 이벤트-

13.1 델리게이트란?


비서처럼 어떤 일을 해줄 코드를 두고,
이 코드가 실행할 세부 코드는 컴파일 시점이 아닌
실행 시점에 부여하는 것을 콜백이라고 부르는데

델리게이트는 이 콜백을 구현하기 위해 사용된다.
델리게이트는 메소드에 대한 참조다.

한정자 delegate 반환형식 델리게이트이름 ( 매개변수목록 );

델리게이트는 인스턴스가 아닌 형식이다.

델리게이트를 이용하여 콜백을 구현하는 과정

1)델리게이트를 선언한다
2)델리게이트의 인스턴스를 생성한다. 인스턴스를 생성할 때는 델리게이트가 참조할 메소드를 매개 변수로 넘긴다.
3)델리게이트를 호출한다.

13.2 델리게이트는 왜, 그리고 언제 사용하나요?

프로그래밍을 하다 보면 "값"이 아닌 "코드" 자체를 매개 변수로 넘기고 싶을 때
델리게이트를 쓴다.

자세히 알아보자.

1)델리게이트를 선언한다.

delegate int Compare(int a, int b);

2) Comparer 델리게이트가 참조할 비교 메소드를 작성한다.

static int AscendComparer(int a, int b)
{
     if(a>b)
        return 1;
     else if(a==b)
        return 0;
     else
        return -1;
}

3) 정렬할 배열과 비교 메소드를 참조할 델리게이트를 매개 변수로 받는 정렬 메소드를 작성한다

static void BubbleSort(int[] DataSet, Compare Comparer)
{
   int i = 0;
   int j = 0;
   int temp = 0;

   for(i = 0; i<DataSet.Length - (i+1); i++)
   {
      if(Comparer(DataSet[j], DataSet[j+1])>0
      {
         temp = DataSet[j+1];
         DataSet[j+1] = DataSet[j];
         DataSet[j] = temp;
      }
   }
}

4) 정렬 메소드를 호출하면 원하는 대로 정렬 방식이 분리된 정렬 코드를 얻을 수 있다.

int[] array = {3,7,4,2,10};
BubbleSort(array, new Compare(AscendComparer); //array는 {2,3,4,7,10}

13.3 일반화 델리게이트

델리게이트는 보통의 메소드뿐 아니라 일반화 메소드도 참조할 수 있다.
이 경우에는 델리게이트도 일반화 메소드를 참조할 수 있도록 
형식 매개 변수를 이용하여 선언해야 한다.

delegate int Compare<T>(T a, T b);

예) 위의 함수를 일반화로 바꾼다면...

static void BubbleSort<T>(T[] DataSet, Compare<T> Comparer)
{
   int i = 0;
   int j = 0;
   int temp = 0;

   for(i = 0; i<DataSet.Length - (i+1); i++)
   {
      if(Comparer(DataSet[j], DataSet[j+1])>0
      {
         temp = DataSet[j+1];
         DataSet[j+1] = DataSet[j];
         DataSet[j] = temp;
      }
   }
}

13.4 델리게이트 체인

델리게이트 하나가 여러개의 메소드를 동시에 참조할 수 있다.

예제)

delegate void ThereIsAFire(string location);

void Call119(string location)
{
   Console.WriteLine("소방서죠? 불났어요! 주소는 {0}", location);
}

void ShotOut(string location)
{
   Console.WriteLine("피하세요!! {0}에 불이 났어요!", location);
}

void Escape(string location)
{
   Console.WriteLine("{0}에서 나갑시다!", location);
}


이렇게 선언한 메소드들은 델리게이트의 인스턴스가 자신들을 동시에 참조할 수 있도록 
+= 연산자를 이용하여 결합할 수 있다.

ThereIsAFire Fire = new ThereIsAFirs(Call119);
Fire += new ThereIsAFire(ShotOut);
Fire += new ThereIsAFire(Escape);

Fire("우리집");

-출력결과-
소방서죠?불났어요!주소는 우리집
피하세요! 우리집에 불이 났어요!
우리집에서 나갑시다!

13.5 익명 메소드

익명 메소드는 이름 그대로 이름이 없는 메소드다.
이름이 없는 메소드를 델리게이트에서 사용하는 법을 알아본다

delegate int Calculate(int a, int b);

라는 델리게이트가 있다면 익명 메소드로는 아래와 같이 쓴다.

public static void Main()
{
   Calculate Calc;
   calc = delegate(int a, int b)
           {
               return a + b;
           }
   Console.WriteLine("3+4 : {0}", Calc(3,4) );
}

익명 메소드는 delegate 키워드를 이용하여 선언한다.

델리게이트 인스턴스 = delegate (매개변수목록)
                          {
                             //실행하고자 하는 코드....
                          }

13.6 이벤트 : 객체에 일어난 사건 알리기

이벤트를 선언하고 사용하는 절차

1)델리게이트를 선언

delegate void EventHandler(string message);

2)클래스 내에 1)에서 선언한 델리게이트 인스턴스를 event 한정자로 수식해서 선언 한다

class MyNotifier
{
   public event EventHandler SomethingHappened;
   public void DoSomething(int number)
   {
      int temp = number % 10;
      if(temp != 0 && temp % 3== 0)
      {
          SomethingHappened(String.Format("{0}: 짝", number);
      }
   }
}

3)이벤트 핸들러 작성. 이벤트 핸들러는 1)에서 선언한 델리게이트와 일치하는 메소드면 된다.

class MainApp
{
    static public void MyHandler(string message)
    {
       Console.WriteLine(message);
    }
    //....
}

4)클래스의 인스턴스를 생성하고, 이 객체의 이벤트에 3)에서 작성한 이벤트 핸들러를 등록

class MainApp
{
   static public void MyHandler(string message)
   {
      Console.WriteLine(message);
   }
   static void Main(string[] args)
   {
     MyNotifer notifier = new MyNotifier();
     notifier.SomethingHappened += new EventHandler(MyHandler);
     for(int i = 1; i < 30; i++)
     {
         notifier.DoSomething(i);
     }
   }
}

5)이벤트가 발생하면 이벤트 핸들러가 호출된다.

class MainApp
{
   static public void MyHandler(string message)
   {
      Console.WriteLine(message);
   }
   static void Main(string[] args)
   {
     MyNotifer notifier = new MyNotifier();
     notifier.SomethingHappened += new EventHandler(MyHandler);
     for(int i = 1; i < 30; i++)
     {
         notifier.DoSomething(i);
     }
   }
}

13.7 델리게이트와 이벤트

이벤트는 델리게이트에 event 키워드로 수식해서 선언한 것에 불과하다.
이벤트가 델리게이트와 가장 크게 다른 점은 
이벤트는 외부에서 직접 사용할 수 없다는 것이다.
이벤트는 public 로 선언되어 있어도 
자신이 선언되어 있는 클래스 외부에서는 호출이 불가능 하다

델리게이트는 콜백 용도로 사용하고,
이벤트는 객체의 상태 변화사건의 발생을 알리는 용도로 구분해서 사용해야 한다.