1. Delegate
-
Delegate 정의
method를 가리킬 수 있는 type. -
Delegate 문법
접근제한자
delegate_method의_return_type
식별자(.....method의_parameter목록.....);
int Clean (object);
를 delegate type로 정의하면 delegate int FuncDelegate(Object arg);
와 같다. delegate object를 생성할 때 delegate에 wrapping되는 method의 이름 또는 anonymous Method를 통해 생성된다. 인스턴스화된 delegate는 object이므로 parameter 또는 property로 할당할 수 있다.
class Test
{
// Create a method for a delegate.
public static void DelegateMethod(string message)
{
System.Console.WriteLine(message);
}
}
class Program
{
public delegate void Del(string message);
static void Main(string[] args)
{
Del handler = new Del(Test.DelegateMethod);
//Del handler = Test.DelegateMethod; c# 2.0부터는 Delegate type을 new없이 대입할 수 있는 문법을 제공한다.
// Call the delegate.
handler("Hello World");
}
}
-
Delegate chain
delegate의 가장 강력한 속성은 여러 method들의 chain을 담을 수 있다는 것이다. method들의 chain은 delegate가 invoke될 때 호출된다. 대부분 .NET language에서 delegate chain은 BCL library의 Delegateclass의 Combine() method에 의해 생성된다. 그러나 C#에서는 +operator가 overload해주기 때문에 delegate chain을 문법적으로 더 깔끔하게 생성할 수 있다. -operator는 delegate chain으로부터 해당 method를 제거한다.
using System;
// Define a custom delegate that has a string parameter and returns void.
delegate void CustomDel(string s);
class TestClass
{
// Define two methods that have the same signature as CustomDel.
static void Hello(string s)
{
System.Console.WriteLine(" Hello, {0}!", s);
}
static void Goodbye(string s)
{
System.Console.WriteLine(" Goodbye, {0}!", s);
}
static void Main()
{
// Declare instances of the custom delegate.
CustomDel hiDel, byeDel, multiDel, multiMinusHiDel;
// In this example, you can omit the custom delegate if you
// want to and use Action<string> instead.
//Action<string> hiDel, byeDel, multiDel, multiMinusHiDel;
// Create the delegate object hiDel that references the
// method Hello.
hiDel = Hello;
// Create the delegate object byeDel that references the
// method Goodbye.
byeDel = Goodbye;
// The two delegates, hiDel and byeDel, are combined to
// form multiDel.
multiDel = hiDel + byeDel;
// Remove hiDel from the multicast delegate, leaving byeDel,
// which calls only the method Goodbye.
multiMinusHiDel = multiDel - hiDel;
Console.WriteLine("Invoking delegate hiDel:");
hiDel("A");
Console.WriteLine("Invoking delegate byeDel:");
byeDel("B");
Console.WriteLine("Invoking delegate multiDel:");
multiDel("C");
Console.WriteLine("Invoking delegate multiMinusHiDel:");
multiMinusHiDel("D");
}
}
/* Output:
Invoking delegate hiDel:
Hello, A!
Invoking delegate byeDel:
Goodbye, B!
Invoking delegate multiDel:
Hello, C!
Goodbye, C!
Invoking delegate multiMinusHiDel:
Goodbye, D!
*/
delegate chain 생성 시 원한다면 Chain()이나 Combine() method를 사용해도 된다. 사실상 +operator를 사용했을 때 C# compiler가 내부적으로 Chain, Combine method를 호출하기 때문이다.
-
Event and Delegate
event와 delegate는 component가 비동기적으로 중요한 일이 발생한 client를 알리는 것을 허락한다. delegate가 흥미로운 것은 .NET Framework에서 delegate가 event 배후의 근본적인 architecture이다. class가 event를 노출시킬 때 실제로 delegate callback mechanism을 사용한다. event 개념은 delegate의 asynchronous 속성과 밀접한 관련이 있다.
2. Event
-
Event 정의
event도 간편표기법 중의 하나인데, 다음 조건을 만족하는 정형화된 callback 패턴을 구현하려고 할 때 event keyword를 사용하면 코드를 줄일 수 있다.
- class에서 event(callback)을 제공한다.
- 외부에서 자유롭게 해당 event(callback)를 구독하거나 해지하는 것이 가능하다.
- 외부에서 구독/해지는 가능하지만 event발생은 오직 내부에서만 가능하다.
- event(callback)의 첫 번째 인자는 event를 발생시킨 타입의 instance다.
- event(callback)의 두 번째 인자는 해당 event에 속한 의미 있는 값이 제공된다.
class OddCallbackArg : EventArgs
{
public int Odd;
public OddCallbackArg(int odd)
{
this.Odd = odd;
}
}
class OddGenerator
{
public event EventHandler OddGenerated;
public void Run(int limit)
{
for (int i = 1; i <= limit; i++)
{
if (i % 2 != 0)
{
OddGenerated(this, new OddCallbackArg(i));
}
}
}
}
class Program
{
static void PrintOdd(object sender, EventArgs arg)
{
Console.Write((arg as OddCallbackArg).Odd + " ");
}
static int Sum;
static void SumOdd(object sender, EventArgs arg)
{
Sum += (arg as OddCallbackArg).Odd;
}
static void Main()
{
OddGenerator gen = new OddGenerator();
gen.OddGenerated += PrintOdd;
gen.OddGenerated += SumOdd;
gen.Run(20);
Console.WriteLine();
Console.WriteLine(Sum);
}
}
3. 람다식
- 람다식 용도
-
코드로서의 람다 식
anonymous method의 간편 표기 용도로 사용된다. -
데이터로서의 람다 식
람다 식 자체가 데이터가 되어 구문 분석의 대상이 된다. 이 람다식은 별도로 컴파일 할 수 있으며, 그렇게 되면 method로도 실행할 수 있다.
- 람다식 용법
- 코드로서의 람다 식
Thread thread = new Thread{
delegate(object obj)
{
Console.WriteLine("ThreadFunc in anonymous method called!");
}};
C# compiler는 Thread type의 생성자가 "void (object obj)"형식의 delegate parameter를 하나 받는다는 사실을 알고 있다. 따라서 anonymous method의 syntax를 더욱 단순화해서 다음과 같이 정의할 수 있다.
Thread thread = new Thread{
(obj) =>
{
Console.WriteLine("ThreadFunc in anonymous method called!");
}};
람다식은 약식표현을 하나 더 제공한다. 기본적으로 값이 반환된다는 가정하에 return 문을 생략할 수 있으며, 이 경우 람다 식의 연산자인 "=>" 기호 다음에 오는 중괄호까지 생략한다. 약식표현된 람다 식은 세미콜론(;)을 사용해 여러 줄의 코드를 넣을 수 없다는 제약이 있다.
class Program
{
delegate int MyAdd(int a, int b);
delegate int? MyDivide(int a, int b);
static void Main(string[] args)
{
Thread thread = new Thread((obj) => Console.WriteLine("ThreadFunc in anonymous method called!"));
thread.Start();
MyAdd myFunc = (a, b) => a + b;
Console.WriteLine("10 + 2 == " + myFunc(10, 2));
}
}
- 데이터로서의 람다 식
식을 표현한 데이터로 expression tree라고 한다.
static void Main(string[] args)
{
Expression<Func<int, bool>> exprTree = num => num < 5;
// Decompose the expression tree.
ParameterExpression param = (ParameterExpression)exprTree.Parameters[0];
BinaryExpression operation = (BinaryExpression)exprTree.Body;
ParameterExpression left = (ParameterExpression)operation.Left;
ConstantExpression right = (ConstantExpression)operation.Right;
Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}", param.Name, left.Name, operation.NodeType, right.Value);
}
-
**람다 식을 위한 전용 delegate :**람다 식은 기존 method와 달리 일회성으로 사용되는 간단한 코드를 표현할 때 사용되는데, 정작 그러한 목적으로 delegate를 일일이 정의해야한다는 불편함이 발생한다. 마이크로소프트에서는 이러한 불편을 덜기 위해 자주 사용되는 delegate형식을 generic의 도움으로 일반화해서 BCL에 Action, Func로 포함시켰다.
// return값이 없는 delegate로 입력될 parameter 1개의 type을 지정
public delegate void Action<T>(T arg);
public delegate void Action<T1, T2>(T arg1, T arg2);
public delegate void Action<T1, T2, T3>(T arg1, T arg2, T arg3);
// return값이 있는 delegate로 TResult 형식 parameter는 반환될 타입을 지정
public delegate TResult Func<TResult>();
public delegate TResult Func<T, TResult>(T arg);
public delegate TResult Func<T1, T2, TResult>(T arg1, T arg2);
[생략 : T1 ~ T16까지 Action, Func delegate 정의]
-
람다식으로 Event를 구현하기
Event의 Event 사용법예제에서 callback method 부분을 아래와 같이 람다 식을 사용해서 구현할 수 있다.
class Program
{
static int Sum;
static void Main()
{
OddGenerator gen = new OddGenerator();
gen.OddGenerated += (sender, arg) => Console.Write((arg as OddCallbackArg).Odd + " ");
gen.OddGenerated += (sender, arg) => Sum += (arg as OddCallbackArg).Odd;
gen.Run(20);
Console.WriteLine();
Console.WriteLine(Sum);
}
}
4. 확장 메소드 조사
일반적으로 기존 class를 확장하는 방법으로 상속이 많이 쓰인다. 하지만 sealed class나 class를 상속받아 확장하면 기존 소스코드를 새롭게 상속받은 클래스명으로 바꿔야하는 경우에 상속이 좋은 선택은 아니다. 따라서 기존 class 내부 구조를 전혀 바꾸지 않고 마치 새로운 instance method를 정의하는 것처럼 추가할 수 있는데, 이를 extension method라 한다.
- 확장 method는 static class에 정의되어야 함
- 확장 method는 반드시 static이여야 한다
- 확장하려는 type의 parameter를 this keyword와 함께 명시
- static method의 호출을 instance method를 호출하듯이 문법적으로 지원해주는 것이므로 base class의 protected member 호출이나 method override가 불가능하다
public static class ExtensionMethods
{
public static string UppercaseFirstLetter(this string value)
{
if (value.Length > 0)
{
char[] array = value.ToCharArray();
array[0] = char.ToUpper(array[0]);
return new string(array);
}
return value;
}
}
class Program
{
static void Main()
{
string value = "dot net perls";
value = value.UppercaseFirstLetter();
Console.WriteLine(value);
}
}
1. FirstOrDefault()
public static TSource FirstOrDefault<TSource>(
this IEnumerable<TSource> source
)
기본적으로 sequence의 first element를 반환하고 element가 없을 경우 default value를 반환한다.
List<int> months = new List<int> { };
int firstMonth = months.FirstOrDefault();
Console.WriteLine("The value of the firstMonth variable is {0}", firstMonth);
months.Add(12);
months.Add(1);
firstMonth = months.FirstOrDefault();
Console.WriteLine("The value of the firstMonth variable is {0}", firstMonth);
2. SingleOrDefault()
public static TSource SingleOrDefault<TSource>(
this IEnumerable<TSource> source
)
element가 1개라는 전제하에 사용되며 그 element를 반환한다. element가 없을 경우 default value를 반환한다(sequence에 하나 이상의 element가 있을 경우 exception을 발생시킨다).
int[] pageNumbers = { 2, };
// Setting the default value to 1 after the query.
int pageNumber1 = pageNumbers.SingleOrDefault();
Console.WriteLine("The value of the pageNumber1 variable is {0}", pageNumber1);
Array.Clear(pageNumbers, 0, 1);
pageNumber1 = pageNumbers.SingleOrDefault();
Console.WriteLine("The value of the pageNumber1 variable is {0}", pageNumber1);
3. Where()
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate
)
predicate 값에 따라 value를 filtering한다.
List<string> fruits = new List<string> { "apple", "passionfruit", "banana", "mango", "orange", "blueberry", "grape", "strawberry" };
IEnumerable<string> query = fruits.Where(fruit => fruit.Length < 6);
foreach (string fruit in query)
{
Console.WriteLine(fruit);
}
4. Select()
public static IEnumerable<TResult> Select<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, TResult> selector
)
sequence의 각각 element를 새로운 형태로 만든다.
List<string> fruits = new List<string> { "apple", "passionfruit", "banana", "mango", "orange", "blueberry", "grape", "strawberry" };
IEnumerable<int> query = fruits.Select(fruit => fruit.Length);
foreach (int fruit in query)
{
Console.WriteLine(fruit);
}
5. LINQ 기본 문법 정리
1. where
LINQ의 where를 통해 IEnumerable<T>
type인 list collection에서 특정 조건을 만족하는 data를 filtering 할 수 있다. where의 조건절에는 return type으로 bool type을 만족하는 어떤 C#코드도 올 수 있다.
2. orderby
orderby를 이용해 정렬 작업을 수행할 수 있다. 기본적으로 orderby person.Age ascending
(올림차순)으로 정렬하지만 orderby person.Age descending
(내림차순)으로 정렬 할 수 있다.
3. group by
LINQ의 group by는 지정된 값을 기준으로 collection의 요소를 그룹으로 분류하고 최종적으로 모든 그룹의 collection을 반환한다. group by의 가능이 select를 담당하고 있기 때문에 select 구문이 올 수 없다.
4. join
LINQ의 join은 data collection 중에서 on equal 조건을 만족하는 요소끼리 묶어 반환하는 기능을 제공한다. on equal은 조건을 만족하는 record가 없다면 제외시킨다. 이러한 join 유형을 Inner join이라고 한다. LINQ에는 Outer join에 대한 keyword는 별도로 없고 아래 그림과 같이 collection을 후처리하는 방법을 사용한다.
List<Person> people = new List<Person>
{
new Person { Name = "Tom", Age = 63, Hometown = "Korea" },
new Person { Name = "Ellie", Age = 23, Hometown = "Tibet" },
new Person { Name = "Suji", Age = 30, Hometown = "Sudan" },
new Person { Name = "Zulla", Age = 35, Hometown = "Korea" },
new Person { Name = "Anders", Age = 44, Hometown = "Korea" },
new Person { Name = "Hans", Age = 24, Hometown = "Sudan" },
};
List<MainLanguage> languages = new List<MainLanguage>
{
new MainLanguage { Name = "Tom", Language = "C" },
new MainLanguage { Name = "Ellie", Language = "C#" },
new MainLanguage { Name = "Anders", Language = "C++" },
new MainLanguage { Name = "Hans", Language = "Java" },
};
var groupTemp = from person in people
join language in languages on person.Name equals language.Name into lang
from language in lang.DefaultIfEmpty(new MainLanguage())
select new
{
Name = person.Name,
Age = person.Age,
Language = language.Language
};
join으로 languages collection의 내용을 lang이라는 이름의 임시 collection으로 보낸 후 개별 요소에 대해 IEnumerable type의 DefaultIfEmpty Extension method를 호출한다.
-
into keyword
추가적인 query operation을 수행하고 싶으면 임시 식별자인 into를 지정할 수 있다. into를 사용하면 query를 계속 해야하고 select statement에서 끝내야 한다.
6. 코드분석
var groupTemp = from data in TxPowerItems
group data by data.Path into newData
orderby newData.Key
select new
{
Key = newData.Key,
Temps = newData.ToList()
};
// TxPowerItems를 data.Path로 group화하고 newData인 임시 collection으로 보낸다.
// newData의 Key값 즉, data.Path를 ascending 정렬한다.
// select를 사용해 Key, Temps로 형변환된 IEnumerable를 반환한다.
질문) groupTemp 변수는 어떤 형태의 값이 채워지나요?
{ Key = PmPowerItem의 Path값, Temps = List(해당 Key(Path)값에 해당하는 PmPowerItem class의 list)} 의 값으로 채워집니다.
예) { Key = 2.경기도-성남시-Probe_00, Temps = List }
{ Key = 2.경기도-성남시-관양로L0_예비, Temps = List }
List값인 Temps는 아래와 같이 접근할 수 있다.
foreach (var data in groupTemp)
{
foreach (PmPowerItem elem in data.Temps)
Console.WriteLine("{0}, {1}, {2}", elem.Number, elem.Type, elem.ProbeId);
}