2016년 2월 25일 목요일

C# 프로그래밍 가이드 중 일부 발췌 - 상호 운용성(Interoperability), LINQ(Language-Integrated Query)

개인적인 copy & paste 정리입니다. 글을 보시기 보다는 아래 링크에 방문하세요.
https://msdn.microsoft.com/ko-kr/library/67ef8sbd.aspx

상호 운용성(Interoperability)

상호 운용성을 활용하면 기존의 비관리 코드를 유지하고 활용할 수 있습니다. CLR(공용 언어 런타임)의 제어 하에 실행되는 코드를 관리 코드라고 하며 CLR 외부에서 실행되는 코드를 비관리 코드라고 합니다. 비관리 코드의 예로는 COM, COM+, C++ 구성 요소, ActiveX 구성 요소 및 Microsoft Win32 API가 있습니다.
.NET Framework에서는 플랫폼 호출 서비스, System.Runtime.InteropServices 네임스페이스, C++ 상호 운용성 및 COM 상호 운용성(COM interop)을 통해 비관리 코드와의 상호 운용성을 제공합니다.

플랫폼 호출은 DLL(동적 연결 라이브러리)에 구현된 관리되지 않는 함수(예: Microsoft Win32 API의 함수)를 관리 코드에서 호출할 수 있게 해 주는 서비스입니다. 플랫폼 호출 서비스는 내보낸 함수를 찾아 호출한 후 필요에 따라 해당 인수(정수, 문자열, 배열, 구조 등)를 상호 운용 경계에서 마샬링합니다.

LINQ(Language-Integrated Query)

https://msdn.microsoft.com/ko-KR/library/bb397676.aspx

