ArrayList는 값을 object형식으로 방식해서 받게 되어 모든 타입을 담을 수 있다.

하지만 Boxing이 일어나는데 이러한 빈번한 Boxing을 막기 위해 우리는 Generic에 있는 List<type>을 이용한다.

다음은 Boxing처리되는 ArrayList와 Boxing되지 않는 Generic List<>의 비교를 보도록 한다.

using System;
using System.Collections;
using System.Collections.Generic;

namespace ConsoleApplication1
{
    struct RGB
    {
        public int red;
        public int green;
        public int blue;

        public RGB(int red, int green, int blue)
        {
            this.red = red;
            this.green = green;
            this.blue = blue;
        }

        public override string ToString()
        {
            return red.ToString("X") + green.ToString("X") + blue.ToString("X");
        }
    }

    class Program
    {
        static ArrayList boxValue;
        static List noBoxValue;

        static void Main(string[] args)
        {
            DateTime start;
            DateTime middle;
            TimeSpan end;

            RGB rgb = new RGB(255, 255, 255);

            #region None Boxing

            Console.WriteLine("No Boxing : List.Add(RGB)");

            noBoxValue = new List();

            start = DateTime.Now;

            for (int i = 0; i < 10000000; i++)
                noBoxValue.Add(rgb);

            foreach (RGB value in noBoxValue)
            {
                string str = value.ToString();
            }

            middle = DateTime.Now;
            end = middle - start;

            Console.WriteLine("시작 = {0}, 끝 = {1}, 경과시간 = {2}", start.ToString("hh:mm:ss"),
                middle.ToString("hh:mm:ss"),
                end.ToString());

            #endregion

            #region Boxing

            Console.WriteLine("Boxing : ArrayList.Add(object)");

            boxValue = new ArrayList();

            start = DateTime.Now;

            for (int i = 0; i < 10000000; i++)
                boxValue.Add(rgb);

            foreach (RGB value in boxValue)
            {
                string str = value.ToString();
            }

            middle = DateTime.Now;
            end = middle - start;

            Console.WriteLine("시작 = {0}, 끝 = {1}, 경과시간 = {2}", start.ToString("hh:mm:ss"),
                middle.ToString("hh:mm:ss"),
                end.ToString());

            #endregion

            Console.Read();
        }
    }
}



Finding Unmatched Records in Dataset Tables Using Linq
http://www.eggheadcafe.com/tutorials/aspnet/9b443d4a-0ec3-4599-be41-d4454c1fbd9b/finding-unmatched-records.aspx

1. LINQ(Language Integrated Query )
http://msdn.microsoft.com/ko-kr/library/bb397926.aspx
쿼리를 이용하여 개체와 데이터 간 격차를 줄여주는 기술.
객체지향 기술을 사용하여 네이티브에 정의되지 않은 정보 접근이나 통합의 복잡함을 경감시키는 방식으로  모든 Data소스에 대해 쿼리가 가능하게 한 framework  
주요장점들
   -  여러 조건을 필터링할 때 더욱 명료하며 읽기 쉽다.
   -  최소한의 응용 프로그램 코드를 사용하여 강력한 필터링, 정렬 및 그룹화 기능을 제공.
   -  거의 수정하지 않거나 약간만 수정하여 다른 데이터 소스에 이식할 수 있다.

1.1 다양한 종류의 데이터 소스와 형식에서 작업하기 위한 일관된 모델을 제공

1.1.1 쿼리 가능한 형식 
   - IEnumerable 또는 IEnumerable<(Of <(T>)>)
   - IQueryable<(Of <(T>)>) 같은 파생 인터페이스를 지원하는 형식
   - SQL Server 데이터베이스, XML 문서, ADO.NET 데이터 집합 

1.2. 코딩시 기본 쿼리 작업 특징들

// Data source.
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
1.2.0 쿼리생성(Query creation)
// var 키워드는 from 절에 지정된 데이터 소스를 검사하여 쿼리 변수의 형식을 추론하도록  컴파일러에 지시
// numQuery변수는는 이 쿼리식만 가지고 실행은 되지 않는다.
var numQuery =  
        from num in numbers where (num % 2) == 0
        select num;

1.2.1. 지연실행
foreach문 사용 시 실재로 실행을함
        foreach (int num in numQuery)
            Console.Write("{0,1} ", num);
1.2.2. 즉시실행강제
-  Count, Max, Average 및 First와 같은 집계함수를 수행 할 때는 내부적으로 내장 foreach문이 실행됨
       int numCount = numQuery .Count();
1.2.3.  실행결과 캐싱
-  ToList<(Of <(TSource>)>) 또는 ToArray<(Of <(TSource>)>) 메서드를 호출 결과를 캐싱
         List<int> numQuery2 = (from num in numbers where (num % 2) == 0 select num).ToList();
        // or like this:
        var numQuery3 = (from num in numbers where (num % 2) == 0 select num).ToArray();
1.2.4.
정렬
      var queryLondonCustomers3 = 
             from cust in customers where cust.City == "London"
            
orderby cust.Name ascending
             select cust;
1.2.5. 그룹화
-
group 절은 그룹의 키 값과 일치하는 하나 이상의 항목을 포함하는 IGrouping<(Of <(TKey, TElement>)>) 개체 시퀀스를 반환
      // Query variable is an IEnumerable<IGrouping<char, Student>>
      var studentQuery1 =
      from student in students
      group student by student.Last[0]; //char형 키를 저장하게된다.

1.2.5.1. 각 그룹에서 추가 쿼리 작업을 수행하려면  into 컨텍스트키워드를 이용하여 임시식별자를 만들어서 수행
      // custQuery is an IEnumerable<IGrouping<string, Customer>>
      var custQuery = from cust in customers
      group cust by cust.City into custGroup
      where
custGroup.Count() > 2
      orderby custGroup.Key
      select custGroup;

1.2.5.2. 그룹쿼리 결과얻기
      // Iterate group items with a nested foreach. This IGrouping encapsulates a sequence of Student objects, and a Key of type char.
      // For convenience, var can also be used in the foreach statement.
       foreach (IGrouping<char, Student> studentGroup in studentQuery2)
      {
            Console.WriteLine(studentGroup.Key);
            // Explicit type for student could also be used here.
            foreach (var student in studentGroup)
           {
                Console.WriteLine("   {0}, {1}", student.Last, student.First);
           }
       }
       
1.2.5.3. 그루핑 시 키 지정
        그룹 키는 문자열, 기본 제공 숫자 형식 또는 사용자 정의 명명된 형식이나 익명 형식과 같은 임의의 형식가능

1.2.5.3.1. 문자열값으로 그룹화예
 Query variable(studentQuery3 ) is an IEnumerable<IGrouping<string, Student>> 이 됨
 var studentQuery3 = from student in students group student by student.Last;

1.2.5.3.2. 부울값으로 그룹화 예
 group by 다음에 조건을 지정하여 조건에 해당하는 두 그룹으로 나누는 경우
 데이타가 아래와 같이 정의된경우,
         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}}
        };
 다음과 같이 조건으로 그루핑하면 키값이 boolean형으로 저장됨
         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());
            }
        }
1.2.5.3.3. 숫자범위로 그룹화 예
 group by 다음에 범위형태로 조건을 줄 수도 있다. 10단위로 키값이 그루핑되나봅니다.
        var studentQuery =
            from student in students
            let avg = (int)student.Scores.Average()
            group student by (avg == 0 ? 0 : avg / 10) into g
            orderby g.Key
            select g;           

        // Execute the query.
        foreach (var studentGroup in studentQuery)
        {
            int temp = studentGroup.Key * 10;
            Console.WriteLine("Students with an average between {0} and {1}", temp, temp + 10);
            foreach (var student in studentGroup)
            {
                Console.WriteLine("   {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
            }
        }
1.2.5.3.3. 복합키로 그룹화 예
group person by new {name = person.surname, city = person.city};

1.2.6. 조인

1.2.6.1. 내부조인
  var innerJoinQuery =
  from category in categories
            join prod in products on category.ID equals prod.CategoryID
  select new { ProductName = prod.Name, Category = category.Name }; //produces flat sequence

1.2.6.2. 그룹조인
  - into 식이 있는 join 절을 그룹 조인을 말한다. T-Sql에 이런게 없는걸로 아는데... 재미난 조인인듯
  - 그룹 조인은 왼쪽 소스 시퀀스의 요소를 오른쪽 소스 시퀀스에 있는 하나 이상의 일치하는 요소와 연결하는 계층적 결과를 생성
  - 왼쪽 소스의 요소와 일치하는 오른쪽 소스 시퀀스의 요소가 없을 경우 join 절은 해당 항목에 대해 빈 배열을 생성
  - 그룹 조인은 결과 시퀀스가 그룹으로 구성된다는 점을 제외하고 기본적으로 내부 동등 조인과 같다?
    var innerGroupJoinQuery = 
    from category in categories  
               join prod in products on category.ID equals prod.CategoryID
into prodGroup
    select new { CategoryName = category.Name, Products = prodGroup };
  - 그룹 조인의 결과를 다른 하위 쿼리의 생성기로 사용 (T-Sql에서 중첩쿼리에 해당하겠네요)
    var innerGroupJoinQuery2 =
    from category in categories
               join prod in products on category.ID equals prod.CategoryID into prodGroup
    from prod2 in prodGroup
    where prod2.UnitPrice > 2.50M
    select prod2;   

1.2.6.3.
왼쪽우선 외부조인 (Tsql의 Left Outer Join)
  - LINQ에서 왼쪽 우선 외부 조인을 수행하려면 DefaultIfEmpty 메서드를 그룹 조인과 함께 사용하여
    왼쪽 요소에 일치 항목이 없을 경우 생성할 기본 오른쪽 요소를 지정
    문법이 매끄럽지가 못하다는 느낌이...(그냥 Left Join으로 표기하지 않구.. 엄청 어렵게 표현T.T)
          var leftOuterJoinQuery =
          from category in categories
                   join prod in products on category.ID equals prod.CategoryID
into prodGroup
          from item in prodGroup.DefaultIfEmpty(new Product{Name = String.Empty, CategoryID = 0})
          select new { CatName = category.Name, ProdName = item.Name };

1.2.6.4. 개체 컬렉션 및 관계형 테이블의 조인
         LINQ 쿼리 식에서는 조인 작업이 개체 컬렉션에서 수행되며 두 개의 관계형 테이블과 동일한 방식으로 "조인"할 수 없다.
         LINQ에서 명시적 join 절은 두 개의 소스 콜렉션이 어떤 관계로도 연결되지 않은 경우에만 필요
         LINQ to SQL을 사용하는 경우 외래 키 테이블은 개체 모델에 기본 테이블의 속성으로 표현되므로 조인이 이미 된 상태임
        ( 예를 들어 Northwind 데이터베이스의 Customer 테이블은 Orders 테이블과 외래 키 관계가 있습니다.
          테이블을 개체 모델에 매핑하면 Customer 클래스에 해당 Customer와 연결된 Orders 컬렉션을 포함하는 Orders 속성이 자동생성됨)

 1.2.6.5. 복합키 조인 
         익명 형식의 복합 키를 만들거나, 비교할 값이 포함된 명명된 형식으로 복합 키를 만듬.
        (참고: db라는 collection의 속성으로 Orders와 Products가 이미 포함되었다고 가정함)
         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};    

1.3. LINQ를 통한 데이터 변환
 LINQ(통합 언어 쿼리)는 데이터를 검색할 뿐 아니라 데이터를 변환하는 강력한 도구.
 LINQ 쿼리를 사용하면 소스 시퀀스를 입력으로 사용하고 다양한 방식으로 수정하여 새 출력 시퀀스를 만들 수 있다.
 LINQ 쿼리의 가장 강력한 기능은 select절을 이용한 새 형식을 만드는 기능

1.3.1. 여러 입력을 하나의 출력 시퀀스로 결합 
두 개의 메모리 내 데이터 구조를 결합하는 방법을 보여 주지만, XML이나 SQL 또는 DataSet 소스의 데이터를 결합하는 경우에도 동일한 원칙을 적용할 수 있다.        
        // Create the first data source.
        List<Student> students = new List<Student>()
        {
            new Student {First="Svetlana", Last="Omelchenko",  ID=111, Street="123 Main Street", City="Seattle", Scores= new List<int> {97, 92, 81, 60}},
            new Student {First="Claire", Last="O’Donnell",  ID=112, Street="124 Main Street", City="Redmond", Scores= new List<int> {75, 84, 91, 39}},
            new Student {First="Sven", Last="Mortensen", ID=113, Street="125 Main Street", City="Lake City", Scores= new List<int> {88, 94, 65, 91}},
        };

        // Create the second data source.
        List<Teacher> teachers = new List<Teacher>()
        {               
            new Teacher {First="Ann", Last="Beebe", ID=945, City = "Seattle"},
            new Teacher {First="Alex", Last="Robinson", ID=956, City = "Redmond"},
            new Teacher {First="Michiyo", Last="Sato", ID=972, City = "Tacoma"}
        };

        // Create the query.
        var peopleInSeattle =
        (
           from student in students
           where student.City == "Seattle"
           select student.Last
        )
        .Concat  // 쿼리의 union all 에 해당
       (
          from teacher in teachers
          where teacher.City == "Seattle"
          select teacher.Last
       );

        Console.WriteLine("The following students and teachers live in Seattle:");
       
        // Execute the query.
        foreach (var person in peopleInSeattle)
        {
            Console.WriteLine(person);
        }

1.3.2. 각 소스 요소의 하위 집합 선택 
 - 단순한 Select
    var query = from cust in Customers
            select cust.City;
 - 새로운 인스턴트스로 생성하며 select
   var query = from cust in Customer
            select new {Name = cust.Name, City = cust.City}; 

