1. Delegate
Delegate 정의
***델리게이트(Delegate)***는 대리자로 해석되며, 메서드를 가리킬 수 있는 타입으로 메서드를 대신 실행해주는 역할을 한다.
delegate
예약어를 사용하여 타입을 선언한다.
Delegate 문법
public delegate int CalcDelegate(int x, int y);
// 대상 메서드의 반환 타입 및 매개변수 목록과 일치하는 델리게이트 타입을 정의
Delegate 속성
- Delegate는 C++의 함수 포인터와 유사하지만 형식이 안전(type-safety)하다.
- Delegate를 통해 메서드를 매개 변수로 전달할 수 있다.
- Delegate를 사용하여 콜백 메서드를 정의할 수 있다.
- 여러 Delegate를 연결할 수 있습니다. 예를 들어 단일 이벤트에 대해 여러 메서드를 호출할 수 있다.
- 메서드와 Delegate 형식이 정확히 일치할 필요는 없다.
형식 안전성(type-safety) : 어떠한 연산이 예측 불가능한 결과는 내지 않는 것. 형식 안전성이 보장되지 않는 언어는 예측 불가능한 결과를 내게 된다.
Delegate 체인
Delegate를 여러개 연결해서 사용하는 방식이다. 연결된 Delegate는 내부 리스트에 저장되며 차례대로 호출되고 예외 발생시 중단된다.
class Program
{
// Delegate 타입 선언
delegate void ClacDelegate(int x, int y);
static void Add(int x, int y) { Console.WriteLine(x + y); }
static void Subtract(int x, int y) { Console.WriteLine(x - y); }
static void Multiply(int x, int y) { Console.WriteLine(x * y); }
static void Divide(int x, int y) { Console.WriteLine(x / y); }
static void Main(string[] args)
{
ClacDelegate calc = Add;
calc(10, 5); // 15
calc += Subtract;
calc += Multiply;
calc += Divide;
calc(10, 5); // 15 5 50 2
calc -= Subtract;
calc(10, 5); // 15 50 2
}
}
2. Event
Event 정의
event
는 정형화된 콜백 패턴 구현을 위해 사용되는 delegate의 간편 표기법이다.
기존 delegate로도 동일하게 구현할 수 있지만, event 예약어를 사용하면 코드를 줄일 수 있다.
또한 event를 이용해 외부 구독자(Subscriber)에 의해 delegate에 새롭게 할당하는 것을 제한하고, 직접적인 delegate 호출을 제한하고 있다.
이는 개발자의 실수를 방지하고, delegate 포함하는 클래스를 통해 delegate를 호출하므로써 외부 클래스가 이벤트를 발생시키는 것을 방지할 수 있다.
Event 문법
public event EventHandler onChange;
// 클래스의 멤버로 정의.
Event 사용법
delegate void eventDelegate();
class EventTest
{
public event eventDelegate clickEvent;
public void buttonClick() { clickEvent(); }
}
class Program
{
static void Click()
{
Console.WriteLine("클릭");
}
static void Main(string[] args)
{
EventTest eventTest = new EventTest();
eventTest.clickEvent += new eventDelegate(Program.Click);
eventTest.buttonClick();
// 아래 코드는 모두 컴파일 에러
// delegate에 할당되는 것을 제한
// eventTest.clickEvent = new eventDelegate(Program.Click);
// 직접적인 delegate 호출하는것을 제한
// eventTest.clickEvent();
}
}
3. 람다식
***람다 식(Lambda Expression)***은 람다 대수(축약 함수)의 형식을 C#에서 구현한 문법이다.
람다식 용도
익명 메서드의 간편 표기와, Expression Tree를 이용한 구문 분석 용도로 사용된다.
람다식 용법
// 익명 메서드
Thread thread1 = new Thread( delegate (object ojb) { Console.WriteLine("ThreadFunc in anonymous method called!"); });
// 람다 식
Thread thread2 = new Thread( (obj) => { Console.WriteLine("ThreadFunc in anonymous method called!"); });
// 람다 식 약식 표현 (괄호 생략)
Thread thread3 = new Thread( (obj) => Console.WriteLine("ThreadFunc in anonymous method called!") );
// 컬렉션과 람다 식
List<int> list = new List<int> { 3, 1, 4, 5, 2 };
list.ForEach((elem) => { Console.WriteLine(elem + " * 2 == " + (elem * 2)); });
// IEnumerable 타입의 확장 메서드를 이용한 조건 카운팅
int count = list.Count((elem) => elem > 3);
람다식으로 Event를 구현하기
delegate void eventDelegate();
class EventTest
{
public event eventDelegate clickEvent;
public void buttonClick() { clickEvent(); }
}
class Program
{
static void Click()
{
Console.WriteLine("클릭");
}
static void Main(string[] args)
{
EventTest eventTest = new EventTest();
eventTest.clickEvent += () => { Console.WriteLine("클릭"); };
eventTest.buttonClick();
}
}
4. 확장 메소드 조사
FirstOrDefault
조건에 맞는 데이터중 첫번째 행을 반환한다. NULL 반환에 대한 처리해주어야 한다.
List<int> months = new List<int> { };
// Setting the default value to 1 after the query.
int firstMonth1 = months.FirstOrDefault();
if (firstMonth1 == 0)
{
firstMonth1 = 1;
}
Console.WriteLine("The value of the firstMonth1 variable is {0}", firstMonth1);
Where
특정 조건을 만족하는 요소를 IEnumerable로 열거형을 반환한다.
List<int> list = new List<int> { 3, 1, 4, 5, 2 };
IEnumerable<int> enumList = list.Where((elem) => elem % 2 == 0);
Array.ForEach(enumList.ToArray(), (elem) => { Console.WriteLine(elem); });
Select
특정 요소를 IEnumerable로 열거형을 반환한다. 개별 요소를 다른 타입으로 변환할 때 사용할 수 있다.
List<int> list = new List<int> { 3, 1, 4, 5, 2 };
IEnumerable<double> enumList = list.Select((elem) => (double)elem);
Array.ForEach(enumList.ToArray(), (elem) => { Console.WriteLine(elem); });
5. LINQ 기본 문법 정리
기본적으로 사용되는 LINQ(Language-Integrated Query) 쿼리구문은 아래와 같다.
var Linq = from 범위변수 in 데이터원본
where 조건(필터)
orderby 정렬기준
select 최종결과
List<int> numbers = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
// Query #1.
IEnumerable<int> filteringQuery =
from num in numbers
where num < 3 || num > 7
select num;
// Query #2.
IEnumerable<int> orderingQuery =
from num in numbers
where num < 3 || num > 7
orderby num ascending
select num;
// Query #3.
string[] groupingQuery = { "carrots", "cabbage", "broccoli", "beans", "barley" };
IEnumerable<IGrouping<char, string>> queryFoodGroups =
from item in groupingQuery
group item by item[0];
// 메서드를 이용한 예제
List<int> numbers1 = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
List<int> numbers2 = new List<int>() { 15, 14, 11, 13, 19, 18, 16, 17, 12, 10 };
// Query #4. 리스트의 평균값
double average = numbers1.Average();
// Query #5. 두 리스트를 조합
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);
// 람다식을 사용한 예제
// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);
// 메서드와 혼합 사용
// Query #7.
int numCount1 =
(from num in numbers1
where num < 3 || num > 7
select num).Count();
// 새로운 변수를 사용하는것을 권장
IEnumerable<int> numbersQuery =
from num in numbers1
where num < 3 || num > 7
select num;
int numCount2 = numbersQuery.Count();
LINQ 쿼리구문 키워드
-
from A in B
: 전체 데이터 B에서 A필드로 검색 범위를 지정한다. 쿼리식의 시작을 알리는 역할로 하위 쿼리를 포함할 수 있다. -
where
: 검색 범위에서 데이터를 걸러내는 필터 역할을 한다. 조건을 명시하면 해당 조건을 만족하는 요소를 반환한다. 여러개의 where 절을 포함할 수 있다. -
orderby
: 걸러진 데이터를 정렬하는 역할을 한다. 기본값으로 오름차순 정렬이다. 여러개의 키를 지정할 수 있다. -
select
: 최종적으로 검색된 데이터를 추출하는 역할을 한다. 추출된 데이터 타입은select
절에서 지정한 변수의 타입으로 결정된다. 쿼리식은select
절 또는group
절로 끝나야 한다. -
group A by B into C
: A를 B조건으로 분류해 C컬렉션에 저장한다. group절은 조건에 맞는 요소를 그룹화하여IGrouping<TKey, TElement>
형태로 반환한다.into
를 사용하여 새로운 그룹을 생성하고 동작을 추가할 수 있다. (where, select) -
join
: 직접 적인 관계가 없는 두 개의 데이터를 통합하는 역할을 한다. 두 요소가 같은지 비교할 수 있는 값을 포함하여야 한다. 내부 조인, 외부 조인, 왼쪽 우선 외부 조인등 3가지 형태의 조인을 제공한다.
6. 아래 코드를 분석해주세요.
void LinqTestCode()
{
// TxPowerItems 테이블에서 data 변수 형식으로 검색한다.
// data.Path 별로 그룹화 하여 newData에 저장한다.
// 검색된 데이터는 newData.Key로 정렬한다. (newData.key는 data.Path)
// Key, Temps 형태의 익명타입 형태로 형변환하고 IEnumerable를 반환한다.
// groupTemp는 Key(data.path)와 Temps(data.path로 분류된 data의 list) 형태로 값이 채워짐.
var groupTemp = from data in TxPowerItems
group data by data.Path into newData
orderby newData.Key
select new
{
Key = newData.Key,
Temps = newData.ToList()
};
}
Expression Trees (ETs)
Expression 객체의 인스턴스 데이터의 역할을 하는 람다 식.
Expression 객체는 데이터를 트리 자료구조 형태로 담고 있다. (Expression tree)
Expression.Compile : 데이터로 담겨 있는 람다 식은 컴파일이 가능함, 이 메서드는 델리게이트를 반환하고 따라서 함수 호출이 가능함.
람다 식을 직접 구성하여 Expression tree를 구성할 수 있음.
여러가지 메서드를 제공함 (Expression 타입의 팩토리 메서드, System.Linq.Expressions에 정의)
궁극적으로 Expression의 여러 팩토리 메서드를 이용해서 C#코드를 프로그램 실행 시점에 만들어 내는것이 가능 함.
개발 보다는, 지원 툴, 컴파일러 관련 부분 개발시 유용한 기능
참고 : ExpressionTreeVisualizer (http://msdn.microsoft.com/ko-kr/library/bb397975(v=vs.90).aspx)
- Body : expression의 몸체를 리턴한다.
- Parameters : 람다식의 파라미터를 리턴한다.
- NodeType : Expression tree의 특정노드의 ExpressionType을 리턴한다. ExpressionType은 45가지의 값을 가진 열거형타입인데,
Expression tree에 속할 수 있는 모든 노드의 목록이 포함되어 있다. 예를 들면, 상수를 리턴하거나, 파라미터를 리턴한다거나, 둘 중에 뭐가 더 큰지 결정한다거나 (<,>), 두 값을 더한다거나(+) 하는 것들이 있다.
- Type : expression의 정적인 타입을 리턴한다.
LINQ
C# 목표중에서 하나는 데이터베이스의 접속 및 제어 방식이 언어 자체에 잘 녹아들도록 만드는 것, MS는 사용자가 직접적인 데이터엑세스 서비스를 추가하지 않아도 C#의 구문 자체에서 데이터를 제어하길 원함.LINQ(Language Integrated Query)가 탄생함.
언어에 통합된 쿼리표현식(Query Expressions) 함수형 언어 개념을 도입하여 쿼리 형태의 표현식을 제공하여 데이터 추출과 연산에 추상화 단계 굉장히 높힌 표현식.
LINQ를 통해 데이터를 가져오는 방법
- Select, First 같은 메서드에 람다식을 조합해서 사용
- 쿼리 구문을 통해서 가져오는 방법 (쿼리 표현식은 데이터를 어떻게 가져오는지 좀 더 명확하고 구체적으로 설명해주는 장점이 있음, SQL 쿼리와 흡사)
LINQ의 종류
- LINQ to Object
- LINQ to SQL
- LINQ to ADO.NET Data Entities
- LINQ to Data Set
- LINQ to XML
- ...
Expression tree와 LINQ의 관계
LINQ to SQL의 경우 쿼리 표현식으로 데이터를 얻을 경우 반환 타입이 IQueryable이다.
IQueryable의 정의
public interface IQueryable : IEnumerable
{
Type ElementType { get; }
Expression Expression { get; }
IQueryProvider Provider { get; }
}
멤버로 Expression타입의 프로퍼티를 가지고 있다. IQueryable의 인스턴스는 Expression tree를 가지고 있도록 설계됨. 그 Expression tree가 코드로 작성한 LINQ 쿼리문의 자료구조다.
SQL 쿼리는 실제로 데이터베이스 서버에서 실행되기 때문에, 데이터베이스가 알아들을 수 있는 형태로 변환을 해야한다. 예를 들면,
SELECT [t0].[uId], [t0].[nickname]
FROM [dbo].[Users] AS [t0]
WHERE [t0].[nickname] = @p0
이렇게 문자열 형태로 만들어서 다른 프로세스에 사용되게끔 보내게 되는데, IL 코드를 SQL 쿼리로 변환하는 것보다, Expression tree 같은 자료구조 형태가 변환하기도 쉽고, 최적화 같은 중간과정 처리도 용이하다고 함.
LINQ to Objects를 통해서 쿼리를 해보면 결과의 타입은 IEnumerable다. 왜 얘네들은 IQueryable가 아닐까?
IEnumerable의 정의
public interface IEnumerable<T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
즉, Expression 타입의 프로퍼티가 없다. 왜냐면, LINQ to Objects는 같은 프로세스내에서 처리될 객체들을 대상으로 쿼리를 하기 때문에 다른 형태로 변환될 필요가 없기 때문이다.
그렇다면, 굳이 Expression tree 같은 자료구조로 변환할 필요가 없으니 대략 아래와 같은 규칙이 성립한다.
- 코드가 같은 프로그램(또는 프로세스)내에서 실행되는 경우라면 IEnumerable
- 쿼리 표현식을 다른 프로그램(또는 프로세스)에서 처리하기 위해서 문자열 형태로 변환해야 한다면 Expression tree를 포함하는 IQueryable를 사용
LINQ
https://msdn.microsoft.com/ko-kr/library/bb397933.aspx
https://msdn.microsoft.com/ko-kr/library/bb383978.aspx
http://www.dotnetperls.com/linq