Table of Content
람다식(Lambda Expression)
- 함수를 좀 더 간결한 코드로 묘사하기 위한 방법
- 익명 메소드를 만들기 위해 사용(람다식으로 만든 익명 메소드는 무명함수(Anonymous Function)라고 부름)
- =>: 입력 연산자. =>를 중심으로 왼쪽엔 매개변수, 오른쪽엔 식이 위치
- 기본 형식: (매개변수_목록) => 식
class Program { delegate int Calc(int a, int b); // 익명 메소드를 만들기 위한 델리게이트 public static void Main(string[] args) { Calc c = (a, b) => a + b; // 익명 메소드를 람다식으로 구현 Console.WriteLine("{0} + {1} = {2}", 1, 2, c(1, 2)); } }
문 형식의 람다식(Statement Lambda)
- 위의 예제는 식(Expression) 형식의 람다식
- 문 형식의 람다식은 반환형식이 없는 무명함수 선언이 가능
- 기본 형식: (매개변수 목록) => { 코드1, 코드2, 코드3, … };
class Program { delegate void DoSomething(); public static void Main(string[] args) { /* 문 형식의 람다식에서 * 전달할 매개변수가 없으면 비워두면 됨 */ DoSomething DoIt = () => { Console.WriteLine("C#"); Console.WriteLine("짱 재밌자너~"); }; DoIt(); } }
Func, Action 델리게이트
- 단 하나의 무명함수를 만든다해도 델리게이트를 선언해야 하는 번거로움이 있으나 Func, Action 델리게이트를 사용하면 이 문제가 해결됨
- Func 델리게이트: 결과를 반환하는 메소드 참조
- 모든 Func 델리게이트의 반환 형식은 형식 매개변수 중 가장 마지막의 것을 따름
- Action 델리게이트: 결과를 반환하지 않는 메소드 참조
- Func, Action 델리게이트는 모두 17개 버전의 델리게이트가 존재
- ref나 out 한정자로 수식된 매개변수를 사용하는 경우가 아니라면 별도의 델리게이트를 만들어 쓸 필요가 거의 없음
class Program { public static void Main(string[] args) { /* 입력 매개변수가 없는 Func 델리게이트 */ Func<int> func1 = () => 10; // 무조건 10 반환 Console.WriteLine(func1()); /* 입력 매개변수가 하나인 Func 델리게이트 */ Func<int, int> func2 = (x) => x * 2; Console.WriteLine(func2(2)); /* 입력 매개변수가 없는 Action 델리게이트 */ Action act1 = () => Console.WriteLine("어떤 매개변수도 사용하지 않음"); act1(); /* 입력 매개변수가 2개인 Action<T1, T2> 델리게이트 */ Action<double, double> act2 = (x, y) => { double d = x + y; Console.WriteLine(d); }; act2(3.14, 1.414); } }
LINQ(Language Integrated Query)
- C#에 통합된 데이터 질의(Query) 기능
- LINQ를 사용하면 데이터 작업 코드가 간결해짐
- 기본적인 질의
- From: 어떤 데이터 집합에서 찾을 것인가?
- Where: 어떤 값의 데이터를 찾을 것인가?
- Select: 어떤 항목을 추출할 것인가?
class Profile { public string Name { get; set; } public int Height { get; set; } } class Program { public static void Main(string[] args) { Profile[] arrProfile = { new Profile(){Name="김씨샵", Height=188}, new Profile(){Name="김씨", Height=172}, new Profile(){Name="김씨플", Height=168}, new Profile(){Name="김자바", Height=192}, new Profile(){Name="김파썬", Height=173} }; /* 키 175 미만인 객체를 골라 담는 LINQ */ var profiles = from profile in arrProfile // arrProfile 데이터로부터 where profile.Height < 175 // Height 175 미만인 객체를 골라 orderby profile.Height // Height 기준으로 정렬하여 select profile; // profile 객체 추출 foreach (var profile in profiles) Console.WriteLine("{0}: {1}", profile.Name, profile.Height); } }
LINQ의 from절, where절, select절
- from절
- 기본 형식: from 범위변수 in 데이터원본
- 모든 LINQ의 쿼리 식(Query Expression)은 반드시 from절로 시작
- from절에 데이터 원본(Data Source)과 범위 변수(Range Variable)를 지정
- from절에 오는 데이터 원본은 IEnumerable를 상속하는 형식이어야 함
- where절: 조건에 부합하는 데이터를 걸러내는 필터
- select절: 최종 결과를 추출하는 쿼리식의 마침표
- LINQ의 질의 결과는 IEnumerable<T>이며 T는 select문에 의해 결정됨
- 무명 형식을 이용하여 새로운 형식 생성도 가능
class Profile { public string Name { get; set; } public int Height { get; set; } } class Program { public static void Main(string[] args) { Profile[] arrProfile = { new Profile(){Name="김씨샵", Height=188}, new Profile(){Name="김씨", Height=172}, new Profile(){Name="김씨플", Height=168}, new Profile(){Name="김자바", Height=192}, new Profile(){Name="김파썬", Height=173} }; /* 키 175 미만인 객체를 골라 담는 LINQ */ var profiles = from profile in arrProfile where profile.Height < 175 orderby profile.Height ascending // orderby 기본값은 오름차순 정렬 select profile; // 반환 형식: IEnumerable<Profile> var profiles2 = from profile in arrProfile where profile.Height < 175 orderby profile.Height descending select new { Name = profile.Name, InchHeight = profile.Height * 0.393 }; // 무명 형식을 이용한 새로운 형식 생성 foreach (var profile in profiles) Console.WriteLine("{0}: {1}", profile.Name, profile.Height); } }
LINQ로 여러 개의 데이터 원본 질의
- 여러 개의 데이터에 접근하려면 from문을 중첩하여 사용
class Class { public string Name { get; set; } public int[] Score { get; set; } } class Program { public static void Main(string[] args) { Class[] arrClass = { new Class(){Name="씨샵반", Score=new int[] { 95, 85, 80, 75, 95 } }, new Class(){Name="자바반", Score=new int[] { 60, 45, 50, 30, 80 } }, new Class(){Name="C++반", Score=new int[] { 90, 50, 10, 80, 90} } }; /* 각 학급의 50점 미만 학생들의 점수를 골라담는 LINQ */ var classes = from c in arrClass from s in c.Score where s < 50 select new { c.Name, Lowest = s }; foreach (var v in classes) Console.WriteLine("{0}: {1}", v.Name, v.Lowest); } }
LINQ의 group by
- 분류 기준에 따라 데이터를 그룹화
- 기본 형식: group 범위변수 by 분류기준 into 그룹변수
- 그룹변수는 IGrouping<T> 형식
class Profile { public string Name { get; set; } public int Height { get; set; } } class Program { public static void Main(string[] args) { Profile[] arrProfile = { new Profile(){Name="김씨샵", Height=188}, new Profile(){Name="김씨", Height=172}, new Profile(){Name="김씨플", Height=168}, new Profile(){Name="김자바", Height=192}, new Profile(){Name="김파썬", Height=173} }; var listProfile = from profile in arrProfile group profile by profile.Height < 175 into g select new { GroupKey = g.Key, Profiles = g }; /* 위 LINQ를 통해 listProfile 객체는 다음과 같은 구조를 가짐 */ /* [Height < 175] * bool GroupKey (값: true) * IGrouping<T> Profiles */ /* [Height >= 175] * bool GroupKey (값: false) * IGrouping<T> Profiles */ foreach (var Group in listProfile) { Console.WriteLine("# 175 미만? {0}", Group.GroupKey); foreach (var v in Group.Profiles) Console.WriteLine("{0}: {1}", v.Name, v.Height); Console.WriteLine(); } } }
애트리뷰트(Attribute)
- 코드에 대한 부가 정보를 기록하고 읽을 수 있는 기능
- 주석은 사람이 읽고, 애트리뷰트는 컴퓨터가 읽음
- 기본 형식: [애트리뷰트_이름(애트리뷰트_매개변수)]
- Obsolete 애트리뷰트: 코드에 대한 메시지(경고)를 알림
- 호출자 정보(Caller Information) 애트리뷰트: C/C++에서 제공하는 매크로와 비슷한 기능
- 호출자 정보는 매개변수에 사용됨
- [CallerMemberName]: 현재 메소드를 호출한 메소드 또는 프로퍼티의 이름
- [CallerFilePath]: 현재 메소드가 호출된 소스파일 경로
- [CallerLineNumber]: 현재 메소드가 호출된 소스파일 내의 행 번호
using System.Runtime.CompilerServices; namespace MyFirstConsoleApp { class Program { /* Obsolete 애트리뷰트 */ [Obsolete("OldMethod()는 폐기되었습니다. NewMethod()를 사용하세요.")] public static void OldMethod() { Console.WriteLine("쓰지 마세요."); } public static void NewMethod() { Console.WriteLine("쓰세요."); } /* 호출자 정보(Caller Information) 애트리뷰트 */ public static void WriteLine(string message, [CallerFilePath] string file="", [CallerLineNumber] int line=0, [CallerMemberName] string member = "") { Console.WriteLine("파일 경로: {0}", file); Console.WriteLine("호출코드 라인: {0}", line); Console.WriteLine("호출 메소드: {0}", member); Console.WriteLine("메시지: {0}", message); } public static void Main(string[] args) { OldMethod(); // 경고 출력되지만 컴파일은 됨 WriteLine("C# 프로그래밍"); } } }
사용자 정의 애트리뷰트
- System.Attribute 클래스를 상속받아 생성
- 애트리뷰트를 2개 이상 사용하면 에러 발생
- System.AttributeUsage 애트리뷰트를 사용하면 해결됨
/* System.AttributeUsage 애트리뷰트의 첫번째 매개변수: 선언한 애트리뷰트의 설명 대상(Attribute Target) * All, Interface, Class, Struct, Constructor, Delegate, Enum, Event 등이 올 수 있으며 * 논리합 연산자(|)로 결합 가능 * * AllowMultiple: 애트리뷰트 2개 이상 사용 가능 여부 */ [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true)] class History : System.Attribute { public string Programmer; public double Version; public string Changes; } [History(Programmer = "김씨샵", Version = 0.1, Changes = "First Release")] [History(Programmer = "김씨샵", Version = 0.2, Changes = "Bug Fix")] class MyClass1 { }
Dynamic 형식
- 형식 검사가 실행할 때 이루어지는 데이터 형식
- 장점: 파생 클래스 수정 시 유연하게 해결할 수 있도록 함
- 상속 관계를 무시하므로 프로그램 동작에 관여하는 부분만 손대면 됨
- 단점: 비주얼 스튜디오의 리펙토링 기능을 이용할 수 없음
- 특정 메소드 명을 수정하려면 모든 코드를 탐색하여 수정해야 함
- 오리 타이핑을 사용할지 말지는 프로그래머 취향에 따름
/* 걷고 헤엄치고 꽥꽥거리면? 오리 */ class Duck { public void Walk() { Console.WriteLine(this.GetType() + ".Walk"); } public void Swim() { Console.WriteLine(this.GetType() + ".Swim"); } public void Quack() { Console.WriteLine(this.GetType() + ".Quack"); } } class Mallard : Duck { } /* 오리 타이핑(Duck Typing): 오리로 인정받으려면 걷고 헤엄치고 꽥꽥거리면 됨 */ class Robot { public void Walk() { Console.WriteLine(this.GetType() + ".Walk"); } public void Swim() { Console.WriteLine(this.GetType() + ".Swim"); } public void Quack() { Console.WriteLine(this.GetType() + ".Quack"); } } class Program { public static void Main(string[] args) { dynamic obj = new dynamic[] { new Duck(), new Mallard(), new Robot() }; foreach(dynamic d in obj) { Console.WriteLine(d.GetType()); d.Walk(); d.Swim(); d.Quack(); Console.WriteLine(); } } }