1.3.3. 메모리 내부 개체를 XML로 변환 
// Create the data source by using a collection initializer.
        List<Student> students = new List<Student>()
        {
            new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores = new List<int>{97, 92, 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>{88, 94, 65, 91}},
        };

        // Create the query. 이렇게 XElement를 이용하여~~~
        var studentsToXML =
        new XElement("Root",
            from student in students
            let x = String.Format("{0},{1},{2},{3}", student.Scores[0], student.Scores[1], student.Scores[2], student.Scores[3])
            select new XElement("student",
                       new XElement("First", student.First),
                       new XElement("Last", student.Last),
                       new XElement("Scores", x)
                    ) // end "student"
                ); // end "Root"

        // Execute the query.
        Console.WriteLine(studentsToXML);

1.3.4.  소스 요소에서 작업 수행
        // Data source.
        double[] radii = { 1, 2, 3 };

        // Query.
        IEnumerable<string> query =
            from rad in radii
            select String.Format("Area = {0}", (rad * rad) * 3.14);

        // Query execution.
        foreach (string s in query)
            Console.WriteLine(s);

1.4. 쿼리 구문과 메서드 구문 비교
NET CLR(공용 언어 런타임) 자체에는 쿼리 구문의 개념이 없고 컴파일 타임에 쿼리 식은 CLR이 인식가능한 메서드 호출로 변환됨
이러한 메서드를 표준 쿼리 연산자 라고 하며 Where, Select, GroupBy, Join, Max, Average,... 등이 있다.
쿼리 구문 대신 메서드 구문을 사용하여 이를 직접 호출할 수 있다.
          int[] numbers = { 5, 10, 8, 3, 6, 12};
         //Query syntax:
        IEnumerable<int> numQuery1 =
            from num in numbers
            where num % 2 == 0
            orderby num
            select num;

        //Method syntax:
        IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);
둘의 결과는 동일하다.
Where(num => num % 2 == 0) 이 인라인 식을 람다식 이라한다.
람다식을 사용하지 않을 경우 익명 메서드나 제네릭 대리자 또는 식 트리와 같은 형식으로 코드를 작성해야 한다.
C#에서 =>는 "goes to"를 나타내는 람다 연산자
 

1.5. 람다식
http://msdn.microsoft.com/ko-kr/library/bb397687.aspx 계속
http://msdn.microsoft.com/ko-kr/library/bb397951.aspx 식트리
http://msdn.microsoft.com/ko-kr/library/bb882636.aspx
식과 문을 포함하고 대리자나 식 트리 형식을 만드는 데 사용할 수 있는 익명 함수
모든 람다 식에는 람다 연산자 =>("이동"이라고 읽음)사용
왼쪽에는 입력 매개 변수(있는 경우)를 지정하고 오른쪽에는 식 또는 문 블록이 위치
=> 연산자는 할당 연산자(=)와 우선 순위가 같으며 오른쪽 결합성이 있다.
예) x => x * x라는 람다 식은 "x는 x 곱하기 x로 이동"으로 읽으며, 이 식은 다음과 같이 대리자 형식에 할당할 수 있다.
delegate int del(int i);
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25

메서드 기반 LINQ 쿼리에서 Where 및 Where 같은 표준 쿼리 연산자 메서드의 인수로 사용.
LINQ to Objects 및 LINQ to XML에서처럼 메서드 기반의 구문을 사용하여 Enumerable 클래스에서 Where 메서드를 호출하는 경우
매개 변수는 System..::.Func<(Of <(T, TResult>)>) 대리자 형식
람다식은 대리자를 만드는 가장 간단한 방법이다.
LINQ to SQL에서 System.Linq..::.Queryable 클래스 에서 호출하는 경우 매개 변수 형식은 System.Linq.Expressions..::.Expression<Func>이고, 여기서 Func는 입력 매개 변수를 5개까지 가질 수 있는 임의의 Func 대리자이다.

1.5.1. 식 람다(expression lamda)
- 오른쪽에 식이 있는 람다 식,  식 트리를 만드는 데 광범위하게 사용,식 람다는 식의 결과를 반환
 (input parameters) => expression
- 괄호는 람다 식에 입력 매개 변수가 하나뿐인 경우에만 생략
- 둘 이상의 입력 매개 변수는 다음과 같이 괄호로 묶고 쉼표로 구분
 (x, y) => x == y
- 컴파일러에서 입력 형식을 유추할 수 없는 경우 다음과 같이 형식을 명시적으로 지정
 (int x, string s) => s.Length > x
- 입력 매개 변수가 0개이면 다음과 같이 빈 괄호를 지정
 () => SomeMethod()
1.5.2.  식트리
System.Linq.Expressions 네임스페이스 필요
IQueryable<(Of <(T>)>)을 구현하는 데이터의 소스를 대상으로 하는 구조화된 쿼리를 나타내기 위해 식 트리를 사용
LINQ에서 Expression<(Of <(TDelegate>)>) 형식 변수에 할당되는 람다 식을 나타내는 데에도 사용
   Expression<Func<int, bool>> exprTree = num => num < 5;
   와같이 Delegate표현대신 Expression클래스 형식을 사용하여 트리형태로 식을저장
ParameterExpression,ConstantExpression,BinaryExpression등과같이 식에표현된 각 부분에 해당하는 클래스들존재

// Manually build the expression tree for the lambda expression num => num < 5.
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 =
    Expression.Lambda<Func<int, bool>>(
        numLessThanFive,
        new ParameterExpression[] { numParam });
와 동일한 람다식표현
// Let the compiler generate the expression tree for the lambda expression num => num < 5.
Expression<Func<int, bool>> lambda2 = num => num < 5;
이정도 개념만 있어도 충분할듯, 더 자세한 부분은 MSDN을 찾아보시고 http://msdn.microsoft.com/ko-kr/library/bb882637.aspx


1.5.3. 문 람다 (statement lamda)
- 문 람다는 다음과 같이 중괄호 안에 문을 지정한다는 점을 제외하면 식 람다와 비슷
 (input parameters) => {statement;}
- 문 람다의 본문에 지정할 수 있는 문의 개수에는 제한이 없지만 일반적으로 2-3개 정도만 지정
  (문 람다는 식 트리를 만드는 데 사용될 수 없다)
 delegate void TestDelegate(string s);
 …
 TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
 myDel("Hello");

1.5.4. 표준 쿼리 연산자와 람다 식
- 대부분의 표준 쿼리 연산자에는 형식이 제네릭 대리자의 Func<(Of <(T, TResult>)>) 패밀리 중 하나인 입력 매개 변수를 사용
 public delegate TResult Func<TArg0, TResult>(TArg0 arg0)
 이 경우 대리자를 Func<int,bool> myFunc로 인스턴스화할 수 있다.
 int는 입력 매개 변수이고, bool은 반환 값(반환 값은 항상 마지막 형식 매개 변수로 지정)

예) 5인지 여부판단하여 bool로 리턴
    Func<int, bool> myFunc = x => x == 5;
    bool result = myFunc(4); // returns false of course
- System.Linq.Queryable에 정의되어 있는 표준 쿼리 연산자의 경우와 같이 인수 형식이 Expression<Func>인 경우에도 람다 식을 사용.
  Expression<Func> 인수를 지정하면 식 트리에 람다 식이 컴파일됨

예) 2로 나누었을 때 나머지가 1인 정수(n)의 수를 계산
      int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
      int oddNumbers = numbers.Count(n => n % 2 == 1);
    시작부터 검사하여 6보다 작은 값들 추출
      var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);
    2개의 파라메타를 입력받아 동적으로 임계치 변경
      var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);

1.5.5. 람다 식에서의 형식 유추
입력 매개 변수의 형식은 대부분의 표준 쿼리 연산자에서 첫 번째 입력 형식은 소스 시퀀스 요소의 형식으로 컴파일러가 유추함
IEnumerable<Customer>을 쿼리할 경우 입력 변수가 Customer 개체로 유추
customers.Where(c => c.City == "London");
- 람다 식적용 규칙
  람다 식과 대리자 형식에 포함된 매개 변수 수가 같아야 한다. 
   람다 식의 각 입력 매개 변수는 해당되는 대리자 매개 변수로 암시적으로 변환될 수 있어야 한다.
  람다 식의 반환 값(있는 경우)은 대리자의 반환 형식으로 암시적으로 변환될 수 있어야 한다.
  람다 식의 "형식"을 비공식적으로 언급해야 할 경우 대리자 형식 또는 람다 식이 변환되는 Expression 형식을 의미

1.5.6. 람다 식의 변수 범위
람다 식은 람다 식이 정의된 바깥쪽 메서드나 형식의 범위에 포함되어 있는 외부 변수를 참조할 수 있다.
이러한 방식으로 캡처되는 변수는 변수가 범위를 벗어나 가비지 수집되는 경우에도 람다 식에 사용할 수 있도록 저장된다.
외부 변수는 명확하게 할당해야만 람다 식에 사용할 수 있다
- 람다 식의 변수 범위규칙
  캡처된 변수는 해당 변수를 참조하는 대리자가 범위에서 벗어날 때까지 가비지 수집되지 않는다.
  람다 식에 사용된 변수는 외부 메서드에 표시되지 않는다.
  람다 식은 바깥쪽 메서드에서 ref 또는 out 매개 변수를 직접 캡처할 수 없습니다.
  람다 식의 return 문에 의해서는 바깥쪽 메서드가 반환되지는 않는다.?
  람다 식에는 포함된 익명 함수의 본문 또는 본문 외부를 대상으로 하는 goto 문, break 문 또는 continue 문이 포함될 수 없다

예)
    delegate bool D();
    delegate bool D2(int i);

    class Test
    {
        D del;
        D2 del2;
        public void TestMethod(int input)
        {
            int j = 0;
            // Initialize the delegates with lambda expressions.
            // Note access to 2 outer variables.
            // del will be invoked within this method.
            del = () => { j = 10;  return j > input; };

            // del2 will be invoked after TestMethod goes out of scope.
            del2 = (x) => {return x == j; };
           
            // Demonstrate value of j:
            // Output: j = 0
            // The delegate has not been invoked yet.
            Console.WriteLine("j = {0}", j);

            // Invoke the delegate.
            bool boolResult = del();

            // Output: j = 10 b = True
            Console.WriteLine("j = {0}. b = {1}", j, boolResult);
        }

        static void Main()
        {
            Test test = new Test();
            test.TestMethod(5);

            // Prove that del2 still has a copy of
            // local variable j from TestMethod.
            bool result = test.del2(10);

            // Output: True
            Console.WriteLine(result);
           
            Console.ReadKey();
        }
    }

2. LINQ to Objects
중간 LINQ 공급자나 API는 사용하지 않고 LINQ to SQL,LINQ to XML처럼 IEnumerable,IEnumerable<(Of <(T>)>) 파생 컬렉션을 직접 사용하는 LINQ쿼리를 말함
LINQ를 사용하여 List<(Of <(T>)>), Array 또는 Dictionary<(Of <(TKey, TValue>)>)와 같은 열거 가능한 컬렉션을 모두 쿼리가능

2.1. LINQ 및 문자열
문자열 및 문자열의 컬렉션을 쿼리하고 변환
일반적인 문자열 함수 및 정규식과 결합될 수 있다.

2.1.1 문자열에서 단어가 나오는 횟수 세기(LINQ)
 class CountWords
{
    static void Main()
    {
        string text = @"Historically, the world of data and the world of objects" +
          @" have not been well integrated. Programmers work in C# or Visual Basic" +
          @" and also in SQL or XQuery. On the one side are concepts such as classes," +
          @" objects, fields, inheritance, and .NET Framework APIs. On the other side" +
          @" are tables, columns, rows, nodes, and separate languages for dealing with" +
          @" them. Data types often require translation between the two worlds; there are" +
          @" different standard functions. Because the object world has no notion of query, a" +
          @" query can only be represented as a string without compile-time type checking or" +
          @" IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to" +
          @" objects in memory is often tedious and error-prone.";

        string searchTerm = "data";

        //Convert the string into an array of words
        string[] source = text.Split(new char[] { '.', '?', '!', ' ', ';', ':', ',' }, StringSplitOptions.RemoveEmptyEntries);

        // Create and execute the query. It executes immediately because a singleton value is produced. 즉시실행???
        // Use ToLowerInvariant to match "data" and "Data"
        var matchQuery = from word in source
                         where word.ToLowerInvariant() == searchTerm.ToLowerInvariant()
                         select word;

        // Count the matches.
        int wordCount = matchQuery.Count();
        Console.WriteLine("{0} occurrences(s) of the search term \"{1}\" were found.", wordCount, searchTerm);

    }
}
/* Output:
   3 occurrences(s) of the search term "data" were found.
*/

2.1.2 지정된 단어 집합이 들어 있는 문장 쿼리(LINQ)
//나눈 문장들에서 "Historically", "data" 및 "integrated"이라는 단어를 모두 포함한 문장을 반환.
        // Split the text block into an array of sentences.
        string[] sentences = text.Split(new char[] { '.', '?', '!' });

        // Define the search terms. This list could also be dynamically populated at runtime.
        string[] wordsToMatch = { "Historically", "data", "integrated" };

        // let에서 문장들 나누고, Distinct로 중복된 문장제거, Intersect로 교집합연산의 개수가 wordsToMatch의 개수와 동일한 문장만 Select
        // Note that the number of terms to match is not specified at compile time.
        var sentenceQuery = from sentence in sentences
                            let w = sentence.Split(new char[] { '.', '?', '!', ' ', ';', ':', ',' },StringSplitOptions.RemoveEmptyEntries)
                            where w.Distinct().Intersect(wordsToMatch).Count() == wordsToMatch.Count()
                            select sentence;
     
        // Execute the query. Note that you can explicitly type the iteration variable here even though sentenceQuery
        // was implicitly typed.
        foreach (string str in sentenceQuery)
        {
            Console.WriteLine(str);
        }
