C# 기초 정리: 람다식, LINQ, 애트리뷰트, Dynamic 형식

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 &lt; 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 &lt; 175
                        orderby profile.Height ascending // orderby 기본값은 오름차순 정렬
                        select profile; // 반환 형식: IEnumerable&lt;Profile&gt;
 
        var profiles2 = from profile in arrProfile
                         where profile.Height &lt; 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 &lt; 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();
        }
    }
}

 

댓글 남기기