LINQ(Language-Integrated Query)는 C# 언어(또한 Visual Basic 및 잠재적으로 다른 모든 .NET 언어)에 대한 쿼리 기능의 직접 통합을 기반으로 하는 기술 집합의 이름입니다. LINQ를 사용하면 쿼리가 클래스, 메서드, 이벤트와 같은 고급 언어 구문이 됩니다.
쿼리를 작성하는 개발자에게 가장 많이 표시되는 LINQ의 "통합 언어" 부분은 쿼리 식입니다. 쿼리 식은 C# 3.0에서 소개된 선언적 쿼리 구문으로 작성됩니다. 쿼리 구문을 사용하면 최소한의 코드로 데이터 소스에 대해 복잡한 필터링, 정렬 및 그룹화 작업을 수행할 수 있습니다. 동일한 기본 쿼리 식 패턴을 사용하여 SQL 데이터베이스, ADO.NET 데이터 집합, .XML 문서 및 스트림, .NET 컬렉션의 데이터를 쿼리하고 변환합니다.
class LINQQueryExpressions
{
    static void Main()
    {

        // Specify the data source.
        int[] scores = new int[] { 97, 92, 81, 60 };

        // Define the query expression.
        IEnumerable<int> scoreQuery =
            from score in scores
            where score > 80
            select score;

        // Execute the query.
        foreach (int i in scoreQuery)
        {
            Console.Write(i + " ");
        }            
    }
}
// Output: 97 92 81
  • 쿼리 식은 LINQ 사용 데이터 소스의 데이터를 쿼리 및 변환하는 데 사용할 수 있습니다. 예를 들어 단일 쿼리는 SQL 데이터베이스에서 데이터를 검색하고 XML 스트림을 출력으로 생성할 수 있습니다.
  • 쿼리 식은 많은 익숙한 C# 언어 구문을 사용하므로 쉽게 익힐 수 있습니다. 자세한 내용은 Getting Started with LINQ in C#을 참조하십시오.
  • 컴파일러가 형식을 유추할 수 있으므로 대부분 명시적으로 형식을 제공할 필요는 없지만 쿼리 식의 변수는 모두 강력한 형식입니다. 자세한 내용은 Type Relationships in LINQ Query Operations (C#)를 참조하십시오.
  • 쿼리는 foreach 문에서 쿼리 변수를 반복할 때까지 실행되지 않습니다. 자세한 내용은 Introduction to LINQ Queries (C#)를 참조하십시오.
  • 컴파일 타임에 쿼리 식은 C# 사양에 설정된 규칙에 따라 표준 쿼리 연산자 메서드로 변환됩니다. 쿼리 구문을 사용하여 표현할 수 있는 모든 쿼리는 메서드 구문으로도 표현할 수 있습니다. 그러나 대체로 쿼리 구문이 더 읽기 쉽고 간결합니다. 자세한 내용은 C# 언어 사양  Standard Query Operators Overview를 참조하십시오.
  • 일반적으로 LINQ 쿼리를 작성할 때는 가능한 한 쿼리 구문을 사용하고 필요한 경우에만 메서드 구문을 사용하는 것이 좋습니다. 두 형태 간에 의미 체계 또는 성능상의 차이는 없습니다. 쿼리 식이 메서드 식으로 작성된 동등한 식보다 읽기 쉽습니다.
  • Count<TSource> 또는 Max 같은 일부 쿼리 작업에는 동등한 쿼리 식 절이 없으므로 메서드 호출로 표현해야 합니다.다양한 방법으로 메서드 구문과 쿼리 구문을 결합할 수 있습니다. 자세한 내용은 Query Syntax and Method Syntax in LINQ (C#)를 참조하십시오.
  • 쿼리가 적용되는 형식에 따라 쿼리 식을 식 트리나 대리자로 컴파일할 수 있습니다. IEnumerable<T> 쿼리는 대리자로 컴파일되고 IQueryable  IQueryable<T> 쿼리는 식 트리로 컴파일됩니다. 자세한 내용은 식 트리(C# 및 Visual Basic)를 참조하십시오.
https://msdn.microsoft.com/ko-KR/library/bb384065.aspx

응용 프로그램의 관점에서 보면 원래 소스 데이터의 특정 형식과 구조는 중요하지 않습니다.  응용 프로그램에서는 항상 소스 데이터를 IEnumerable<T> 또는 IQueryable<T> 컬렉션으로 간주합니다.  LINQ to XML에서 소스 데이터를IEnumerable<XElement>로 표시할 수 있습니다  LINQ to DataSet에서는 IEnumerable<DataRow>입니다.  LINQ to SQL에서는 SQL 테이블의 데이터를 나타내기 위해 정의한 사용자 지정 개체에 상관없이 IEnumerable 또는 IQueryable입니다. 

int highScoreCount =
    (from score in scores
     where score > 80
     select score)
     .Count();
앞의 예제에서는 Count 메서드에 대한 호출 앞에 있는 쿼리 식을 괄호로 묶었습니다.  또한 구체적인 결과를 저장할 새 변수를 사용하여 이 식을 표현할 수도 있습니다.  이 기술을 사용하면 쿼리를 저장하는 변수와 결과를 저장하는 쿼리가 별도로 유지되기 때문에 읽기가 더 쉬워집니다. 

쿼리 식은 쿼리 구문으로 표현되는 쿼리입니다.  쿼리 식은 고급 언어 구문입니다.  다른 식과 똑같으며 C# 식을 사용할 수 있는 모든 컨텍스트에서 사용할 수 있습니다.  쿼리 식은 SQL 또는 XQuery와 유사한 선언적 구문으로 작성된 절의 집합으로 구성됩니다.  각 절은 하나 이상의 C# 식을 포함하고 이러한 식 자체도 쿼리 식이거나 쿼리 식을 포함할 수 있습니다.  
쿼리 식은 from 절로 시작하여 select 또는 group 절로 끝나야 합니다.  첫 번째 from 절과 마지막 select 또는 group 절 사이에는 where, orderby, join, let 및 추가 from 절과 같은 선택적 절 중 하나 이상이 포함될 수 있습니다.  또한 into 키워드를 사용하여 join 또는 group 절의 결과를 같은 쿼리 식에 있는 다른 쿼리 절의 소스로 사용할 수 있습니다.
반면 다음 두 예제에서는 각각 쿼리로 초기화되어 있지만 쿼리 변수는 아닌 변수를 보여 줍니다.  이들은 결과를 저장하므로 쿼리 변수가 아닙니다:  
int highestScore =
    (from score in scores
     select score)
    .Max();

// or split the expression
IEnumerable<int> scoreQuery =
    from score in scores
    select score;

int highScore = scoreQuery.Max();

List<City> largeCitiesList =
    (from country in countries
     from city in country.Cities
     where city.Population > 10000
     select city)
       .ToList();

// or split the expression
IEnumerable<City> largeCitiesQuery =
    from country in countries
    from city in country.Cities
    where city.Population > 10000
    select city;

List<City> largeCitiesList2 = largeCitiesQuery.ToList();

일반적으로 이 문서에서는 쿼리 변수와 select 절 사이의 형식 관계를 보여 주기 위해 쿼리 변수의 명시적 형식을 제공합니다.  그러나 var 키워드를 사용하여 컴파일러에서 컴파일할 때 쿼리 변수 또는 다른 지역 변수의 형식을 유추하도록 할 수도 있습니다.  예를 들어 이 항목의 앞부분에 있는 쿼리 예제를 다음과 같이 암시적 형식화를 사용하여 표현할 수도 있습니다:  
// Use of var is optional here and in all queries.
// queryCities is an IEnumerable<City> just as 
// when it is explicitly typed.
var queryCities =
    from city in cities
    where city.Population > 100000
    select city;

쿼리 식은 from 절로 시작해야 합니다.  이 절은 범위 변수와 함께 데이터 소스를 지정합니다.  범위 변수는 소스 시퀀스가 이동될 때 소스 시퀀스의 각 연속 요소를 나타냅니다.  범위 변수는 데이터 소스에 있는 요소의 형식을 기반으로 하는 강력한 형식입니다.  다음 예제에서 countries Country 개체의 배열이므로 범위 변수도 Country로 형식화됩니다.  범위 변수는 강력한 형식이므로 도트 연산자를 사용하여 형식의 사용 가능한 멤버에 액세스할 수 있습니다.  
IEnumerable<Country> countryAreaQuery =
    from country in countries
    where country.Area > 500000 //sq km
    select country;
범위 변수는 쿼리가 세미콜론이나 continuation 절로 종료될 때까지 범위 내에 있습니다.
쿼리 식에는 여러 from 절이 포함될 수 있습니다.  소스 시퀀스의 각 요소가 컬렉션이거나 컬렉션을 포함하는 경우 추가from 절을 사용합니다.  예를 들어 Country 개체의 컬렉션이 있고 이들 각각에 Cities라는 City 개체의 컬렉션이 포함되어 있다고 가정합니다.   Country에서 City 개체를 쿼리하려면 다음과 같이 두 개의 from 절을 사용합니다.  
IEnumerable<City> cityQuery =
    from country in countries
    from city in country.Cities
    where city.Population > 10000
    select city;

https://msdn.microsoft.com/ko-KR/library/bb383978.aspx

쿼리 식은 from 절로 시작해야 합니다. 또한 쿼리 식은 마찬가지로 from 절로 시작하는 하위 쿼리를 포함할 수 있습니다. from절은 다음을 지정합니다.
  • 쿼리 또는 하위 쿼리가 실행되는 데이터 소스
  • 소스 시퀀스의 각 요소를 나타내는 지역 범위 변수
범위 변수와 데이터 소스는 둘 다 강력한 형식입니다. from 절에서 참조되는 데이터 소스는 IEnumerable 또는 IEnumerable<T>형식이나 IQueryable<T>과 같은 파생 형식이어야 합니다.
다음 예제에서 numbers는 데이터 소스이고 num은 범위 변수입니다. var 키워드가 사용되어도 두 변수는 모두 강력한 형식입니다.
class LowNums
{
    static void Main()
    {   
        // A simple data source.
        int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

        // Create the query.
        // lowNums is an IEnumerable<int>
        var lowNums = from num in numbers
            where num < 5
            select num;

        // Execute the query.
        foreach (int i in lowNums)
        {
            Console.Write(i + " ");
        }
    }        
}
// Output: 4 1 3 2 0

쿼리 식은 select 절 또는 group 절로 끝나야 합니다.
https://msdn.microsoft.com/ko-KR/library/bb384063.aspx

group 절은 그룹의 키 값과 일치하는 하나 이상의 항목을 포함하는 IGrouping<TKey, TElement> 개체 시퀀스를 반환합니다.예를 들어 각 문자열의 첫 글자에 따라 문자열 시퀀스를 그룹화할 수 있습니다.이 경우 첫 글자가 키가 되고 키의 형식은 char이며 각IGrouping<TKey, TElement> 개체의 Key 속성에 저장됩니다.컴파일러가 키의 형식을 유추합니다.

다음 예제에서는 키에 부울 값을 사용하여 결과를 두 개의 그룹으로 나누는 방법을 보여 줍니다. group 절의 하위 식에서 값이 생성됩니다.
class GroupSample1
{
    // The element type of the data source.
    public class Student
    {
        public string First { get; set; }
        public string Last { get; set; }
        public int ID { get; set; }
        public List<int> Scores;
    }

    public static List<Student> GetStudents()
    {
        // Use a collection initializer to create the data source. Note that each element
        //  in the list contains an inner sequence of scores.
        List<Student> students = new List<Student>
        {
           new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81, 60}},
           new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
           new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}},
           new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}},
           new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}} 
        };

        return students;

    }

    static void Main()
    {
        // Obtain the data source.
        List<Student> students = GetStudents();

        // Group by true or false.
        // Query variable is an IEnumerable<IGrouping<bool, Student>>
        var booleanGroupQuery =
            from student in students
            group student by student.Scores.Average() >= 80; //pass or fail!

        // Execute the query and access items in each group
        foreach (var studentGroup in booleanGroupQuery)
        {
            Console.WriteLine(studentGroup.Key == true ? "High averages" : "Low averages");
            foreach (var student in studentGroup)
            {
                Console.WriteLine("   {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
            }
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
  Low averages
   Omelchenko, Svetlana:77.5
   O'Donnell, Claire:72.25
   Garcia, Cesar:75.5
  High averages
   Mortensen, Sven:93.5
   Garcia, Debra:88.25
*/

select 절을 사용하면 다른 모든 형식의 시퀀스를 생성할 수 있습니다.  간단한 select 문에서는 데이터 소스에 포함된 개체와 동일한 형식의 개체 시퀀스를 생성합니다.  이 예제에서 데이터 소스에는 Country 개체가 포함되어 있습니다.  orderby 절은 요소를 새로운 순서로 정렬하고 select 절은 다시 정렬된 Country 개체의 시퀀스를 생성합니다.  
IEnumerable<Country> sortedQuery =
    from country in countries
    orderby country.Area
    select country;
select 절을 사용하여 소스 데이터를 새로운 형식의 시퀀스로 변환할 수도 있습니다.  이러한 변환을 프로젝션이라고도 합니다.  다음 예제에서 select 절은 원래 요소에 있는 필드의 하위 집합만 포함하는 익명 형식의 시퀀스를 프로젝션합니다.  새 개체는 개체 이니셜라이저를 사용하여 초기화됩니다.  
// Here var is required because the query
// produces an anonymous type.
var queryNameAndPop =
    from country in countries
    select new { Name = country.Name, Pop = country.Population };
select 절을 사용하여 소스 데이터를 변환하는 모든 방법에 대한 자세한 내용은 select 절(C# 참조)을 참조하십시오.
컨텍스트 키워드 into group, join 또는 select 절의 결과를 새 식별자에 저장하는 임시 식별자를 만드는 데 사용할 수 있습니다.이 식별자 자체는 추가 쿼리 명령에 대한 생성기가 될 수 있습니다. group 또는 select 절에서 사용되는 경우 새 식별자의 사용을연속이라고도 합니다.

다음 예제에서는 IGrouping의 유추된 형식을 포함하는 임시 식별자 fruitGroup을 활성화하도록 into 키워드를 사용하는 방법을 보여 줍니다. 식별자를 사용하여 각 그룹에서 Count 메서드를 호출하고 두 개 이상의 단어를 포함하는 해당 그룹만 선택할 수 있습니다.
class IntoSample1
{
    static void Main()
    {

        // Create a data source.
        string[] words = { "apples", "blueberries", "oranges", "bananas", "apricots"};

        // Create the query.
        var wordGroups1 =
            from w in words
            group w by w[0] into fruitGroup
            where fruitGroup.Count() >= 2
            select new { FirstLetter = fruitGroup.Key, Words = fruitGroup.Count() };

        // Execute the query. Note that we only iterate over the groups, 
        // not the items in each group
        foreach (var item in wordGroups1)
        {
            Console.WriteLine(" {0} has {1} elements.", item.FirstLetter, item.Words);
        }

        // Keep the console window open in debug mode
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
   a has 2 elements.
   b has 2 elements.
*/

join 절을 사용하면 한 데이터 소스의 요소와 다른 데이터 소스의 열을 각 요소에서 지정한 키 사이의 같음 비교를 기반으로 연결 또는 결합할 수 있습니다.  LINQ에서 조인 작업은 해당 요소의 형식이 다른 개체의 시퀀스에 대해 수행합니다.  두 시퀀스를 조인한 후에는 select 또는 group 문을 사용하여 출력 시퀀스에 저장할 요소를 지정해야 합니다.  또한 익명 형식을 사용하여 연관된 각 요소 집합의 속성을 출력 시퀀스의 새 형식으로 결합할 수 있습니다.  다음 예제에서는 해당 Category 속성이 categories 문자열 변수의 범주 중 하나와 일치하는 prod 개체를 연결합니다.   Categorycategories의 어떤 문자열과도 일치하지 않는 제품은 필터링됩니다.   select 문에서는 해당 속성을 cat  prod 모두에서 가져오는 새 형식을 프로젝션합니다.  
var categoryQuery =
    from cat in categories
    join prod in products on cat equals prod.Category
    select new { Category = cat, Name = prod.Name };
또한 into 키워드를 사용하여 join 작업의 결과를 임시 변수에 저장하는 방법을 통해 그룹 조인을 수행할 수도 있습니다.  자세한 내용은 join 절(C# 참조)을 참조하십시오.  

let 절을 사용하면 메서드와 같은 식의 결과를 새 범위 변수에 저장할 수 있습니다.  다음 예제에서 범위 변수 firstName Split에서 반환된 문자열 배열의 첫 번째 요소를 저장합니다.  
string[] names = { "Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia" };
IEnumerable<string> queryFirstNames =
    from name in names
    let firstName = name.Split(new char[] { ' ' })[0]
    select firstName;

foreach (string s in queryFirstNames)
    Console.Write(s + " ");
//Output: Svetlana Claire Sven Cesar
자세한 내용은 let 절(C# 참조)을 참조하십시오.
https://msdn.microsoft.com/ko-kr/library/bb397678.aspx

C#에서 LINQ 쿼리를 작성할 수 있는 세 가지 방법을 보여 줍니다.
  1. 쿼리 구문 사용
  2. 메서드 구문 사용
  3. 쿼리 구문과 메서드 구문의 조합 사용
다음 예제에서는 앞에서 설명한 각 방법을 사용하여 몇 가지 간단한 LINQ 쿼리를 보여 줍니다. 일반적으로 규칙은 가능하면 (1)을 사용하고 필요에 따라 (2)와 (3)을 사용하는 것입니다.
System_CAPS_note참고
이러한 쿼리는 간단한 메모리 내 컬렉션을 수행합니다. 그러나 기본 구문은 LINQ to SQL 및 LINQ to XML에서 사용된 구문과 동일합니다.

대부분의 쿼리를 작성하는 데 권장되는 방법은 쿼리 구문을 사용하여 쿼리 식을 만드는 것입니다. 다음 예제에서는 세 개의 쿼리 식을 보여 줍니다.

일부 쿼리 작업은 메서드 호출로 표시되어야 합니다. 가장 일반적인 이러한 메서드는 Sum, Max, Min, Average 등과 같은 singleton 숫자 값을 반환하는 메서드입니다. 이러한 메서드는 단일 값만 나타내며 추가 쿼리 작업에 대해 소스로 사용할 수 없으므로 항상 모든 쿼리에서 마지막으로 호출되어야 합니다. 다음 예제에서는 쿼리 식에서 메서드를 호출하는 방법을 보여 줍니다.

예제

메서드에 매개 변수가 있으면 매개 변수가 다음 예제와 같이 람다 식 형식으로 제공됩니다.
// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);
앞의 쿼리에서 쿼리 #4만 즉시 실행됩니다. 이는 쿼리가 단일 값을 반환하고 제네릭 IEnumerable<T> 컬렉션이 아니기 때문입니다. 이 메서드 자체는 값을 계산하기 위해 foreach를 사용해야 합니다.
앞의 각 쿼리는 다음 예제와 같이 var를 통한 암시적 형식 지정을 사용하여 작성할 수 있습니다.
// var is used for convenience in these queries
var average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);

이 예제에서는 쿼리 절의 결과로 메서드 구문을 사용하는 방법을 보여 줍니다. 쿼리 식을 괄호로 묶은 다음 도트 연산자를 적용하고 메서드를 호출합니다. 다음 예제에서는 쿼리 #7에서 값이 3과 7 사이인 숫자의 수를 반환합니다. 그러나 일반적으로 두 번째 변수를 사용하여 메서드 호출의 결과를 저장하는 것이 좋습니다. 이 경우 쿼리가 쿼리 결과와 혼동될 가능성은 낮습니다.
// Query #7.

// Using a query expression with method syntax
int numCount1 =
    (from num in numbers1
     where num < 3 || num > 7
     select num).Count();

// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
    from num in numbers1
    where num < 3 || num > 7
    select num;

int numCount2 = numbersQuery.Count();
쿼리 #7에서는 컬렉션이 아닌 단일 값을 반환하기 때문에 쿼리가 즉시 실행됩니다.
https://msdn.microsoft.com/ko-kr/library/bb882532.aspx

모든 쿼리는 IEnumerable 또는 IEnumerable<T> 형식이거나 IQueryable<T>과 같은 파생 형식이어야 합니다. 따라서 쿼리를 반환하는 메서드의 모든 반환 값 또는 out 매개 변수도 이와 같은 형식이어야 합니다. 메서드에서 쿼리를 List<T> 또는 Array 형식으로 구체화할 경우 해당 메서드는 쿼리 자체가 아니라 쿼리 결과를 반환하는 것으로 간주됩니다. 메서드에서 반환되는 쿼리 변수는 구성 또는 수정이 가능합니다.

https://msdn.microsoft.com/ko-kr/library/bb513810.aspx
쿼리는 기본적으로 데이터를 검색하고 구성하는 방법에 대한 명령 집합입니다. 쿼리를 실행하려면 해당 GetEnumerator 메서드를 호출해야 합니다. 이 호출은 foreach 루프를 사용하여 요소를 반복하는 경우에 요청됩니다. 쿼리를 평가 하 고 실행 하지 않고 해당 결과 저장 하는 foreach 루프, 다음 방법 중 하나에서 쿼리 변수를 호출 하기만 하면:
쿼리 결과를 저장하는 경우 다음의 예제와 같이 새 변수에 반환된 컬렉션 개체를 할당하는 것이 좋습니다.

예제

class StoreQueryResults
{
    static List<int> numbers = new List<int>() { 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 };
    static void Main()
    {

        IEnumerable<int> queryFactorsOfFour =
            from num in numbers
            where num % 4 == 0
            select num;

        // Store the results in a new variable
        // without executing a foreach loop.
        List<int> factorsofFourList = queryFactorsOfFour.ToList();

        // Iterate the list just to prove it holds data.
        foreach (int n in factorsofFourList)
        {
            Console.WriteLine(n);
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key");
        Console.ReadKey();
    }
}

https://msdn.microsoft.com/ko-kr/library/bb545971.aspx
그룹화는 LINQ의 가장 강력한 기능 중 하나입니다. 다음 예제에서는 다양한 방식으로 데이터를 그룹화하는 방법을 보여 줍니다.
  • 단일 속성 사용
  • 문자열 속성의 첫 글자 사용
  • 계산된 숫자 범위 사용
  • 부울 조건자 또는 기타 식 사용
  • 복합 키 사용
https://msdn.microsoft.com/ko-kr/library/bb545974.aspx
다음 예제에서는 LINQ 쿼리 식에서 중첩 그룹을 만드는 방법을 보여 줍니다. 학년이나 등급에 따라 만들어진 각 그룹은 개인의 이름에 기초한 그룹으로 더 세분화됩니다.

예제

public void QueryNestedGroups()
{
    var queryNestedGroups =
        from student in students
        group student by student.Year into newGroup1
        from newGroup2 in
            (from student in newGroup1
             group student by student.LastName)
        group newGroup2 by newGroup1.Key;

    // Three nested foreach loops are required to iterate 
    // over all elements of a grouped group. Hover the mouse 
    // cursor over the iteration variables to see their actual type.
    foreach (var outerGroup in queryNestedGroups)
    {
        Console.WriteLine("DataClass.Student Level = {0}", outerGroup.Key);
        foreach (var innerGroup in outerGroup)
        {
            Console.WriteLine("\tNames that begin with: {0}", innerGroup.Key);
            foreach (var innerGroupElement in innerGroup)
            {
                Console.WriteLine("\t\t{0} {1}", innerGroupElement.LastName, innerGroupElement.FirstName);
            }
        }
    }
}
/*
 Output:
DataClass.Student Level = SecondYear
        Names that begin with: Adams
                Adams Terry
        Names that begin with: Garcia
                Garcia Hugo
        Names that begin with: Omelchenko
                Omelchenko Svetlana
DataClass.Student Level = ThirdYear
        Names that begin with: Fakhouri
                Fakhouri Fadi
        Names that begin with: Garcia
                Garcia Debra
        Names that begin with: Tucker
                Tucker Lance
DataClass.Student Level = FirstYear
        Names that begin with: Feng
                Feng Hanying
        Names that begin with: Mortensen
                Mortensen Sven
        Names that begin with: Tucker
                Tucker Michael
DataClass.Student Level = FourthYear
        Names that begin with: Garcia
                Garcia Cesar
        Names that begin with: O'Donnell
                O'Donnell Claire
        Names that begin with: Zabokritski
                Zabokritski Eugene        
 */
중첩 그룹의 내부 요소를 반복하려면 세 개의 중첩 foreach 루프가 필요합니다.
https://msdn.microsoft.com/ko-kr/library/bb311041.aspx

이 항목에서는 소스 데이터를 그룹으로 순서 정렬하는 쿼리를 만들어 각 그룹에 대한 하위 쿼리를 개별적으로 수행하는 두 가지 방법에 대해 설명합니다. 각 예제에서 기본적인 방법은 newGroup이라는 연속 문자를 사용하여 newGroup에 대한 새로운 하위 쿼리를 생성하여 소스 개체를 그룹화하는 것입니다. 이 하위 쿼리는 외부 쿼리를 사용하여 만든 각각의 새로운 그룹에 대해 실행됩니다. 이 특정 예제에서 마지막 출력은 그룹이 아니라 익명 형식의 기본 시퀀스입니다.

예제

public void QueryMax()
{
    var queryGroupMax =
        from student in students
        group student by student.Year into studentGroup
        select new
        {
            Level = studentGroup.Key,
            HighestScore =
            (from student2 in studentGroup
             select student2.ExamScores.Average()).Max()
        };

    int count = queryGroupMax.Count();
    Console.WriteLine("Number of groups = {0}", count);

    foreach (var item in queryGroupMax)
    {
        Console.WriteLine("  {0} Highest Score={1}", item.Level, item.HighestScore);
    }
}

https://msdn.microsoft.com/ko-kr/library/bb397941.aspx

관계형 데이터베이스에서 말하는 내부 조인이란 첫 번째 컬렉션의 각 요소가 두 번째 컬렉션에서 일치하는 모든 요소에 대해 한 번만 표시되는 결과 집합을 생성하는 것입니다. 첫 번째 컬렉션의 요소와 일치하는 요소가 없는 경우 해당 요소가 결과 집합에 표시되지 않습니다. C#에서 join절로 호출되는 Join 메서드는 내부 조인을 구현합니다.
이 항목에서는 다음과 같은 내부 조인의 네 가지 변형을 수행하는 방법을 보여 줍니다.
  • 간단한 키를 기준으로 두 데이터 소스의 요소를 연관시키는 간단한 내부 조인
  • 복합 키를 기준으로 두 데이터 소스의 요소를 연관시키는 내부 조인. 둘 이상의 값으로 구성된 복합 키를 사용하면 둘 이상의 속성을 기준으로 요소를 연관시킬 수 있습니다.
  • 연속된 조인 작업이 서로에게 추가되는 여러 조인
  • 그룹 조인을 사용하여 구현된 내부 조인
https://msdn.microsoft.com/ko-kr/library/bb397905.aspx
그룹 조인은 계층적 데이터 구조를 생성하는 데 유용합니다. 그룹 조인은 첫 번째 컬렉션의 각 요소와 두 번째 컬렉션의 연관된 요소 집합을 쌍으로 만듭니다.
예를 들어, Student라는 클래스나 관계형 데이터베이스 테이블은 Id 및 Name이라는 두 개의 필드를 포함할 수 있습니다. Course라는 두 번째 클래스나 관계형 데이터베이스 테이블은 StudentId 및 CourseTitle이라는 두 개의 필드를 포함할 수 있습니다. 이 경우 일치하는 Student.Id 및 Course.StudentId를 기반으로 이러한 두 데이터 소스의 그룹 조인은 각 Student를 Course 개체(비어 있을 수도 있음)의 컬렉션과 그룹화합니다.
System_CAPS_note참고
첫 번째 컬렉션의 각 요소는 두 번째 컬렉션에서 연관된 요소가 발견되는지 여부에 상관없이 그룹 조인의 결과 집합에 나타납니다. 연관된 요소가 발견되지 않을 경우 해당 요소에 대한 연관된 요소의 시퀀스는 비어 있습니다. 따라서 결과 선택기는 첫 번째 컬렉션의 모든 요소에 액세스할 수 있습니다. 이는 두 번째 컬렉션에 일치하는 항목이 없는 첫 번째 컬렉션의 요소에 액세스할 수 없는 비그룹 조인의 결과 선택기와 다릅니다.
https://msdn.microsoft.com/ko-kr/library/bb882517.aspx
이 예제에서는 조인 연산 결과의 순서를 정렬하는 방법을 보여 줍니다. 순서 정렬 작업은 조인 이후에 수행됩니다. 조인하기 전에 하나 이상의 소스 시퀀스에 대해 orderby 절을 사용할 수도 있지만 이 방법은 사용하지 않는 것이 좋습니다. 일부 LINQ 공급자에서는 조인 이후에 이 정렬 순서를 유지하지 않기 때문입니다.

https://msdn.microsoft.com/ko-kr/library/bb907099.aspx
이 예제에서는 둘 이상의 키를 사용하여 일치 항목을 정의하기 위해 조인 연산을 수행하는 방법을 보여 줍니다. 조인 연산을 수행하려면 복합 키를 사용합니다. 익명 형식의 복합 키를 만들거나, 비교할 값이 포함된 명명된 형식으로 복합 키를 만듭니다. 메서드 경계를 넘어서 쿼리 변수를 전달하려면 키의 Equals  GetHashCode를 재정의하는 명명된 형식을 사용합니다. 각 키는 속성 이름 및 속성 순서가 동일해야 합니다.

다음 예제에서는 복합 키를 사용하여 세 테이블의 데이터를 조인하는 방법을 보여 줍니다.
var query = from o in db.Orders
    from p in db.Products
    join d in db.OrderDetails 
        on new {o.OrderID, p.ProductID} equals new {d.OrderID,
        d.ProductID} into details
        from d in details
        select new {o.OrderID, p.ProductID, d.UnitPrice};
복합 키의 형식 유추는 키의 속성 이름과 속성 순서에 따라 달라집니다. 소스 시퀀스에서 속성의 이름이 같지 않으면 키에 새 이름을 지정해야 합니다. 예를 들어 Orders 테이블과 OrderDetails 테이블의 열 이름이 서로 다르면 다음과 같이 익명 형식의 동일한 이름을 지정하여 복합 키를 만들 수 있습니다.
join...on new {Name = o.CustomerName, ID = o.CustID} equals 
    new {Name = d.CustName, ID = d.CustID }
복합 키는 group 절에서도 사용할 수 있습니다.
https://msdn.microsoft.com/ko-kr/library/bb882535.aspx
이 예제에서는 소스 컬렉션에 포함될 수 있는 null 값의 처리 방법을 보여 줍니다. IEnumerable<T> 과 같은 개체 컬렉션에는 값이 null인 요소가 포함될 수 있습니다. 소스 컬렉션이 null이거나 값이 null인 요소가 소스 컬렉션에 들어 있고 쿼리에서 null 값을 처리하지 않는 경우, 쿼리를 실행하면 NullReferenceException이 throw됩니다.

다음 예제와 같이 null 참조 예외가 발생하지 않도록 코드를 작성할 수 있습니다.
var query1 =
    from c in categories
    where c != null
    join p in products on c.ID equals
        (p == null ? null : p.CategoryID)
    select new { Category = c.Name, Name = p.Name };


위의 예제에서 where 절은 범주 시퀀스에서 null 요소를 모두 필터링하여 제외합니다. 이 기술은 조인 절의 null 검사 기능과는 관계없습니다.이 예제에서 null을 사용하는 조건 식은 Products.CategoryID int? 형식(Nullable<int>의 축약형)이기 때문에 제대로 작동합니다.
조인 절에서 비교 키 중 하나만 nullable 값 형식인 경우에는 쿼리 식에서 다른 키를 nullable 형식으로 캐스팅할 수 있습니다. 다음 예제에서는EmployeeID 열에 int? 형식의 값이 들어 있다고 가정합니다.
void TestMethod(Northwind db)
{
    var query =
        from o in db.Orders
        join e in db.Employees
            on o.EmployeeID equals (int?)e.EmployeeID
        select new { o.OrderID, e.FirstName };
}
https://msdn.microsoft.com/ko-kr/library/bb513730.aspx
쿼리 식의 컨텍스트에서 메서드를 호출할 수 있습니다. 그러나 데이터 소스의 내용 변경 및 예외 throw 등 의도하지 않은 결과가 발생할 수 있는 쿼리 식에서는 메서드를 호출하지 않는 것이 좋습니다. 이 예제에서는 예외 처리에 대한 일반적인 .NET Framework 지침을 위반하지 않고 쿼리 식에서 메서드를 호출할 때 예외가 발생하지 않도록 지정하는 방법을 보여 줍니다. 이러한 지침에서는 해당 컨텍스트에서 특정 예외가 throw되는 이유를 알고 있는 경우 해당 예외를 catch할 수 있다고 설명합니다. 자세한 내용은 최선의 예외 구현 방법를 참조하십시오.
마지막 예제에서는 쿼리 실행 중에 예외를 throw해야 하는 경우를 처리하는 방법을 보여 줍니다.
경우에 따라 쿼리 내에서 throw되는 예외에 대한 가장 좋은 응답은 쿼리 실행을 즉시 중지하는 것입니다. 다음 예제에서는 쿼리 본문 내부에서 throw되는 예외를 처리하는 방법을 보여 줍니다. SomeMethodThatMightThrow에서 쿼리 실행을 중지하는 예외를 발생시킬 수 있다고 가정합니다.
try 블록에서는 쿼리 자체가 아니라 foreach 루프를 포함합니다. 이는 쿼리가 실제로 실행되는 지점에 foreach 루프가 있기 때문입니다. 자세한 내용은 LINQ 쿼리 소개(C#)를 참조하십시오.
class QueryThatThrows
{
    static void Main()
    {
        // Data source.
        string[] files = { "fileA.txt", "fileB.txt", "fileC.txt" };

        // Demonstration query that throws.
        var exceptionDemoQuery =
            from file in files
            let n = SomeMethodThatMightThrow(file)
            select n;

        // Runtime exceptions are thrown when query is executed.
        // Therefore they must be handled in the foreach loop.
        try
        {
            foreach (var item in exceptionDemoQuery)
            {
                Console.WriteLine("Processing {0}", item);
            }
        }

        // Catch whatever exception you expect to raise
        // and/or do any necessary cleanup in a finally block
        catch (InvalidOperationException e)
        {
            Console.WriteLine(e.Message);
        }

        //Keep the console window open in debug mode
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }

    // Not very useful as a general purpose method.
    static string SomeMethodThatMightThrow(string s)
    {
        if (s[4] == 'C')
            throw new InvalidOperationException();
        return @"C:\newFolder\" + s;
    }
}
/* Output:
    Processing C:\newFolder\fileA.txt
    Processing C:\newFolder\fileB.txt
    Operation is not valid due to the current state of the object.
 */

댓글 없음:

댓글 쓰기