2.1.3 문자열의 문자 쿼리(LINQ)
String 클래스는 제네릭 IEnumerable<(Of <(T>)>) 인터페이스를 구현하기 때문에 모든 문자열을 문자 시퀀스로 쿼리할 수 있다.
class QueryAString
{
    static void Main()
    {
        string aString = "ABCDE99F-J74-12-89A";

        // Select only those characters that are numbers 숫자로~~
        IEnumerable<char> stringQuery =
          from ch in aString
          where Char.IsDigit(ch)
          select ch;

        // Execute the query
        foreach (char c in stringQuery)
            Console.Write(c + " ");

        // Call the Count method on the existing query.
        int count = stringQuery.Count();
        Console.WriteLine("Count = {0}", count);

        // Select all characters before the first '-'
        IEnumerable<char> stringQuery2 = aString.TakeWhile(c => c != '-');

        // Execute the second query
        foreach (char c in stringQuery2)
            Console.Write(c);

        Console.WriteLine(System.Environment.NewLine + "Press any key to exit");
        Console.ReadKey();

    }
}
/* Output:
  Output: 9 9 7 4 1 2 8 9
  Count = 8
  ABCDE99F
*/

2.1.4. LINQ 쿼리와 정규식 결합
Regex 클래스를 사용하여 텍스트 문자열에서 좀 더 복잡한 비교를 위해 정규식을 만드는 방법
class QueryWithRegEx
{
     // This method assumes that the application has discovery permissions for all folders under the specified path.
    static IEnumerable<System.IO.FileInfo> GetFiles(string path)
    {
        if (!System.IO.Directory.Exists(path))
            throw new System.IO.DirectoryNotFoundException();

        string[] fileNames = null;
        List<System.IO.FileInfo> files = new List<System.IO.FileInfo>();

        fileNames = System.IO.Directory.GetFiles(path, "*.*", System.IO.SearchOption.AllDirectories);
        foreach (string name in fileNames)
        {
            files.Add(new System.IO.FileInfo(name));
        }
        return files;
    }
    public static void Main()
    {
        // Modify this path as necessary.
        string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";

        // Take a snapshot of the file system.
        IEnumerable<System.IO.FileInfo> fileList = GetFiles(startFolder); //폴더의 모든 파일정보

        // Create the regular expression to find all things "Visual".
        System.Text.RegularExpressions.Regex searchTerm =
            new System.Text.RegularExpressions.Regex(@"Visual (Basic|C#|C\+\+|J#|SourceSafe|Studio)");

        // Search the contents of each .htm file.
        // Remove the where clause to find even more matches!
        // This query produces a list of files where a match was found, and a list of the matches in that file.
        // Note: Explicit typing of "Match" in select clause.
        // This is required because MatchCollection is not a generic IEnumerable collection.
        var queryMatchingFiles =
            from file in fileList
            where file.Extension == ".htm" //확장자가 htm인 것들중에
            let fileText = System.IO.File.ReadAllText(file.FullName) // 파일명만 fileText 목록(집합)으로두고
            let matches = searchTerm.Matches(fileText) // fileText에서 searchTerm에 해당하는 것들만
            where searchTerm.Matches(fileText).Count > 0 //이게 없으면 matches에 null도 들어가나??? 확인필요
            select new
            {
                name = file.FullName,
                matches = from System.Text.RegularExpressions.Match match in matches
                          select match.Value
            };

        // Execute the query.
        Console.WriteLine("The term \"{0}\" was found in:", searchTerm.ToString());

        foreach (var v in queryMatchingFiles)
        {
            // Trim the path a bit, then write the file name in which a match was found.
            string s = v.name.Substring(startFolder.Length - 1);
            Console.WriteLine(s);

            // For this file, write out all the matching strings
            foreach (var v2 in v.matches)
            {
                Console.WriteLine("  " + v2);
            }
        }
    } 
}
2.1.5. 두 목록 간의 차집합 구하기(LINQ)
 names1.txt에 있지만 names2.txt에는 없는 해당 줄을 출력
class CompareLists
{       
    static void Main()
    {
        // Create the IEnumerable data sources.
        string[] names1 = System.IO.File.ReadAllLines(@"../../../names1.txt");
        string[] names2 = System.IO.File.ReadAllLines(@"../../../names2.txt");

        // Create the query. Note that method syntax must be used here.
        IEnumerable<string> differenceQuery =
          names1.Except(names2);

        // Execute the query.
        Console.WriteLine("The following lines are in names1.txt but not names2.txt");
        foreach (string s in differenceQuery)
            Console.WriteLine(s);
    }
}
2.1.6. 단어 또는 필드에 따라 텍스트 데이터 정렬 또는 필터링(LINQ)
쉼표로 구분된 값과 같은 구조화된 텍스트 줄을 해당 줄에서 임의의 필드를 기준으로 정렬하는 방법
scores.csv의 필드는 학생의 ID 번호와 4개의 테스트 점수를 나타낸다고 가정
public class SortLines
{
    static void Main()
    {
        // Create an IEnumerable data source
        string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");

        // Change this to any value from 0 to 4.
        int sortField = 1; //두번째 필드 말하죠.. (0부터시작)

        Console.WriteLine("Sorted highest to lowest by field [{0}]:", sortField);

        // Demonstrates how to return query from a method.
        // The query is executed here.
        foreach (string str in RunQuery(scores, sortField))
        {
            Console.WriteLine(str);
        }
    }

    // Returns the query variable, not query results!
    static IEnumerable<string> RunQuery(IEnumerable<string> source, int num)
    {
        // Split the string and sort on field[num]
        var scoreQuery = from line in source
                         let fields = line.Split(',')
                         orderby fields[num] descending
                         select line;

        return scoreQuery;
    }
}
/* Output (if sortField == 1):
   Sorted highest to lowest by field [1]:
    116, 99, 86, 90, 94
    120, 99, 82, 81, 79
    111, 97, 92, 81, 60
    114, 97, 89, 85, 82
    121, 96, 85, 91, 60
    122, 94, 92, 91, 91
    117, 93, 92, 80, 87
    118, 92, 90, 83, 78
    113, 88, 94, 65, 91
    112, 75, 84, 91, 39
    119, 68, 79, 88, 92
    115, 35, 72, 91, 70
 */
2.1.7. 구분된 파일의 필드 다시 정렬후 파일로 저장.
- spreadsheet1.csv라는 일반 텍스트 내용
Adams,Terry,120
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Cesar,114
Garcia,Debra,115
Garcia,Hugo,118
Mortensen,Sven,113
O'Donnell,Claire,112
Omelchenko,Svetlana,111
Tucker,Lance,119
Tucker,Michael,122
Zabokritski,Eugene,121

-파일을 읽어 쉼표로 구분하고,  3번쨰 필드로 정렬한 후 다시 파일로 저장
        // Create the IEnumerable data source
        string[] lines = System.IO.File.ReadAllLines(@"../../../spreadsheet1.csv");

        // Create the query. Put field 2 first, then
        // reverse and combine fields 0 and 1 from the old field
        IEnumerable<string> query =
            from line in lines
            let x = line.Split(',')
            orderby x[2] 
            select x[2] + ", " + (x[1] + " " + x[0]);

        // Execute the query and write out the new file. Note that WriteAllLines
        // takes a string[], so ToArray is called on the query.
        System.IO.File.WriteAllLines(@"../../../spreadsheet2.csv", query.ToArray());

2.1.7. 문자열 컬렉션 결합 및 비교
텍스트 줄이 포함된 파일을 병합한 다음 결과를 정렬하는 방법
특히 두 개의 텍스트 줄 집합에서의 간단한 연결, 공용 구조체 및 교집합을 수행
- names1.txt 내용
Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
- names2.txt 내용
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi

        static void OutputQueryResults(IEnumerable<string> query, string message)
        {
            Console.WriteLine(System.Environment.NewLine + message);
            foreach (string item in query)
            {
                Console.WriteLine(item);
            }
            Console.WriteLine("{0} total names in list", query.Count());
        }
//위 함수가 미리 정의돼 있다고 가정
            //Put text files in your solution folder
            string[] fileA = System.IO.File.ReadAllLines(@"../../../names1.txt");
            string[] fileB = System.IO.File.ReadAllLines(@"../../../names2.txt");

            //Simple concatenation and sort. Duplicates are preserved.
            IEnumerable<string> concatQuery = fileA.Concat(fileB).OrderBy(s => s);

            // Pass the query variable to another function for execution.
            OutputQueryResults(concatQuery, "Simple concatenate and sort. Duplicates are preserved:");

            // Concatenate and remove duplicate names based on default string comparer.(병합)
            IEnumerable<string> uniqueNamesQuery =
                fileA.Union(fileB).OrderBy(s => s); //병합하면서 distinct도 될터
            OutputQueryResults(uniqueNamesQuery, "Union removes duplicate names:");

            // Find the names that occur in both files (based on default string comparer). (교집합)
            IEnumerable<string> commonNamesQuery =
                fileA.Intersect(fileB);
            OutputQueryResults(commonNamesQuery, "Merge based on intersect:");

            // Find the matching fields in each list. Merge the two results by using Concat,
            // and then
sort using the default string comparer.
            string nameMatch = "Garcia";

            IEnumerable<String> tempQuery1 =
                from name in fileA
                let n = name.Split(',')
                where n[0] == nameMatch //,로 분리후 0번필드중에  nameMatch와 일치하는  것들 

                select name;

            IEnumerable<string> tempQuery2 =
                from name2 in fileB
                let n2 = name2.Split(',')
                where n2[0] == nameMatch
                select name2;

            IEnumerable<string> nameMatchQuery =
                tempQuery1.Concat(tempQuery2).OrderBy(s => s); // 이러면 union효과가 되나? 아님 Left join?? 확인필요
            OutputQueryResults(nameMatchQuery, String.Format("Concat based on partial name match \"{0}\":", nameMatch));

2.1.8. 여러 소스로 개체 컬렉션 채우기(두 파일읽어서 조인효과 구현)
- scores.csv
111, 97, 92, 81, 60
112, 75, 84, 91, 39
113, 88, 94, 65, 91
114, 97, 89, 85, 82
115, 35, 72, 91, 70
116, 99, 86, 90, 94
117, 93, 92, 80, 87
118, 92, 90, 83, 78
119, 68, 79, 88, 92
120, 99, 82, 81, 79
121, 96, 85, 91, 60
122, 94, 92, 91, 91
- names.csv
Omelchenko,Svetlana,111
O'Donnell,Claire,112
Mortensen,Sven,113
Garcia,Cesar,114
Garcia,Debra,115
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Hugo,118
Tucker,Lance,119
Adams,Terry,120
Zabokritski,Eugene,121
Tucker,Michael,122
        // These data files are defined in How to: Join Content from Dissimilar Files (LINQ)
        string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
        string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");

        // Merge the data sources using a named type.
        // var could be used instead of an explicit type.
        // Note the dynamic creation of a list of ints for the TestScores member. We skip 1 because the first string
        // in the array is the student ID, not an exam score.
        IEnumerable<Student> queryNamesScores =
            from name in names
            let x = name.Split(',')
            from score in scores
            let s = score.Split(',')
            where x[2] == s[0]  // score의 첫번째 컬럼과 names의 두번째 컬럼과 조인(학생ID)
            select new Student()  //이렇게 새로운 타입으로 new
            {
                FirstName = x[0],
                LastName = x[1],
                ID = Convert.ToInt32(x[2]), //타입명시 
                ExamScores = (from scoreAsText in s.Skip(1) //첫번째 값은 ID이므로 skip 
                              select Convert.ToInt32(scoreAsText)).
                              ToList()
            };

        // Optional. Store the newly created student objects in memory for faster access in future queries. Could be useful with
        // very large data files.
        List<Student> students = queryNamesScores.ToList();

        // Display the results and perform one further calculation.
        foreach (var student in students)
        {
            Console.WriteLine("The average score of {0} {1} is {2}.",
                student.FirstName, student.LastName, student.ExamScores.Average());
        }
2.1.9. 그룹을 사용하여 파일을 여러 파일로 분할
- names1.csv
Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
- names2.csv
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi

        string[] fileA = System.IO.File.ReadAllLines(@"../../../names1.txt");
        string[] fileB = System.IO.File.ReadAllLines(@"../../../names2.txt");

        // Concatenate and remove duplicate names based on default string comparer
        var mergeQuery = fileA.Union(fileB);

        // Group the names by the first letter in the last name.
        var groupQuery = from name in mergeQuery
                         let n = name.Split(',')
                         group name by n[0][0] into g  //제일 첫글자 , n[0]은 첫번쨰 컬럼
                         orderby g.Key
                         select g;

        // Create a new file for each group that was created
        // Note that nested foreach loops are required to access
        // individual items with each group.
        foreach (var g in groupQuery)
        {
            // Create the new file name.
            string fileName = @"../../../testFile_" + g.Key + ".txt";
            // Write file. 그룹별로 파일생성
            using (System.IO.StreamWriter sw = new System.IO.StreamWriter(fileName))
            {
                foreach (var item in g)
                {
                    sw.WriteLine(item);
                    // Output to console for example purposes.
                    Console.WriteLine("   {0}", item);
                }
            }
        }

2.1.10. 서로 다른 파일의 콘텐츠 조인 
코드만 봐도 될듯
        string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
        string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");
        // Name:    Last[0],                First[1],      ID[2],     Grade Level[3]
        //                  Omelchenko,    Svetlana,      11,          2
        // Score:   StudentID[0],  Exam1[1]   Exam2[2],  Exam3[3],  Exam4[4]
        //          111,           97,        92,        81,        60

        // This query joins two dissimilar spreadsheets based on common ID value.
        // Multiple from clauses are used instead of a join clause
        // in order to store results of id.Split.
        IEnumerable<string> scoreQuery1 =
            from name in names
            let nameFields = name.Split(',')
            from id in scores
            let scoreFields = id.Split(',')
            where nameFields[2] == scoreFields[0]
            select nameFields[0] + "," + scoreFields[1] + "," + scoreFields[2]
                   + "," + scoreFields[3] + "," + scoreFields[4];

        // Pass a query variable to a method and
        // execute it in the method. The query itself
        // is unchanged.
        OutputQueryResults(scoreQuery1, "Merge two spreadsheets:");

