호출자 정보
class CallerInfo
{
public static void Main()
{
LogMessage("test");
}
//호출자 정보는 '호출하는 측의 정보'를 메서드의 인자로 전달하는 것이다.
//호출자 정보 특성이 명시된 매개변수는 선택적 매개변수 형식이여야 한다.
//컴파일 시점에 값이 치환되어 빌드된다.
static void LogMessage(string text,
[CallerMemberName] string memberName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
Console.WriteLine("텍스트 : " + text); //"test" 출력
Console.WriteLine("이 메서드를 호출하는 메서드 이름" + memberName); //Main 출력
Console.WriteLine("호출 파일 경로" + filePath); //파일 경로 출력
Console.WriteLine("메서드가 호출된 라인 번호" + lineNumber); //10 출력
}
}
비동기 호출
async/await 예약어
비동기 호출에 await 예약어가 함께 쓰이면 C# 컴파일러는 이를 인지하고 그 이후의 코드를 묶어서 비동기 호출이 끝난 다음에 실행되도록 코드를 변경해서 컴파일한다. async 예약어가 있으면 컴파일러는 await 예약어를 예약어로 인식한다. async 예약어가 없으면 그냥 식별자로 인식한다.
class AsyncAwaitTest
{
//async 예약어가 없이 await를 사용할 수 없다.
private static async void AwaitRead()
{
using (FileStream fs = new FileStream("test.log", FileMode.Open))
{
byte[] buffer = new byte[fs.Length];
Console.WriteLine("실행 전의 스레드 id : " + Thread.CurrentThread.ManagedThreadId);
await fs.ReadAsync(buffer, 0, buffer.Length);
Console.WriteLine("실행 후의 스레드 id : " + Thread.CurrentThread.ManagedThreadId);
//ReadAsync 이하의 처리는 스레드 풀 스레드가 담당하기 때문에 스레드 id가 다르게 나온다.
//이하의 라인은 ReadAsync 비동기 호출이 완료된 후 호출
string txt = Encoding.UTF8.GetString(buffer);
Console.WriteLine(txt);
}
}
public static void Main()
{
AwaitRead();
Console.WriteLine("메인 함수 스레드 id : " + Thread.CurrentThread.ManagedThreadId);
//이게 2번째로 실행된다.
Console.ReadLine();
}
}
class BCLAsyncTest //WebClient 비동기 호출 async/await 예제
{
public static void Main()
{
AwaitDownloadString();
Console.ReadLine();
}
private static async void AwaitDownloadString()
{
WebClient wc = new WebClient();
wc.Encoding = Encoding.UTF8;
//DownloadStringAsync보다 더 간편하게 이용 가능.
string text = await wc.DownloadStringTaskAsync("http://www.naver.com");
Console.WriteLine(text);
}
}
class BCLAsyncTest2 //TCP 서버 비동기 통신 예제
{
public static void Main()
{
TcpListener listener = new TcpListener(IPAddress.Any, 11200);
listener.Start();
while (true)
{
//연결 요청을 받아들인다.
var client = listener.AcceptTcpClient();
ProcessTcpClient(client);
}
}
//NetworkStream 클래스의 ReadAsync와 WriteAsync를 이용하면 간단하게 비동기 통신 구현 가능
private static async void ProcessTcpClient(TcpClient client)
{
NetworkStream ns = client.GetStream();
byte[] buffer = new byte[1024];
int received = await ns.ReadAsync(buffer, 0, buffer.Length);
string txt = Encoding.UTF8.GetString(buffer, 0 ,received);
byte[] sendBuffer = Encoding.UTF8.GetBytes("Hello : " + txt);
await ns.WriteAsync(sendBuffer, 0, sendBuffer.Length);
ns.Close();
}
}
Task, Task<TResult> 타입
Async 메서드의 반환값은 모두 Task 또는 Task<TResult> 유형이다. Task 타입은 반환값이 없는 경우 사용되고, Task<TResult> 타입은 TResult 형식 매개변수로 지정된 반환값이 있는 경우로 구분된다.
class TaskType
{
public static void Main()
{
//스레드 풀에서
ThreadPool.QueueUserWorkItem((obj) => { Console.WriteLine("process workitem"); }, null);
Task task1 = new Task(() => { Console.WriteLine("process taskitem"); });
task1.Start();
Task task2 = new Task((obj) => { Console.WriteLine("process taskitem(obj)"); }, null);
task2.Start();
task1.Wait(); //task1의 작업이 끝날 때까지 현재 스레드를 대기한다.
task2.Wait();
//task 객체를 생성할 필요 없이 바로 작업 시작 가능.
Task.Factory.StartNew(() => { Console.WriteLine("process taskitem StartNew"); });
//Task<TResult> 타입은 값을 반환할 수 있다.
Task<int> task3 = new Task<int>(() =>
{
Random rand = new Random((int)DateTime.Now.Ticks);
return rand.Next();
}
);
task3.Start();
task3.Wait();
Console.WriteLine("무작위 숫자 값 : " + task3.Result);
//StartNew<TResult> 도 반환.
Task<int> taskReturn = Task.Factory.StartNew<int>(() => 255);
taskReturn.Wait();
Console.WriteLine(taskReturn.Result);
}
}
Async 메서드가 아닌 경우의 비동기 처리
class TaskAsync
{
//비동기로 처리할 ReadAllTextAsync라는 메서드를 만들어서 파일 경로를 넘겨준다.
private static async void AwaitFileRead(string filePath)
{
string fileText = await ReadAllTextAsync(filePath);
Console.WriteLine(fileText);
}
//Task를 이용하여 넘겨받은 파일 경로로 들어가 텍스트를 읽고 리턴한다.
static Task<string> ReadAllTextAsync(string filePath)
{
return Task.Factory.StartNew(() =>
{
return File.ReadAllText(filePath);
});
}
public static void Main()
{
string filePath = "test.log";
//비동기로 처리되므로 바로 다음 줄 실행
AwaitFileRead(filePath);
Console.ReadLine();
}
}
비동기 호출의 병렬 처리
//비동기 처리 담당 메서드
private static async void DoAsyncTask()
{
var task3 = Method3Async();
var task5 = Method5Async();
//task3, task5가 끝날 때까지 대기한다.
await Task.WhenAll(task3, task5);
//그 이후 실행
Console.WriteLine(task3.Result + task5.Result);
}
//3초 후 3리턴
private static Task<int> Method3Async()
{
return Task.Factory.StartNew( () =>
{
Thread.Sleep(3000);
return 3;
});
}
//5초 후 5리턴
private static Task<int> Method5Async()
{
return Task.Factory.StartNew(() =>
{
Thread.Sleep(5000);
return 5;
});
}
public static void Main()
{
DoAsyncTask();
Console.WriteLine("먼저 출력되고, 비동기 값 나중에 출력됨.");
Console.ReadLine();
}
}
확장메서드
Static 메서드를 인스턴스 메서드처럼 클래스명 없이 바로 호출할 수 있는 메서드이다. 메서드의 첫 번째 인자에 확장하려는 타입을 넣고 그 앞에 this 키워드를 붙이면 된다.
//현재 날짜로부터 n일을 뺀 날짜를 구해주는 메서드를 가진 헬퍼 클래스.
public static class DateTimeHelper
{
//기존 헬퍼 메서드
public static DateTime MinusDays(DateTime dt, int days)
{
DateTime d = dt.AddDays(-days);
return d;
}
//확장 메서드
//확장하려는 타입이 DateTime이고 그 앞에 this를 붙였다.
public static DateTime MinusDaysEx(this DateTime dt, int days)
{
DateTime d = dt.AddDays(-days);
return d;
}
}
class ExtensionMethod
{
public static void Main()
{
//DateHelper라는 클래스 이름과 함께 MinusDays 메서드를 호출해야한다.
//날짜 연산의 대상이 되는 current 객체를 넘겨줘야 한다.
DateTime current = DateTime.Now;
DateTime dt1 = DateTimeHelper.MinusDays(current, 3);
Console.WriteLine(dt1.ToString());
//확장 메서드를 이용하면 헬퍼 클래스의 이름을 기억해서 호출할 필요가 없다.
//대상이 되는 객체를 인자로 넘겨줄 필요가 없다.
DateTime dt2 = current.MinusDaysEx(3);
Console.WriteLine(dt2.ToString());
Console.ReadLine();
}
}
람다 식
코드로서의 람다 식
익명 메서드를 이용하면 델리게이트에 전달되는 메서드가 일회성으로 사용될 때 간편하게 나타낼 수 있다.
//익명 메서드를 이용한 예시
Thread thread = new Thread(
delegate(object obj)
{
Console.WriteLine("익명 메서드로 호출!");
});
위의 코드를 람다 식을 이용하면 더욱 간편하게 표기가능하다.
Thread thread = new Thread(
//object obj 처럼 더 명확하게 나타낼 수 있다. 전달되는 매개변수가 없으면 ()로 표기.
(obj) =>
{
Console.WriteLine("람다 식으로 호출!");
});
매개변수가 2개 이상이면 다음과 같이 표기한다.
delegate int? MyDivide(int a, int b);
MyDivide myFunc = (a,b) =>
{
if (b == 0)
{
return null;
}
return a / b;
};
return 문을 생략도 가능하다.
delegate int MyAdd(int a, int b);
MyAdd myFunc = (a, b) => a + b;
람다 식을 위한 전용 델리게이트
람다식은 델리게이트랑 대응된다. 하지만 일회성으로 사용되는 간단한 람다 식을 쓸 때마다 델리게이트를 정의해야 한다면 불편할 것이다. 그래서 자주 사용되는 델리게이트 형식을 제공한다.
public delegate void Action<T>(T obj);
-> 반환값이 없는 델리게이트로서 T 형식 매개변수는 입력될 인자 1개의 타입을 지정.
public delegate TResult Func<TResult>();
-> 반환값이 있는 델리게이트로서 TResult 형식 매개변수는 반환될 타입을 지정.
class ActionFunc
{
public static void Main()
{
Action<string> printText =
(txt) =>
{
Console.WriteLine("매개변수로 입력된 텍스트는 : " + txt);
};
printText("좋은 하루입니다!");
//Func<T, TResult>(T arg);
Func<int, int> square = (num) => num* num;
Console.WriteLine(square(5));
}
}
인자를 16개까지 받을 수 있는 Action과 Func가 미리 정의되어 있다.
컬렉션과 람다 식
class CollectionLambda
{
public static void Main()
{
List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
//ForEach 메서드를 이용해 간단하게 컬렉션에서의 연산 가능.
//컬렉션에서 요소를 하나씩 꺼내서 element로 전달. Action<T> 연산 수행.
list.ForEach((element) => { Console.WriteLine(element + " * 2 == " + (element * 2)); });
Array.ForEach(list.ToArray(), (element) => { Console.WriteLine(element + " * 2 == " + (element * 2)); });
//FindAll 메서드를 이용하면 특정 조건에 맞는 요소만 고를 수 있다.
List<int> evenList = list.FindAll((element) => element % 2 == 0);
evenList.ForEach(Console.WriteLine);
//Count 메서드를 이용해서 특정 조건을 만족하는 요소들의 개수를 셀 수 있다.
int count = list.Count((element) => element > 3);
Console.WriteLine("3보다 큰 요소의 개수 : " + count);
}
}
LINQ 메서드식 표현
LINQ 쿼리는 컴파일 될 때 LINQ 확장 메서드로 자동 변경되서 호출된다. 다음의 세 가지 코드는 완전히 동일한 역할을 한다.
표현 | 코드 |
---|---|
LINQ 표현 | from person in people select person; |
확장 메서드 표현 | people.Select( (elem) => elem ); |
일반 메서드 표현 | IEnumerable SelectFunc(List people) { foreach (var item in people) { yield return item; } } |
LINQ 확장 메서드 중 Where()과 FirstOrDefault()의 예제 코드이다.
//public static IEnumerable<TSource> Where<TSource>(
//this IEnumerable<TSource> source, Func< TSource, bool> predicate)
//Where 메서드는 IEnumerable<T> 인터페이스를 지원하는 모든 타입에 사용 가능하다.
//predicate가 True를 리턴하는 요소만 모아서 IEnumerable<T>로 열거형을 반환한다.
IEnumerable<int> enumList = list.Where((element) => element % 2 == 0);
Array.ForEach(enumList.ToArray(), (element) => { Console.WriteLine(element); });
var enumList2 = list.Where((element) => element % 2 == 1);
enumList2.ToList<int>().ForEach(n => Console.WriteLine("홀수는 = " + n));
//public static TSource FirstOrDefault<TSource>(
//this IEnumerable<TSource> source)
//FirstOrDefault 메서드는 조건식에 맞는 처음 요소를 찾는 경우에 사용한다.
//조건에 맞는 요소가 여러 개일 경우 첫 요소만 반환하며, 하나도 없을 경우는 기본값(보통 null)을 반환한다.
List<string> stringList = new List<string>() { "Apple", "AOA", "Banana", "Grape" };
var result = stringList.Where((element) => element.StartsWith("A")).FirstOrDefault();
Console.WriteLine("A로 시작하는 첫 번째 요소는 : " + result);
var result2 = stringList.Where((element) => element.StartsWith("C")).FirstOrDefault();
//그냥 null로 체크하는 것 보다는 default(type)으로 체크하는게 더 안전하다.
//밸류 타입의 경우는 null로 체크하면 에러를 반환한다.
if (result2 == default(string))
{
Console.WriteLine("C로 시작하는 요소가 없습니다.");
}
else
{
Console.WriteLine("C로 시작하는 첫 번째 요소는 : " + result2);
}