2.1.10. CSV 텍스트 파일의 열 값 계산 

         string[] lines = System.IO.File.ReadAllLines(@"../../../scores.csv");
          .....
        IEnumerable<IEnumerable<int>> query = from line in strs
            let x = line.Split(',')
            let y = x.Skip(1)
            select (
                            from str in y
                            select Convert.ToInt32(str));

        // Execute and cache the results for performance.
        // ToArray could also be used here.
        var results = query.ToList();

        // Find out how many columns we have.
        int columnCount = results[0].Count();

        // Perform aggregate calculations on each column.           
        // One loop for each score column in scores.
        // We can use a for loop because we have already executed the columnQuery in the call to ToList.
        for (int column = 0; column < columnCount; column++)
        {
            var res2 = from row in results
                       select row.ElementAt(column);

            double average = res2.Average(); //컬럼에 대한 average
            int max = res2.Max();
            int min = res2.Min();

            // 1 is added to column because Exam numbers
            // begin with 1
            Console.WriteLine("Exam #{0} Average: {1:##.##} High Score: {2} Low Score: {3}",
                          column + 1, average, max, min);
        }

2.2. LINQ 및 리플렉션
LINQ를 리플렉션과 함께 사용하여 지정한 검색 조건에 일치하는 메서드에 대한 특정 메타데이터를 검색할 수 있다

using System.Reflection;
using System.IO;
namespace LINQReflection
{
    class ReflectionHowTO
    {
        static void Main(string[] args)
        {
            Assembly assembly = Assembly.Load("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken= b77a5c561934e089");
            var pubTypesQuery = 
            from type in assembly.GetTypes() // 타입목록 추출
            where type.IsPublic
            from method in type.GetMethods() // 각 타입에서 메소드목록 추출
            where method.ReturnType.IsArray == true 
                      || ( method.ReturnType.GetInterface(typeof(System.Collections.Generic.IEnumerable<>).FullName ) != null
                     && method.ReturnType.FullName != "System.String" )
            group method.ToString() by type.ToString();
            // 이렇게 되면 타입콜렉션의 속성으로 method가 들어가는 형태임. 아래에서 키(콜렉션 내에서 타입을 식별하는)별로 메소드 출력
            foreach (var groupOfMethods in pubTypesQuery)
            {
                Console.WriteLine("Type: {0}", groupOfMethods.Key);
                foreach (var method in groupOfMethods)
                {
                    Console.WriteLine("  {0}", method);
                }
            }

            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }
    } 
}

2.3. LINQ 및 파일 디렉터리
문자열을 다루면서 파일에 관련된 내용이 나오긴 했지만 여기에서 더 상세히 다룸.

2.1.1. 지정된 특성 또는 이름을 갖는 파일 쿼리 
//주어진 경로의 파일정보 가져오는 함수  
// This method assumes that the application has discovery  permissions for all folders under the specified path.
    static IEnumerable<System.IO.FileInfo> GetFiles(string path)
    {
        if (!System.IO.Directory.Exists(path))
            throw new System.IO.DirectoryNotFoundException();

        string[] fileNames = null;
        List<System.IO.FileInfo> files = new List<System.IO.FileInfo>();

        fileNames = System.IO.Directory.GetFiles(path, "*.*", System.IO.SearchOption.AllDirectories);           
        foreach (string name in fileNames)
        {
            files.Add(new System.IO.FileInfo(name));
        }
        return files;
    }
...
        string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";

        // Take a snapshot of the file system.
        IEnumerable<System.IO.FileInfo> fileList = GetFiles(startFolder);

        //Create the query
        IEnumerable<System.IO.FileInfo> fileQuery =
            from file in fileList
            where file.Extension == ".txt"
            orderby file.Name
            select file;

        //Execute the query. This might write out a lot of files!
        foreach (System.IO.FileInfo fi in fileQuery)         
             Console.WriteLine(fi.FullName);    // Create and execute a new query by using the previous
        // query as a starting point. fileQuery is not
        // executed again until the call to Last()
        var newestFile =
            (from file in fileQuery
            orderby file.CreationTime
            select new { file.FullName, file.CreationTime })
            .Last();

        Console.WriteLine("\r\nThe newest .txt file is {0}. Creation time: {1}",
            newestFile.FullName, newestFile.CreationTime);

2.1.2. 확장명에 따라 파일 그룹화 

        // Take a snapshot of the file system.
        string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\Common7";

        // Used in WriteLine to trim output lines.
        int trimLength = startFolder.Length;

        // Take a snapshot of the file system.
        IEnumerable<System.IO.FileInfo> fileList = GetFiles(startFolder);

        // Create the query.
        var queryGroupByExt =
            from file in fileList
            group file by file.Extension.ToLower() into fileGroup
            orderby fileGroup.Key //그루핑이 되면서 동일그룹에 키값들이 할당됨에 유의
            select fileGroup;
      PageOutput(trimLength, queryGroupByExt); // 바로아래에 정의

   private static void PageOutput( int rootLength,
                                    IEnumerable<System.Linq.IGrouping<string, System.IO.FileInfo>> groupByExtList)
    {
        // Flag to break out of paging loop.
        bool goAgain = true;

        // "3" = 1 line for extension + 1 for "Press any key" + 1 for input cursor.
        int numLines = Console.WindowHeight - 3;

        // Iterate through the outer collection of groups.
        foreach (var filegroup in groupByExtList)
        {
            // Start a new extension at the top of a page.
            int currentLine = 0;

            // Output only as many lines of the current group as will fit in the window.
            do
            {
                Console.Clear();
                Console.WriteLine(filegroup.Key == String.Empty ? "[none]" : filegroup.Key);

                // Get 'numLines' number of items starting at number 'currentLine'.
                var resultPage = filegroup.Skip(currentLine).Take(numLines);

                //Execute the resultPage query
                foreach (var f in resultPage)
                {
                    Console.WriteLine("\t{0}", f.FullName.Substring(rootLength));
                }

                // Increment the line counter.
                currentLine += numLines;

                // Give the user a chance to escape.
                Console.WriteLine("Press any key to continue or the 'End' key to break...");
                ConsoleKey key = Console.ReadKey().Key;
                if (key == ConsoleKey.End)
                {
                    goAgain = false;
                    break;
                }
            } while (currentLine < filegroup.Count());

            if (goAgain == false)
                break;
        }
    }
2.1.3. 폴더 집합의 전체 바이트 수 쿼리
    static long GetFileLength(string filename)
    {
        long retval;
        try { System.IO.FileInfo fi = new System.IO.FileInfo(filename);
            retval = fi.Length;
        }
        catch (System.IO.FileNotFoundException)
        {
            // If a file is no longer present,
            // just add zero bytes to the total.
            retval = 0;
        }
        return retval;
    }
.....
        string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\VC#";

        // Take a snapshot of the file system.
        // This method assumes that the application has discovery permissions
        // for all folders under the specified path.
        IEnumerable<string> fileList = System.IO.Directory.GetFiles(startFolder, "*.*", System.IO.SearchOption.AllDirectories);

        var fileQuery = from file in fileList
                        select GetFileLength(file);

        // Cache the results to avoid multiple trips to the file system.
        long[] fileLengths = fileQuery.ToArray();

        // Return the size of the largest file
        long largestFile = fileLengths.Max();

        // Return the total number of bytes in all the files under the specified folder.
        long totalBytes = fileLengths.Sum();

        Console.WriteLine("There are {0} bytes in {1} files under {2}",
            totalBytes, fileList.Count(), startFolder);
        Console.WriteLine("The largest files is {0} bytes.", largestFile);
2.1.3. 두 폴더의 내용 비교
세 가지 방법
- 두 개의 파일 목록이 동일한지 여부를 지정하는 부울 값 쿼리
- 양쪽 파일에 있는 파일을 검색하는 교차 부분 쿼리
- 한 폴더에는 있지만 다른 폴더에는 없는 파일을 검색하는 차집합 쿼리

    class FileCompare : System.Collections.Generic.IEqualityComparer<System.IO.FileInfo>
    {
        public FileCompare() { }

        public bool Equals(System.IO.FileInfo f1, System.IO.FileInfo f2)
        {
            return (f1.Name == f2.Name && f1.Length == f2.Length); //파일명과 길이비교
        }

        // Return a hash that reflects the comparison criteria. According to the
        // rules for IEqualityComparer<T>, if Equals is true, then the hash codes must
        // also be equal. Because equality as defined here is a simple value equality, not
        // reference identity, it is possible that two or more objects will produce the same
        // hash code.
        public int GetHashCode(System.IO.FileInfo fi)
        {
            string s = String.Format("{0}{1}", fi.Name, fi.Length);
            return s.GetHashCode();
        }
    }
라는 Compare클래스 미리정의 된 경우
            string pathA = @"C:\TestDir";
            string pathB = @"C:\TestDir2"; // Take a snapshot of the file system.
            IEnumerable<System.IO.FileInfo> list1 = GetFiles(pathA);
            IEnumerable<System.IO.FileInfo> list2 = GetFiles(pathB);

            //A custom file comparer defined below
            FileCompare myFileCompare = new FileCompare();

            // This query determines whether the two folders contain identical file lists, based on the custom file comparer
            // that is defined in the FileCompare class.
            // The query executes immediately because it returns a bool.
            bool areIdentical = list1.SequenceEqual(list2, myFileCompare);
            if (areIdentical == true)
                Console.WriteLine("the two folders are the same");
            else
                Console.WriteLine("The two folders are not the same");

            // Find the common files. It produces a sequence and doesn't execute until the foreach statement.
            var queryCommonFiles = list1.Intersect(list2, myFileCompare);
           if (queryCommonFiles.Count() > 0)
           {
                Console.WriteLine("The following files are in both folders:");
                foreach (var v in queryCommonFiles)
                {
                    Console.WriteLine(v.FullName); //shows which items end up in result list
                }
            }
            else
            {
                Console.WriteLine("There are no common files in the two folders.");
            }

            // Find the set difference between the two folders.
            // For this example we only check one way.
            var queryList1Only = (from file in list1 select file).Except(list2, myFileCompare);

            Console.WriteLine("The following files are in list1 but not list2:");
            foreach (var v in queryList1Only)
            {
                Console.WriteLine(v.FullName);
            }
2.1.4. 디렉터리 트리에서 가장 큰 파일을 하나 이상 쿼리 
파일 크기(바이트)와 관련된 5개의 쿼리방법
- 가장 큰 파일의 크기(바이트)를 검색하는 방법
- 가장 작은 파일의 크기(바이트)를 검색하는 방법
- 지정한 루트 폴더에 있는 하나 이상의 폴더에서 FileInfo 개체의 가장 큰 파일이나 가장 작은 파일을 검색하는 방법
- 가장 큰 10개 파일과 같은 시퀀스를 검색하는 방법
- 지정한 크기보다 작은 파일을 무시하여 파일 크기(바이트)에 따라 파일을 그룹으로 정렬하는 방법
       string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";

        // Take a snapshot of the file system.
        // fileList is an IEnumerable<System.IO.FileInfo>
        var fileList = GetFiles(startFolder);

        //Return the size of the largest file
        long maxSize =
            (from file in fileList
             let len = GetFileLength(file)
             select len).Max();

        Console.WriteLine("The length of the largest file under {0} is {1}",
            startFolder, maxSize);

        // Return the FileInfo object for the largest file by sorting and selecting from beginning of list
        System.IO.FileInfo longestFile =
            (from file in fileList
            let len = GetFileLength(file)
            where len > 0
            orderby len descending
            select file).First();

        Console.WriteLine("The largest file under {0} is {1} with a length of {2} bytes",
                            startFolder, longestFile.FullName, longestFile.Length);

        //Return the FileInfo of the smallest file
        System.IO.FileInfo smallestFile =
            (from file in fileList
            let len = GetFileLength(file)
            where len > 0
            orderby len ascending
            select file).First();

        Console.WriteLine("The smallest file under {0} is {1} with a length of {2} bytes",
                            startFolder, smallestFile.FullName, smallestFile.Length);

        //Return the FileInfos for the 10 largest files queryTenLargest is an IEnumerable<System.IO.FileInfo>
        var queryTenLargest =
            (from file in fileList
            let len = GetFileLength(file)
            orderby len descending
            select file).Take(10);

        Console.WriteLine("The 10 largest files under {0} are:", startFolder);

        foreach (var v in queryTenLargest)
        {
            Console.WriteLine("{0}: {1} bytes", v.FullName, v.Length);
        }


        // Group the files according to their size, leaving out files that are less than 200000 bytes.
        var querySizeGroups =
            from file in fileList
            let len = GetFileLength(file)
            where len > 0
            group file by (len / 100000) into fileGroup
            where fileGroup.Key >= 2
            orderby fileGroup.Key descending
            select fileGroup;


        foreach (var filegroup in querySizeGroups)
        {
            Console.WriteLine(filegroup.Key.ToString() + "00000");
            foreach (var item in filegroup)
            {
                Console.WriteLine("\t{0}: {1}", item.Name, item.Length);
            }
        }

2.1.5. 디렉터리 트리의 중복 파일 쿼리
 첫 번째 쿼리에서는 간단한 키를 사용하여 일치하는 항목을 확인(이름은 같지만 내용은 다를 수 있는 파일임)
 두 번째 쿼리에서는 복합 키를 사용하여 FileInfo 개체의 세 가지 속성에 대한 일치 여부를 확인
class QueryDuplicateFileNames
{
    static void Main(string[] args)
    {  
        // Uncomment QueryDuplicates2 to run that query.
        QueryDuplicates();           
        // QueryDuplicates2();

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

    static void QueryDuplicates()
    {
        // Change the root drive or folder if necessary
        string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";

        // Take a snapshot of the file system.
        IEnumerable<System.IO.FileInfo> fileList = GetFiles(startFolder);

        // used in WriteLine to keep the lines shorter
        int charsToSkip = startFolder.Length;

        // var can be used for convenience with groups.
        var queryDupNames =
            from file in fileList
            group file.FullName.Substring(charsToSkip) by file.Name into fileGroup
            where fileGroup.Count() > 1
            select fileGroup;

        // Pass the query to a method that will
        // output one page at a time.
        PageOutput<string,string>(queryDupNames);
    }

    // A Group key that can be passed to a separate method.
    // Override Equals and GetHashCode to define equality for the key.
    // Override ToString to provide a friendly name for Key.ToString()
    class PortableKey
    {
        public string Name { get; set; }
        public DateTime CreationTime { get; set; }
        public long Length {get;  set;}

        public override bool Equals(object obj)
        {
            PortableKey other = (PortableKey)obj;
            return other.CreationTime == this.CreationTime &&
                   other.Length == this.Length &&
                   other.Name == this.Name;
        }

        public override int GetHashCode()
        {
            string str = String.Format("{0}{1}{2}", this.CreationTime, this.Length, this.Name);
            return str.GetHashCode();
        }
        public override string ToString()
        {
            return String.Format("{0} {1} {2}", this.Name, this.Length, this.CreationTime);
        }
    }
    static void QueryDuplicates2()
    {
        // Change the root drive or folder if necessary.
        string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\Common7";

        // Make the the lines shorter for the console display
        int charsToSkip = startFolder.Length;

        // Take a snapshot of the file system.
        IEnumerable<System.IO.FileInfo> fileList = GetFiles(startFolder);

        // Note the use of a compound key. Files that match
        // all three properties belong to the same group.
        // A named type is used to enable the query to be
        // passed to another method. Anonymous types can also be used
        // for composite keys but cannot be passed across method boundaries
        //
        var queryDupFiles =
            from file in fileList
            group file.FullName.Substring(charsToSkip) by
                new PortableKey{ Name=file.Name, CreationTime=file.CreationTime, Length=file.Length } into fileGroup
            where fileGroup.Count() > 1

            select fileGroup;

        var list = queryDupFiles.ToList();

        int i = queryDupFiles.Count();

        PageOutput<PortableKey, string>(queryDupFiles);              
    }


    // A generic method to page the output of the QueryDuplications methods
    // Here the type of the group must be specified explicitly. "var" cannot
    // be used in method signatures. This method does not display more than one
    // group per page.
    private static void PageOutput<K,V>(IEnumerable<System.Linq.IGrouping<K, V>> groupByExtList)
    {
        // Flag to break out of paging loop.
        bool goAgain = true;

        // "3" = 1 line for extension + 1 for "Press any key" + 1 for input cursor.
        int numLines = Console.WindowHeight - 3;

        // Iterate through the outer collection of groups.
        foreach (var filegroup in groupByExtList)
        {
            // Start a new extension at the top of a page.
            int currentLine = 0;

            // Output only as many lines of the current group as will fit in the window.
            do
            {
                Console.Clear();
                Console.WriteLine("Filename = {0}", filegroup.Key.ToString() == String.Empty ? "[none]" : filegroup.Key.ToString());

                // Get 'numLines' number of items starting at number 'currentLine'.
                var resultPage = filegroup.Skip(currentLine).Take(numLines);

                //Execute the resultPage query
                foreach (var fileName in resultPage)
                {
                    Console.WriteLine("\t{0}", fileName);
                }

                // Increment the line counter.
                currentLine += numLines;

                // Give the user a chance to escape.
                Console.WriteLine("Press any key to continue or the 'End' key to break...");
                ConsoleKey key = Console.ReadKey().Key;
                if (key == ConsoleKey.End)
                {
                    goAgain = false;
                    break;
                }
            } while (currentLine < filegroup.Count());

            if (goAgain == false)
                break;
        }
    }


    // This method assumes that the application has discovery
    // permissions for all folders under the specified path.
    static IEnumerable<System.IO.FileInfo> GetFiles(string path)
    {
        if (!System.IO.Directory.Exists(path))
            throw new System.IO.DirectoryNotFoundException();

        string[] fileNames = null;
        List<System.IO.FileInfo> files = new List<System.IO.FileInfo>();

        fileNames = System.IO.Directory.GetFiles(path, "*.*", System.IO.SearchOption.AllDirectories);
        foreach (string name in fileNames)
        {
            files.Add(new System.IO.FileInfo(name));
        }
        return files;
    }
}

2.1.6. 폴더의 파일 내용 쿼리  

        string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";

        // Take a snapshot of the file system.
        IEnumerable<System.IO.FileInfo> fileList = GetFiles(startFolder);

        string searchTerm = @"Visual Studio";

        // Search the contents of each file.
        // A regular expression created with the RegEx class
        // could be used instead of the Contains method.
        // queryMatchingFiles is an IEnumerable<string>.
        var queryMatchingFiles =
            from file in fileList
            where file.Extension == ".htm"
            let fileText = GetFileText(file.FullName)
            where fileText.Contains(searchTerm)
            select file.FullName;

        // Execute the query.
        Console.WriteLine("The term \"{0}\" was found in:", searchTerm);
        foreach (string filename in queryMatchingFiles)
        {
            Console.WriteLine(filename);
        }

3. LINQ to XML
http://msdn.microsoft.com/ko-kr/library/bb387098.aspx
DOM(문서 개체 모델) XML 프로그래밍 인터페이스와 유사.
LINQ to XML의 쿼리 기능은 기능 면에서(구문 면에서는 아니지만) XPath 및 XQuery와 유사.
클래스들

3.1. LINQ to XML과 DOM 비교
LINQ to XML과 현재 주로 사용되는 XML 프로그래밍 API인 W3C DOM(문서 개체 모델)의 몇 가지 주요 차이점

3.1.1. W3C DOM에서는 상향식으로 XML 트리를 빌드. 즉, 문서를 만들고 요소를 만든 다음 요소를 문서에 아래코드와 같이 추가 하는 형식이지만
LINQ to XML에서 함수 생성을 사용하여 훨씬 직관적인 코드를 생성할 수 있다.
 예) DOM의 Microsoft 구현인 XmlDocument를 사용하여 XML 트리를 만드는 일반적인 방법
XmlDocument doc = new XmlDocument();
XmlElement name = doc.CreateElement("Name");
name.InnerText = "Patrick Hines";
XmlElement phone1 = doc.CreateElement("Phone");
phone1.SetAttribute("Type", "Home");
phone1.InnerText = "206-555-0144";       
XmlElement phone2 = doc.CreateElement("Phone");
phone2.SetAttribute("Type", "Work");
phone2.InnerText = "425-555-0145";       
XmlElement street1 = doc.CreateElement("Street1");       
street1.InnerText = "123 Main St";
XmlElement city = doc.CreateElement("City");
city.InnerText = "Mercer Island";
XmlElement state = doc.CreateElement("State");
state.InnerText = "WA";
XmlElement postal = doc.CreateElement("Postal");
postal.InnerText = "68042";
XmlElement address = doc.CreateElement("Address");
address.AppendChild(street1);
address.AppendChild(city);
address.AppendChild(state);
address.AppendChild(postal);
XmlElement contact = doc.CreateElement("Contact");
contact.AppendChild(name);
contact.AppendChild(phone1);
contact.AppendChild(phone2);
contact.AppendChild(address);
XmlElement contacts = doc.CreateElement("Contacts");
contacts.AppendChild(contact);
doc.AppendChild(contacts);
이 코딩 스타일은 XML 트리의 구조에 대한 많은 정보를 시각적으로 제공하지 않는다.
LINQ to XML에서는 이러한 XML 트리 생성 방법을 지원하며 추가적으로 다음과 같이
함수 생성에서는 XElement 및 XAttribute 생성자를 사용하여 XML 트리를 빌드하는 방법을 제공

비교) LINQ to XML에서 함수 생성을 사용하여 동일한 XML 트리를 생성하는 방법
XElement contacts =
    new XElement("Contacts",
        new XElement("Contact",
            new XElement("Name", "Patrick Hines"),
            new XElement("Phone", "206-555-0144",
                new XAttribute("Type", "Home")),
            new XElement("phone", "425-555-0145",
                new XAttribute("Type", "Work")),
            new XElement("Address",
                new XElement("Street1", "123 Main St"),
                new XElement("City", "Mercer Island"),
                new XElement("State", "WA"),
                new XElement("Postal", "68042")
            )
        )
    );
3.1.2 LINQtoXML에서는  바로 위 코드와 같이  LINQ XML 요소(XElement)로 직접 XML핸들링이 가능하다.
XML 파일에서 T:System.Xml.Linq.XElement 개체를 직접 로드도 가능
T:System.Xml.Linq.XElement 개체를 파일이나 스트림으로 serialize가능


반면에 W3C DOM에서는 아래코드와 같이 XML 문서가 XML 트리의 논리 컨테이너로 사용
XmlDocument doc = new XmlDocument();
XmlElement name = doc.CreateElement("Name");
name.InnerText = "Patrick Hines";
doc.AppendChild(name);
여러 문서에서 요소를 사용하려면 여러 문서의 노드를 가져와야 하지만 LINQ to XML에서는 이러한 복잡한 작업이 필요 없다.
LINQ to XML을 사용할 때 문서의 루트 수준에서 주석이나 처리 명령을 추가하려는 경우에만 XDocument 클래스를 사용.

3.1.3 LINQ to XML에서는 이름 및 네임스페이스의 처리가 간단하다.
기존 DOM 프로그래밍에서는 이름, 네임스페이스 및 네임스페이스 접두사의 처리는 XML 프로그래밍의 복잡한 부분.
LINQ to XML에서는 네임스페이스 접두사를 처리하는 요구 사항을 제거하여 이름과 네임스페이스를 단순화.
네임스페이스 접두사를 제어할 수 있다.
네임스페이스 접두사를 명시적으로 제어하지 않을경우
LINQ to XML에서는 serialize할 때 네임스페이스 접두사를 할당하거나, 기본 네임스페이스를 사용하여 serialize해야한다.
기본 네임스페이스가 사용되는 경우 생성되는 문서에는 네임스페이스 접두사가 없다.
기존 DOM 프로그래밍 방식에서는 노드의 이름을 변경할 수 없다.
대신 새 노드를 만들고 모든 자식 노드를 새 노드에 복사해야 하므로 원래 노드 ID가 손실됩니다.
LINQ to XML에서는 노드에서 XName 속성을 설정할 수 있도록 하여 이 문제를 방지합니다.

3.1.3.1. XML 네임스페이스 작업(네임스페이스 접두사 제어)
 LINQ to XML에서 XML 이름을 나타내는 클래스는 XName
 - 네임스페이스를 사용하여 문서 만들기(C#)(LINQ to XML)

 예1) 네임스페이스가 하나 포함된 문서를생성. 기본적으로 LINQ to XML은 기본 네임스페이스를 사용하여 이 문서를 serialize
  // Create an XML tree in a namespace.
XNamespace aw = "http://www.adventure-works.com";
XElement root = new XElement(aw + "Root", new XElement(aw + "Child", "child content"));
Console.WriteLine(root);
결과)
<Root xmlns="http://www.adventure-works.com">
  <Child>child content</Child>
</Root>

예2) 네임스페이스 접두사가 포함된 네임스페이스를 선언
// Create an XML tree in a namespace, with a specified prefix
XNamespace aw = "http://www.adventure-works.com";
XElement root = new XElement(aw + "Root",
    new XAttribute(XNamespace.Xmlns + "aw", "http://www.adventure-works.com"),
    new XElement(aw + "Child", "child content")
);
Console.WriteLine(root);
결과)
<aw:Root xmlns:aw="http://www.adventure-works.com">
  <aw:Child>child content</aw:Child>
</aw:Root>

예3)두 네임스페이스가 포함된 문서를 만드는 방법
두 네임스페이스 중 하나는 기본 네임스페이스이고 다른 하나는 접두사가 포함된 네임스페이스
루트 요소에 네임스페이스 특성을 포함하면 http://www.adventure-works.com이 기본 네임스페이스가 되도록 네임스페이스가 serialize되고
www.fourthcoffee.com이 "fc" 접두사를 사용하여 serialize됩니다.
기본 네임스페이스를 선언하는 특성을 만들려면 네임스페이스 없이 이름이 "xmlns"인 특성을 만듭니다.
특성 값은 기본 네임스페이스 URI입니다.
// The http://www.adventure-works.com namespace is forced to be the default namespace.
XNamespace aw = "http://www.adventure-works.com";
XNamespace fc = "www.fourthcoffee.com";
XElement root = new XElement(aw + "Root",
    new XAttribute("xmlns", http://www.adventure-works.com), //기본 네임스페이스가 된다.
    new XAttribute(XNamespace.Xmlns + "fc", "www.fourthcoffee.com"),
    new XElement(fc + "Child",
        new XElement(aw + "DifferentChild", "other content")
    ),
    new XElement(aw + "Child2", "c2 content"),
    new XElement(fc + "Child3", "c3 content")
);
Console.WriteLine(root);
결과)
<Root xmlns="http://www.adventure-works.com" xmlns:fc="www.fourthcoffee.com">
  <fc:Child>
    <DifferentChild>other content</DifferentChild>
  </fc:Child>
  <Child2>c2 content</Child2>
  <fc:Child3>c3 content</fc:Child3>
</Root>

예4)네임스페이스 접두사가 있는 두 가지 네임스페이스가 포함된 문서를 만들기
XNamespace aw = "http://www.adventure-works.com";
XNamespace fc = "www.fourthcoffee.com";
XElement root = new XElement(aw + "Root",
    new XAttribute(XNamespace.Xmlns + "aw", aw.NamespaceName),
    new XAttribute(XNamespace.Xmlns + "fc", fc.NamespaceName),
    new XElement(fc + "Child",
        new XElement(aw + "DifferentChild", "other content")
    ),
    new XElement(aw + "Child2", "c2 content"),
    new XElement(fc + "Child3", "c3 content")
);
Console.WriteLine(root);
결과)
<aw:Root xmlns:aw="http://www.adventure-works.com" xmlns:fc="www.fourthcoffee.com">
  <fc:Child>
    <aw:DifferentChild>other content</aw:DifferentChild>
  </fc:Child>
  <aw:Child2>c2 content</aw:Child2>
  <fc:Child3>c3 content</fc:Child3>
</aw:Root>

예5)확장된 이름이 포함된 문자열을 전달하여 위와 동일한 결과얻기
{}안의 내용을 파싱해야하므로 성능이 명시적으로 XNamespace를 선언하는 경우보다 떨어진다.
// Create an XML tree in a namespace, with a specified prefix
XElement root = new XElement("{http://www.adventure-works.com}Root",
    new XAttribute(XNamespace.Xmlns + "aw", "http://www.adventure-works.com"),
    new XElement("{http://www.adventure-works.com}Child", "child content")
);
Console.WriteLine(root);
결과)
<aw:Root xmlns:aw="http://www.adventure-works.com">
  <aw:Child>child content</aw:Child>
</aw:Root>

3.1.3.2. C#에서 기본 네임스페이스 범위(LINQ to XML)
XML 트리에 나타나는 기본 네임스페이스는 쿼리의 범위에 포함되지 않는다.
기본 네임스페이스에 있는 XML을 사용하는 경우 XNamespace 변수를 선언하고 로컬 이름과 결합하여
쿼리에서 사용할 정규화된 이름을 만들어야만 쿼리식에서 사용할 수 있다.

예1)집합에서는 기본 네임스페이스의 XML이 로드되지만 적절하지 않게 쿼리되는 경우의 예
XElement root = XElement.Parse(
@"<Root xmlns='http://www.adventure-works.com'>
    <Child>1</Child>
    <Child>2</Child>
    <Child>3</Child>
    <AnotherChild>4</AnotherChild>
    <AnotherChild>5</AnotherChild>
    <AnotherChild>6</AnotherChild>
</Root>");
IEnumerable<XElement> c1 =
    from el in root.Elements("Child")
    select el;
Console.WriteLine("Result set follows:");
foreach (XElement el in c1)
    Console.WriteLine((int)el);
Console.WriteLine("End of result set");
결과)
Result set follows:
End of result set

예2) 집합에서는 네임스페이스의 XML을 쿼리할 수 있도록 필요한 수정을 하는 방법을
XElement root = XElement.Parse(
@"<Root xmlns='http://www.adventure-works.com'>
    <Child>1</Child>
    <Child>2</Child>
    <Child>3</Child>
    <AnotherChild>4</AnotherChild>
    <AnotherChild>5</AnotherChild>
    <AnotherChild>6</AnotherChild>
</Root>");
XNamespace aw = "http://www.adventure-works.com";
IEnumerable<XElement> c1 =
    from el in root.Elements(aw + "Child") //명시적으로 지정.
    select el;
Console.WriteLine("Result set follows:");
foreach (XElement el in c1)
    Console.WriteLine((int)el);
Console.WriteLine("End of result set");
결과)
Result set follows:
1
2
3
End of result set

3.1.4 XML을 로드하기 위한 정적 메서드 지원 
예) 파일에서 XML 로드
XElement booksFromFile = XElement.Load(@"books.xml");
Console.WriteLine(booksFromFile);

3.1.5 DTD 구문에 대한 지원 제거  
LINQ to XML에서는 엔터티와 엔터티 참조에 대한 지원 기능을 제거하여 XML 프로그래밍을 더욱 단순화.  

3.1.6 조각에 대한 지원   
LINQ to XML에서는 XmlDocumentFragment 클래스와 동일한 항목을 제공하지만 대부분의 경우에 XmlDocumentFragment 개념은 XNode의 IEnumerable<(Of <(T>)>)이나 XElement의 IEnumerable<(Of <(T>)>)로 형식화된 쿼리의 결과에 의해 처리될 수 있다.

3.1.7 XPathNavigator에 대한 지원  
LINQ to XML에서는 System.Xml.XPath 네임스페이스의 확장 메서드를 통해 XPathNavigator를 지원.
System.Xml.XPath..::.Extensions 클래스의 멤버함수를 이용

3.1.7.1 Extensions..::.CreateNavigator 메서드
XNode에 대해 XPathNavigator를 만듬.
이 메서드에서 반환되는 XPathNavigator를 사용하여 XML 트리를 편집할 수 없다. (CanEdit 속성은 false로 반환).
XDocumentType 노드에 대해 XPathNavigator를 만들 수 없다.(문서형식(XDocumentType)은 XPath 데이터 모델과 관련이 없다)
네임스페이스 선언은 왼쪽에서 오른쪽으로 보고.
XmlDocument 네임스페이스는 오른쪽에서 왼쪽으로 보고.
이 메서드에서 반환되는 탐색기에 대해 MoveToId 메서드는 지원되지 않음.
이 메서드를 사용하여 XSLT 변환가능.

 예)XML 트리를 만들고, XML 트리에서 XPathNavigator를 만들고, 새 문서를 만들고, 새 문서에 쓸 XmlWriter를 만든다.
그런 다음 XPathNavigator 및 XmlWriter를 변환에 전달하여 XSLT 변환을 호출할 수 있다.
변환이 완료된 후 새 XML 트리는 변환 결과로 채운다.

XSLT 변환을 수행하려면 XmlReader 또는 XPathNavigator를 사용.
XmlReader를 사용할 때 더 빨리 실행되는 변환도 있고, XPathNavigator를 사용할 때 더 빨리 실행되는 변환도 있습니다.

string xslMarkup = @"<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>
    <xsl:template match='/Parent'>
        <Root>
            <C1><xsl:value-of select='Child1'/></C1>
            <C2><xsl:value-of select='Child2'/></C2>
        </Root>
    </xsl:template>
</xsl:stylesheet>";

XDocument xmlTree = new XDocument(
    new XElement("Parent",
        new XElement("Child1", "Child1 data"),
        new XElement("Child2", "Child2 data")
    )
);

XDocument newTree = new XDocument();
using (XmlWriter writer = newTree.CreateWriter()) {
    // Load the style sheet.
    XslCompiledTransform xslt = new XslCompiledTransform();
    xslt.Load(XmlReader.Create(new StringReader(xslMarkup)));

    // Execute the transform and output the results to a writer.
    xslt.Transform(xmlTree.CreateNavigator(), writer);
}

Console.WriteLine(newTree);
결과)
<Root>
  <C1>Child1 data</C1>
  <C2>Child2 data</C2>
</Root>

3.1.7.2 Extensions..::.XPathEvaluate 메서드
XPath 식을 평가
예) 다음 예제에서는 특성이 있는 작은 XML 트리를 만든 다음 XPathEvaluate 메서드를 사용하여 특성을 검색합니다.
String xml = "<root a='value'/>";
XDocument d = XDocument.Parse(xml);
IEnumerable att = (IEnumerable)d.XPathEvaluate("/root/@a");
Console.WriteLine(att.Cast<XAttribute>().FirstOrDefault());
결과)
a="value"

3.1.7.3 Extensions..::.XPathSelectElement 메서드
XPath 식을 사용하여 XElement를 선택
예) 작은 XML 트리를 만든 다음 XPathSelectElement를 사용하여 단일 요소를 선택
XElement root = new XElement("Root",
    new XElement("Child1", 1),
    new XElement("Child2", 2),
    new XElement("Child3", 3),
    new XElement("Child4", 4),
    new XElement("Child5", 5),
    new XElement("Child6", 6)
);
XElement el = root.XPathSelectElement("./Child4");
Console.WriteLine(el);
결과)
<Child4>4</Child4>

3.1.7.4 Extensions..::.XPathSelectElements 메서드
XPath 식을 사용하여 요소 컬렉션을 선택
예) 작은 XML 트리를 만든 다음 XPathSelectElements를 사용하여 요소 집합을 선택합
XElement root = new XElement("Root",
    new XElement("Child1", 1),
    new XElement("Child1", 2),
    new XElement("Child1", 3),
    new XElement("Child2", 4),
    new XElement("Child2", 5),
    new XElement("Child2", 6)
);
IEnumerable<XElement> list = root.XPathSelectElements("./Child2");
foreach (XElement el in list)
    Console.WriteLine(el);
결과)
<Child2>4</Child2>
<Child2>5</Child2>
<Child2>6</Child2>

3.1.8 공백 및 들여쓰기에 대한 지원   
서식이 있는 XML을 serialize하는 경우 XML 트리의 유효 공백만 유지(LINQ to XML의 기본 동작)
LINQ to XML에서는 DOM의 경우처럼 serialize된 Whitespace 노드 형식을 사용하는 대신 XText 노드로 공백을 저장.

3.1.9 주석에 대한 지원    
LINQ to XML에서 주석을 사용하여 임의의 형식에 대한 임의의 개체를 XML 트리의 XML 구성 요소와 연결
주석은 XML infoset의 일부가 아니므로 serialize되거나 deserialize되지 않는다.
아래 메서드호출(형식별로 주석을 검색가능)
AddAnnotation - XObject의 주석 목록에 개체를 추가합니다.
Annotation - 지정된 형식의 첫 번째 주석 개체를 XObject에서 가져옵니다.
Annotations -  XObject에 대한 지정된 형식의 주석 컬렉션을 가져옵니다.
RemoveAnnotations - 지정된 형식의 주석을 XObject에서 제거합니다.

예1) XElement에 주석을 추가
 public class MyAnnotation {
    private string tag;
    public string Tag {get{return tag;} set{tag=value;}}
    public MyAnnotation(string tag) {
        this.tag = tag; }} public class Program {
    public static void Main(string[] args) {  
        MyAnnotation ma = new MyAnnotation("T1");
        XElement root = new XElement("Root", "content");
        root.AddAnnotation(ma);

        MyAnnotation ma2 = (MyAnnotation)root.Annotation<MyAnnotation>();
        Console.WriteLine(ma2.Tag);
    }
}
결과)
T1

예2)  요소의 주석을 검색.
public class MyAnnotation {
    private string tag;
    public string Tag {get{return tag;} set{tag=value;}}
    public MyAnnotation(string tag) {
        this.tag = tag; }} class Program {
    static void Main(string[] args) {  
        XElement root = new XElement("Root", "content");
        root.AddAnnotation(new MyAnnotation("T1"));
        root.AddAnnotation(new MyAnnotation("T2"));
        root.AddAnnotation("abc");
        root.AddAnnotation("def");

        IEnumerable<MyAnnotation> annotationList;
        annotationList = root.Annotations<MyAnnotation>();
        foreach (MyAnnotation ma in annotationList)
            Console.WriteLine(ma.Tag);
        Console.WriteLine("----");

        IEnumerable<string> stringAnnotationList;
        stringAnnotationList = root.Annotations<string>();
        foreach (string str in stringAnnotationList)
            Console.WriteLine(str);
    }
}
결과)
T1
T2
----
abc
def

3.1.10 스키마 정보에 대한 지원
LINQ to XML에서는 System.Xml.Schema 네임스페이스의 확장 메서드를 통해 XSD 유효성 검사를 지원
XML 트리가 XSD를 준수하는지 확인할 수 있으며, PSVI(Post-Schema-Validation Infoset)를 사용하여 XML 트리를 채울 수 있다
예1) XmlSchemaSet을 만든 다음 스키마 집합에 대해 두 XDocument 개체의 유효성을 검사.
string xsdMarkup =
    @"<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'>
       <xsd:element name='Root'>
        <xsd:complexType>
         <xsd:sequence>
          <xsd:element name='Child1' minOccurs='1' maxOccurs='1'/>
          <xsd:element name='Child2' minOccurs='1' maxOccurs='1'/>
         </xsd:sequence>
        </xsd:complexType>
       </xsd:element>
      </xsd:schema>";
XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add("", XmlReader.Create(new StringReader(xsdMarkup)));

XDocument doc1 = new XDocument(
    new XElement("Root",
        new XElement("Child1", "content1"),
        new XElement("Child2", "content1")
    )
);

XDocument doc2 = new XDocument(
    new XElement("Root",
        new XElement("Child1", "content1"),
        new XElement("Child3", "content1")
    )
);

Console.WriteLine("Validating doc1");
bool errors = false;
doc1.Validate(schemas, (o, e) =>
                     {
                         Console.WriteLine("{0}", e.Message);
                         errors = true;
                     });
Console.WriteLine("doc1 {0}", errors ? "did not validate" : "validated");

Console.WriteLine();
Console.WriteLine("Validating doc2");
errors = false;
doc2.Validate(schemas, (o, e) =>
                     {
                         Console.WriteLine("{0}", e.Message);
                         errors = true;
                     });
Console.WriteLine("doc2 {0}", errors ? "did not validate" : "validated");
결과)
Validating doc1
doc1 validated

Validating doc2
The element 'Root' has invalid child element 'Child3'. List of possible elements expected: 'Child2'.
doc2 did not validate

예2) :Track('ctl00_rs1_mainContentContainer_cpe497038_c|ctl00_rs1_mainContentContainer_ctl23',this);" href="http://msdn.microsoft.com/ko-kr/library/bb387025.aspx">샘플 XML 파일: 고객 및 주문(LINQ to XML)에 있는 XML 문서가 :Track('ctl00_rs1_mainContentContainer_cpe497038_c|ctl00_rs1_mainContentContainer_ctl24',this);" href="http://msdn.microsoft.com/ko-kr/library/bb675181.aspx">샘플 XSD 파일: 고객 및 주문에 있는 스키마별로 유효한지 확인한 다음 소스 XML 문서를 수정합니다. 여기에서는 첫 번째 고객에 대한 CustomerID 특성을 변경합니다. 변경한 후에는 주문이 존재하지 않는 고객을 참조하게 되므로 XML 문서가 더 이상 유효하지 않습니다.
XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add("", "CustomersOrders.xsd");

Console.WriteLine("Attempting to validate");
XDocument custOrdDoc = XDocument.Load("CustomersOrders.xml");
bool errors = false;
custOrdDoc.Validate(schemas, (o, e) =>
                     {
                         Console.WriteLine("{0}", e.Message);
                         errors = true;
                     });
Console.WriteLine("custOrdDoc {0}", errors ? "did not validate" : "validated");

Console.WriteLine();
// Modify the source document so that it will not validate.
custOrdDoc.Root.Element("Orders").Element("Order").Element("CustomerID").Value = "AAAAA";
Console.WriteLine("Attempting to validate after modification");
errors = false;
custOrdDoc.Validate(schemas, (o, e) =>
                     {
                         Console.WriteLine("{0}", e.Message);
                         errors = true;
                     });
Console.WriteLine("custOrdDoc {0}", errors ? "did not validate" : "validated");
결과)
Attempting to validate
custOrdDoc validated

Attempting to validate after modification
The key sequence 'AAAAA' in Keyref fails to refer to some key.
custOrdDoc did not validate


3.2. LINQ to XML과 다른 XML 기술 비교
LINQ to XML, XmlReader, XSLT, MSXML 및 XmlLite와 같은 XML 기술을비교

3.2.1 LINQ to XML 와 XmlReader
XmlReader는 캐시하지 않으며 정방향으로만 작동하는 빠른 파서.
LINQ to XML은 XmlReader를 기반으로 구현되었으며 두 기술은 밀접하게 통합되어 있다.( XmlReader를 단독으로 사용가능)

예)초당 수백 개의 XML 문서를 구문 분석할 웹 서비스를 빌드하는데 문서의 구조가 동일하여
XML의 구문을 분석하는 코드의 구현을 하나만 작성하면 되는 경우 XmlReader를 단독으로 사용할 수 있습니다.
반면에 크기가 작은 다양한 XML 문서의 구문을 분석하는 시스템을 빌드하는데 각 문서의 구조가 서로 다르면
LINQ to XML에서 제공하는 성능 향상 기능을 사용할 수 있습니다.

3.2.2 LINQ to XML 와 XSLT
LINQ to XML과 XSLT는 모두 광범위한 XML 문서 변환 기능을 제공
XSLT는 규칙 기반의 선언적 방법.
고급 XSLT 프로그래머는 상태 비저장 방법을 강조하는 함수형 프로그래밍 스타일로 XSLT를 작성.
이 경우 부작용 없이 구현되는 순수 함수를 사용하여 변환을 작성할 수 있다.
이 규칙 기반 방법 또는 함수 방법은 대부분의 개발자에게 익숙하지 않으며 배우는 데 많은 시간과 노력이 필요.

XSLT는 고성능 응용 프로그램을 생성하는 생산성이 매우 높은 시스템.
예를 들어 규모가 큰 일부 웹 회사에서는 다양한 데이터 저장소에서 가져온 XML에서 HTML을 생성하는 방법으로 XSLT를 사용.
관리되는 XSLT 엔진은 XSLT를 CLR 코드로 컴파일하며 일부 시나리오에서 네이티브 XSLT 엔진보다 성능이 훨씬 좋다.
그러나 XSLT에서는 개발자가 C# 및 Visual Basic 지식을 활용할 수 없으며 복잡하고 다른 프로그래밍 언어로 코드를 작성해야 합니다.
C#(또는 Visual Basic) 및 XSLT와 같은 통합되지 않은 두 가지 개발 시스템을 사용하면 소프트웨어 시스템을 개발하고 유지 관리하기가 더 어렵습니다.

LINQ to XML 쿼리 식을 완전히 익히고 나면 강력한 LINQ to XML 변환 기술을 쉽게 사용할 수 있다.
기본적으로 다양한 소스에서 데이터를 가져와서 XElement 개체를 동적으로 생성하고 전체 데이터를 새 XML 트리로 어셈블하는 함수 생성을 사용하여 XML 문서를 만든다.
변환을 통해 완전히 새로운 문서가 생성될 수 있다.
LINQ to XML에서는 비교적 쉽고 직관적으로 변환을 생성할 수 있으며 생성되는 코드도 쉽게 읽을 수 있다.
따라서 개발 및 유지 관리 비용이 줄어듭니다.

LINQ to XML은 XSLT를 대체하기 위한 것이 아니다.
XSLT는 여전히 복잡하고 문서 중심적인 XML(특히 문서 구조가 제대로 정의되지 않은 경우) 변환에 사용할 수 있다.

XSLT는 W3C(World Wide Web Consortium) 표준이므로 표준 기술만 사용해야 하는 요구 사항이 있는 경우 XSLT가 더 적합할 수 있다.
XSLT는 XML이므로 프로그래밍 방식으로 조작할 수 있다.

3.2.3 LINQ to XML과 MSXML 비교
MSXML은 Microsoft Windows에 포함된 XML을 처리할 수 있는 COM 기반 기술.
MSXML은 XPath 및 XSLT를 지원하는 DOM의 기본적인 구현을 제공하며 캐시하지 않는 이벤트 기반의 SAX2 파서도 포함하고 있다.
MSXML은 성능이 좋으며 대부분의 시나리오에서 기본적으로 안전.
AJAX 스타일의 응용 프로그램에서 클라이언트측 XML 처리를 수행하기 위해 Internet Explorer에서 MSXML에 액세스할 수 있다.
C++, JavaScript 및 Visual Basic 6.0을 비롯한 COM을 지원하는 모든 프로그래밍 언어에서 MSXML을 사용할 수 있다.
CLR(공용 언어 런타임) 기반의 관리 코드에서는 MSXML을 사용하지 않는 것이 좋다.

3.2.4 LINQ to XML과 XmlLite 비교
XmlLite는 캐시하지 않으며 정방향으로만 작동하는 가져오기 파서.
개발자는 주로 C++와 함께 XmlLite를 사용.
관리 코드와 함께 XmlLite를 사용하는 것은 권장되지 않는다.
XmlLite의 주요 이점은 대부분의 시나리오에서 안전하며 간단하고 빠른 XML 파서다.
 XmlLite에서 위협에 노출되는 영역은 매우 작다.
신뢰할 수 없는 문서의 구문을 분석해야 하고 서비스 거부나 데이터 노출과 같은 공격으로부터 보호하려면 XmlLite를 선택하는 것이 좋다.
XmlLite는 LINQ(통합 언어 쿼리)와 통합되지 않았으며 LINQ에 핵심적인 프로그래머 생산성 향상 기능을 제공하지 않는다.

Introduction

Text files are easy way to store data in either comma separated values or separated by new line. While working with many application usually we come across situations where we need to read and write data from / into text files. This article describes different approach of reading and writing data into the text files.

Essentials

In order to work with reading and writing data from / into text file, you will have to use System.IO namespace. You need to specify the path of the file to read from and write data into as Physical path on your hard driver. If you know the physical path of your file you want to read or write, you can specify as follows

string fileNameWithPath = @"E:\Csharp\temp\withsinglelinecode.txt";

If you do not know or not sure the path of the file from the drive or you want to create the text file in your applications sub folders, you can get the physical file path by using Server.MapPath method and passing the location of the file from your root directory of the application.

string fileNameWithPath = Server.MapPath("temp/withsinglelinecode1.txt");

In order to create a new line into the text file, you can use System.Environment.NewLine or "\r\n", to place a tab space you can use "\t". Reading data from text file and showing on the web page, will not automatically convert \r\n to new line, you will have to replace these characters with the <br /> as shown below.

litText.Text = contents.Replace("\r\n", "<br />");

Here litText is the Literal control I have placed in my .aspx page to show the contents of the file.

Reading and Writing with Single line of code


To simply write and read all contents of any any text file, you can use following one line code.

C# Code

 string data = "This is the first line . \r\n" 
    + "This is the second line. next two line space \r\n\r\n"
    + "\t This comes after a tab space";

// writing content into a text file with a single line of 
codeFile.WriteAllText(fileNameWithPath, data);

// reading content from a textfile with single line of codestring 
contents = File.ReadAllText(fileNameWithPath);

// Format the data with new line so that on the web page it appears in the new line
litText.Text = contents.Replace("\r\n", "< br />");

In the above code snippet, File.WriteAllText method accepts following parameters

  1. fileNameWithPath - physical location of the file with its name
  2. data - contents to write into the text file

File.ReadAllText method accepts following parameters

  1. fileNameWithPath - physical location of the file with its name

In the remaining lines, I have replaced the new line characters with the "<br />" so that it appears as next line in my web page.

Appending contents into a File and Reading data one by one

C# Code

 // Append data into the text file
using (StreamWriter writer = File.AppendText(fileNameWithPath))
{
    // writer.Write method will simply append the contents into the text // file without creating a new line
    writer.Write("NEW LINE ADDED without Creating a line line. ");
    writer.Write("This line will not appear into new line. ");
    
    // writer.WriteLine method append the contents into the text file 
    // and add a new line so that next appended contents appear in new line
    writer.WriteLine("Appended into above line but next appended contents will appear into a new line. ");
    writer.Write("This should appear in the next line. ");
}

// Read data from text file
using (StreamReader reader = File.OpenText(fileNameWithPath))
{
    // reads a single line of contents
    contents = reader.ReadLine();

    // reads complete contents of the the file
    contents += reader.ReadToEnd();
}

litText.Text = contents

In the above code snippets, I am using StreamWriter to append the contents into the file. File.AppendText method accepts physical location of the file with its name and returns StreamWriter, that can be used to append contents into the text file. This can be done using Write method or WriteLine method.

  1. writer.Write() - method accepts contents to append and simply append the contents at the end of the file.
  2. writer.WriteLine() - method accepts contents to append and add a new line after appending contents so that next appended contents appears in the new line.
Reading and Writing using FileStream

C# Code

// using File Stream
fileNameWithPath = Server.MapPath("temp/filestream.txt");

// writing contents using FileStream
using (FileStream stream = File.Open(fileNameWithPath, FileMode.OpenOrCreate))
{
     // writing data
     byte[] info = new UTF8Encoding(true).GetBytes(data);
     stream.Write(info, 0, info.Length);
}
// Reading contents using FileStream
using (FileStream stream = File.OpenRead(fileNameWithPath))
{
     // reading data
     StringBuilder strb = new StringBuilder();
     byte[] b = new byte[stream.Length];
     UTF8Encoding temp = new UTF8Encoding(true);

     while (stream.Read(b, 0, b.Length) > 0)
     {
           strb.Append(temp.GetString(b)); 
     }

     // write the contents of the file now
     Response.Write("Using FileStream 
" + strb.ToString() + "
"); }

FileStream object gives you extra control over how you want to read or write data from / into a file. This also let you specify the mode of the file to access like in Append mode, or Create mode or only Read mode. Also you can specify Type of access you want to have as third parameter of the File.Open() method.

FileStream buffers input and output for better performance. In order to use FileStream for reading and writing, you will need to use System.Text namespace as encoding related objects like UTF8Encoding contains in this namespace.

For detailed explanations FileStream, please visit MSDN.

'Framework' 카테고리의 다른 글

LINQ 자료  (2) 2009.10.09
C# Collection Classes (부제:List 와 ArrayList 의 차이 )  (0) 2009.10.06
[펌]Mircrosoft Office 2007 Groove 사용 방법  (0) 2009.09.02
XML 특수문자 처리  (0) 2009.08.11
.NET Naming Rule Guidelines  (0) 2009.07.02

What is Cryptography?

암호화란 데이터를 보호 하는데 사용되며 그 외에도 유용하게 많은 곳에서 사용된다.
이는 데이터가 보여지고, 수정되어지는 것을 방지하고, 생성자의 데이터에 대한 무결성을 보장하기 위해 사용되어진다.
암호화는 데이터를 암호화 하여 암호화 된 상태에서 네트워크 상에 전송하며 그리고 수신측에서 다시 데이터를 암호 해독함으로써, 인터넷과 같은 안전하지 않은 네트워크 상에서 보안 통신을 제공하는 메커니즘을 제공한다.
Encryption은 데이터베이스에 저장된 암호와 같은 정보를 사람이 읽을수 있고 이해할 수 있게 하는 것을 방지하는 추가적인 보안 메커니즘에 이용되어 질수 있다.

Encryption Component
인크립션은 데이터를 암호화하고 해독하는 키와 조합된 암호화 알고리즘의 사용을 포함한다.
모든 암호화 알고리즘의 목적은 적절한 키 없이는 데이터를 암호해독 하는 것을 가급적 어렵게 만드는데 있다.
Microsoft .NET 프레임워크 클래스(System.Security.Cryptography)는 암호화에 대한 상세정보를 관리해 줄것이다.
이 클래스들은 동일한 인터페이스로 구현된다. ; 그래서 이 클래스들을 가지고 작업하는 것은 Cryptography 네임스페이스를 통해 하는 것과 동일하다.
프레임워크내의 어떤 클래스들은 단지 Microsoft CryptoAPI에 존재하는 알고리즘에 대한 래퍼이다. 다른 클래스들은 그 주요한 알고리즘에 대한 구현을 관리한다.

Public-Key Encryption
비대칭 암호화를 알려진 공개키 암호화는 공개키와 비밀키 쌍을 이용하여 데이터를 암호화하고 암호해독한다.
공개키는 누구나 사용할 수 있도록 되어있으며, 비밀키 소유자에게 보내질 데이터를 암호화 하는데 사용된다.
비밀키는 그 이름이 의미하듯 기밀을 유지한다. 비밀키는 데이터를 암호해독하는데 사용되며 오직 정확한 공개키가 데이터 암호화에 사용된 경우에만 작동하게 된다.
비밀키는 공개키로 암호화된 데이터를 암호해독 하는데 사용될수 있는 유일한 키이다.
키들은 여러 번 사용될수 있게 저장될수 있으며 또는 한번만 사용되기 위해 생성 될수도 있다.

비대칭 암호화 알고리즘은 대게 단지 적은 양의 데이터를 암호화하는데 효율적이다.
아래의 공개키 알고리즘은 .NET Framework 상에서 사용될 수 있다.

- Digital Signature Algorithm(DSA)
- RSA

Private-Key Encryption
대칭 암호화로 알려진 비밀키 암호화는 정보를 암호화하고 암호해독하는데 하나의 키를 이용한다. 키는 인증되지 않은 자에 의해 데이터가 암호해독되어 데이터가 손상되지 않도록 기밀을 유지해야 하며. 비밀키 알고리즘은 상대적으로 빠르며 거대한 데이터 스트림을 암호화하고 암호해독하는데 사용될수 있다.
비밀키 알고리즘은 block ciphers로 알려져 있다. 왜냐하면, 이 알고리즘은 데이터를 한번에 한 블록씩 암호화하기 때문이다.
Block ciphers는 알고리즘과 키에 기반하여 동일한 입력블럭을 동일한 출력블럭으로 암호화한다. 만일 데이터의 구조를 누군가 알고 있다면, 패턴이 인식되어 키가 역공학되어 질수 있는 가능성이 있다. 이를 해결하기 위해, .NET Framework 상의 클래스들은 Chaining이라 알려진 처리를 이용한다. 이는 이전 블록의 정보가 현재 블록을 암호화하는데 사용되도록 한다. 이는 키가 발견되지 않도록 방지한다. 이는 첫 블록의 데이터를 암호화 하는데 초기화 백터(Initialization vector : IV)를 요구한다.

아래의 비밀키 알고리즘은 .NET Framework 상에서 이용가능한 것이다. 각각의 상세내용에는 각 알고리즘에 대한 기본적인 정보를 담고 있으며 장, 단점을 포함하고 있다.

- Data Encryption Standard(DES) 알고리즘은 데이터를 64 bit 블록으로 암호화하고 해독하는데 64bit 키를 이용한다. 키가 64bit라 할지라도 효율적인 키 강도는 단지 56 bit이다. 적당한 시간안에 모든 DES 키를 검색할수 있는 하드웨어 장비가 존재한다. 이것은 DES 알고리즘이 깨어질수 있게 하였고, 그래서 이 알고리즘은 쓸모없는 것으로 취급되었다.
- RC2는 가변 키 길이 block cipher이다. 키 길이는 8bit에서 64bit로 가변적일 수 있다. 이는 특히 DES보다 안전한 대안으로서 설계되었다. 처리시간은 DES에 비해 2, 3배 빨라졌다. 하지만 .NET Framework 에서 사용가능한 RC2CryptoServiceProvider는 8 문자 또는 64bit 키로 제한되었다. 8문자 제한은 이 알고리즘이 DES가 가졌던 공격위험성을 그대로 가지게 하였다.
- TripleDES 알고리즘은 DES알고리즘의 세가지 계승적인 반복을 사용한다.
- 이 알고리즘은 2개 키, 3개 키를 사용한다. DES알고리즘과 같이 키 길이는 64bit 에 유효 키강도는 56bit이다. TripleDES 알고리즘은 DES 알고리즘의 결점을 정정하기 위해 설계되었다. 그러나 세 반복(Three Iteration)은 처리시간을 DES보다 세배 정보 느리게 하는 결과를 낳았다.
- Advanced Encryption Standard(AES) 알고리즘의 하나인 Rijndeal 알고리즘은 DES 알고리즘의 대안으로서 설계되었다. 키 강도는 DES보다 강하고 DES보다 나은 성능을 위해 설계되었다. 키의 길이는 128, 192, 256 bit로 가변적일수 있다. 이 알고리즘은 가장 신뢰할수 있으며 이번 칼럼 내용의 예제에서 사용하겠다.

Hashing Algorithm
Hashing은 특정 길이의 데이터를 고정 길이 바이트 시퀀스로 맵핑하는 것을 의미한다.
입력이 타이핑 테스트 이거나 법전의 내용이든 상관없이 결과는 동일 크기로 나타날 것이다.
해싱은 독특한 결과를 생산한다. 똑 같은 두개의 눈송이가 존재하지 않듯이 두개의 입력 조합이 동일한 해시 결과를 생산할 수는 없다. 입력이 하나의 문자가 다를지라도 다른 결과를 생산한다. .NET Framework는 아래의 해시 알고리즘에 대한 자원을 제공한다.

- HMACSHAI
- MACTripleDES
- MD5CryptoServiceProvider
- SHA1Managed
- SHA256Managed
- SHA384Managed
- SHA512Managed

How to Generate a Key and IV for Private-key Encryption
각 알고리즘은 사용되는 특정 키 사이즈를 가지고 있다. 각 키는 미리 정의된 크기, 전형적으로 1 문자(8bit) 에서 32문자(256bit)의 범위를 가지는, 에 맞추어야 한다.
어떤 알고리즘은 다양한 키 사이즈를 지원한다. 그러나 특정 알고리즘의 키 사이즈의 유효한 범위 내에 있어야만 한다. 우리는 128, 192, 256 bit key를 지원하는 RiJndeal를 사용할 것이다. 128 bit 키를 만드는 방법은 해싱 알고리즘의 하나를 이용해서 만든다.
아래의 code outline에서 클래스는 입력구를 받아서 키를 생성하고 IV를 생성하는 메서드를 포함한다.


// Initialize internal values
this._Key = new byte[24];
this._IV = new byte[16];

// Perform a hash operation using the phrase. This will generate
// a unique 48 character value to be used as the key and IV.
byte[] bytePhrase = Encoding.ASCII.GetBytes(Phrase);
SHA384Managed sha384 = new SHA384Managed();
sha384.ComputeHash(bytePhrase);
byte[] result = sha384.Hash;

// Transfer the first 24 characters of the hashed value to the key
// and 16 more characters for the initialization vector.
for( int loop=0; loop<24; loop++ ) this._Key[loop] = result[loop];
for( int loop=24; loop<40; loop++ ) this._IV[loop-24] = result[loop];


How to Use Private-Key Encryption
아래의 클래스는 데이터를 암호하 하는데 사용되는 비밀키를 생성하기 위해 해싱을 하는 방법을 데모한다. 암호해독 메서드는 암호화 함수에서 사용된 것의 역 함수를 사용함으로써 간단히 이용될수 있다.


using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

internal class MyEncryptor
{
// Internal value of the phrase used to generate the secret key
private string _Phrase = "";
/// Set the phrase used to generate the secret key.
public override string Phrase
{
set
{
this._Phrase = value;
this.GenerateKey();
}
}

// Internal initialization vector value to
// encrypt/decrypt the first block
private byte[] _IV;

// Internal secret key value
private byte[] _Key;

///
/// Constructor
///

/// Secret phrase to
generate key
public MyEncryptor(string SecretPhrase)
{
this.Phrase = SecretPhrase;
}

///
/// Encrypt the given value with the Rijndael algorithm.
///

/// Value to encrypt
/// Encrypted value.
public string Encrypt(string EncryptValue)
{
CryptoStream encryptStream = null; // Stream used to encrypt
RijndaelManaged rijndael = null; // Rijndael provider
ICryptoTransform rijndaelEncrypt = null; // Encrypting object
MemoryStream memStream = new MemoryStream(); // Stream to contain
// data

try
{
if( EncryptValue.Length > 0 )
{
// Create the crypto objects
rijndael = new RijndaelManaged();
rijndael.Key = this._Key;
rijndael.IV = this._IV;
rijndaelEncrypt = rijndael.CreateEncryptor();
encryptStream = new CryptoStream(
memStream, rijndaelEncrypt, CryptoStreamMode.Write);

// Write the encrypted value into memory
byte[] input = Encoding.UTF8.GetBytes(EncryptValue);
encryptStream.Write(input, 0, input.Length);
encryptStream.FlushFinalBlock();

// Retrieve the encrypted value and return it
return( Convert.ToBase64String(memStream.ToArray()) );
}
else
{
return "";
}
}
finally
{
if( rijndael != null ) rijndael.Clear();
if( rijndaelEncrypt != null ) rijndaelEncrypt.Dispose();
if( memStream != null ) memStream.Close();
}
}

/*****************************************************************
* Generate an encryption key based on the given phrase. The
* phrase is hashed to create a unique 32 character (256-bit)
* value, of which 24 characters (192 bit) are used for the
* key and the remaining 8 are used for the initialization
* vector (IV).
*
* Parameters: SecretPhrase - phrase to generate the key and
* IV from.
*
* Return Val: None
***************************************************************/
private void GenerateKey(string SecretPhrase)
{
// Initialize internal values
this._Key = new byte[24];
this._IV = new byte[16];

// Perform a hash operation using the phrase. This will
// generate a unique 32 character value to be used as the key.
byte[] bytePhrase = Encoding.ASCII.GetBytes(SecretPhrase);
SHA384Managed sha384 = new SHA384Managed();
sha384.ComputeHash(bytePhrase);
byte[] result = sha384.Hash;

// Transfer the first 24 characters of the hashed value to the key
// and the remaining 8 characters to the intialization vector.
for( int loop=0; loop<24; loop++ ) this._Key[loop] = result[loop];
for( int loop=24; loop<40; loop++ ) this._IV[loop-24] = result[loop];
}
}


ASP.NET

·         Extending the GridView to Include Sort Arrows: Scott Mitchell has a nice article that describes how to add a visual indicator to the GridView control to indicate the current sort order on columns.

·         Using ASP.NET 3.5's ListView and DataPager Controls: Sorting Data: Scott Mitchell continues his ListView control series with a good article on enabling sorting scenarios with the new ListView control.

·         Building a Grouping Grid with the ListView and LinqDataSource Controls: Matt Berseth has an awesome post that shows off using the new ListView control and LinqDataSource controls to build a hierarchical grouping grid.  A post to bookmark.

·         Using the ListView, DataPager and LinqDataSource Controls: Matt Berseth has a good tutorial post that shows off using these new controls to join data from two database tables using LINQ.

·         Some ASP.NET 3.5 ListView Control Examples: Mike Ormond has a nice post that provides a number of samples that show how to use the new ASP.NET ListView control.  For even more ListView articles, check out my last link-listing post which pointed to a bunch of them.

·         Large File Uploads in ASP.NET: Jon Galloway has a nice post that provides some good details on handing large file uploads using ASP.NET.

ASP.NET AJAX

·         Four ASP.NET AJAX JavaScript UI Methods You Should Learn: Dave Ward has another great post in his series about ASP.NET AJAX's client-side JavaScript Helper Methods.

·         Five Tab Themes Created for the ASP.NET AJAX Control Toolkit: Matt Berseth posts some really cool themes created for the ASP.NET AJAX Control Toolkit's Tab control. Very slick!

·         CNN Style Scrolling Ticker with the Marquee Toolkit Control: Matt Berseth posts another great one that shows how to implement a scrolling marquee UI using the ASP.NET AJAX Control Toolkit.

Visual Studio

·         Did You Know?: Lisa Feigenbaum from the VB team has posted a really cool series of blog posts that talk about some of the new VS 2008 editor and IDE features.  Read Part 1: Intellisense Everywhere, Part 2: IntelliSense is now Transparent, Part 3: Ctrl+Tab to Navigate Windows, Part 4: What You Can Do with Debugger DataTips, and Part 5: VB IntelliSense now filters as you type.

·         Web Server Settings for ASP.NET Web Application Projects can now be stored per user as well as per project: The VS Web Tools Team has a nice post that describes how you can now store web server settings per-user instead of per-project.  This is very useful for multi-developer scenarios (where you don't want to check-in these values into source control).

·         Using Ctrl-Break to Stop VS Building: Steven Harman points out a cool tip/trick, which is that you can use the Ctrl-Break key within Visual Studio to kill the current compilation build.  A useful tip if you've accidentally kicked off a long build or get tired waiting for it to finish.

·         Visual Studio 2008 Trouble Shooting Guide: If you run into any issues installing VS 2008, make sure to check out this blog post.  It details a bunch of common causes of failures, and how to fix them.

.NET

·         Marshaling between Managed and Unmanaged Code: Yi Zhang and Xiaoying Guo from my team in Shanghai have written a great MSDN article that describes how to use the marshaling interop features of the CLR to call native code.  One of the tools they highlight is an awesome P/Invoke Interop Assistant application they built that makes it much, much easier to generate p/invoke interop signatures when calling native methods.  A must-have tool for anyone doing native/managed interop!

·         .NET Framework 3.5 Poster: Brad Abrams posts about the cool new .NET Framework 3.5 posters now available for download (now in multiple file formats).

IIS

·         Microsoft Web Deployment Tool Technical Preview 1: Yesterday the IIS team posted the first preview of a new Microsoft Web Deployment tool.  This tool works with both IIS6 and IIS7 and enables automated deployment, synchronization, and migrating of applications on web servers.  If you are looking for a great way to automate the deployment of your ASP.NET applications then this tool is definitely one to check out.  To learn more, read the walkthroughs at the bottom of this page (in particular the "Introduction to MS Deploy" one).  This tool is awesome and should make automated deployment much easier.
Hope this helps,
Scott

+ Recent posts