마이크로소프트 윈도우10에서 Visual Studio 2017로 개발할 때 여러 앱을 개발할때 원활한 개발환경을 제공받기 위해서는 윈도우 개발자 모드를 켜야 한다.

특히 UWP 앱을 개발하려면 반드시 활성화를 해야 한다. 윈도우 마켓에서 허가되지 않은 앱도 실행이 가능하기 때문이다.


개발자 모드를 활성화 하기 위한 방법이다.


1. Windows 설정에 들어가 업데이트 및 보안을 선택한다.

 



2. 왼쪽 택에서 개발자용을 선택한다.




3. 개발자 모드를 선택한다.


Posted by 꿈만은공돌
,

C#에서 비동기로 병렬 처리하는 방법에 대해서 알아보겠다.


1. 일반적인 함수호출(단일 스레드)

아래 코드는 3개의 함수(FuncA, FuncB, FuncC)를 순서대로 호출하는 코드이다.

세가지의 함수가 단일스레드로 호출하는데 총 걸리는 시간을 측정하여 출력한다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
namespace T
{
    class Program
    {
        static void FuncA()
        {
            Console.WriteLine("A함수 시작");
            Thread.Sleep(1000);
            Console.WriteLine("A함수 끝");
        }
        static void FuncB()
        {
            Console.WriteLine("B함수 시작");
            Thread.Sleep(2000);
            Console.WriteLine("B함수 끝");
        }
        static void FuncC()
        {
            Console.WriteLine("C함수 시작");
            Thread.Sleep(3000);
            Console.WriteLine("C함수 끝");
        }
        static void Main(string[] args)
        {
            var timer = Stopwatch.StartNew(); //시간측정 시작
            FuncA();           //A함수 호출
            FuncB();           //B함수 호출
            FuncC();           //C함수 호출
            timer.Stop();      //시간 측정 끝
            Console.WriteLine(timer.ElapsedMilliseconds + "s");
        }
    }
}
 
 
cs

- 출력 결과 -

A함수 시작

A함수 끝

B함수 시작

B함수 끝

C함수 시작

C함수 끝

6005s



2. 비동기 멀티 테스크 만들기

Thread 클르스를 이용하면 스레드를 만들고 관리할 수 있다. 하지만 이 Thread 클레스는 직접 사용하기가 까다로운 점이 많다.

그래서 C# 4.0부터 추가된 Task 클래스를 사용하면 스레드를 쉽게 생성하고 관리 할 수 있다.

아래 예제는 비동기로 3가지 함수를 각각 실행한다.

Task 클래스를 사용하여 객체를 선언하고 Start함수로 태스크를 시작한다. 그러면 스레드가 특정한 순서 없이 동시에 실행이 된다. 그래서 실행순서는 보장하지 않고 끝나는 시점은 딜레이가 가장 짧은 FuncA부터 종료하게 된다. Wait 함수로 해당 태스크가 종료될때까지 해당 코드에서 대기한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
 
namespace T
{
    class Program
    {
        static void FuncA()
        {
            Console.WriteLine("A함수 시작");
            Thread.Sleep(1000);
            Console.WriteLine("A함수 끝");
        }
        static void FuncB()
        {
            Console.WriteLine("B함수 시작");
            Thread.Sleep(2000);
            Console.WriteLine("B함수 끝");
        }
        static void FuncC()
        {
            Console.WriteLine("C함수 시작");
            Thread.Sleep(3000);
            Console.WriteLine("C함수 끝");
        }
        static void Main(string[] args)
        {
            Task taskA = new Task(FuncA); //태스크 객체
            Task taskB = new Task(FuncB);
            Task taskC = new Task(FuncC);
            var timer = Stopwatch.StartNew(); //시간측정 시작
            taskA.Start();         //태스크 A시작
            taskB.Start();         //태스크 B시작
            taskC.Start();         //태스크 C시작
            taskA.Wait();         //태스크 A 끝날때까지 대기
            taskB.Wait();         //태스크 B 끝날때까지 대기
            taskC.Wait();         //태스크 C 끝날때까지 대기
            timer.Stop();         //시간 측정 끝
            Console.WriteLine(timer.ElapsedMilliseconds + "s");
        }
    }
}
cs


- 출력 결과 -

C함수 시작

B함수 시작

A함수 시작

A함수 끝

B함수 끝

C함수 끝

3008s



3. 다양한 방법으로 태스크 만들기

위의 예제에서는 Task객체를 각각 생성해서 Start함수를 호출하였지만 아래 예제에서는 Task.Factory.StartNew(FuncA) 와 같은 방식이나 Task.Run(new Action(FuncB))와 같은 방식으로 객체를 생성과 동시에 Task를 시작시킨다.

그리고 각각의 Task들에 Wait함수를 호출하여 종료를 대기했지만 아래 예제에서는 Task 배열을 만들고 Task.WaitAll() 함수에 Task 배열을 파라미터로 넘겨줘서 한번에 대기시킬 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
 
namespace T
{
    class Program
    {
        static void FuncA()
        {
            Console.WriteLine("A함수 시작");
            Thread.Sleep(1000);
            Console.WriteLine("A함수 끝");
        }
        static void FuncB()
        {
            Console.WriteLine("B함수 시작");
            Thread.Sleep(2000);
            Console.WriteLine("B함수 끝");
        }
        static void FuncC()
        {
            Console.WriteLine("C함수 시작");
            Thread.Sleep(3000);
            Console.WriteLine("C함수 끝");
        }
        static void Main(string[] args)
        {
            var timer = Stopwatch.StartNew(); //시간측정 시작
            Task taskA = Task.Factory.StartNew(FuncA);  //쓰레드 A 생성 후 시작
            Task taskB = Task.Run(new Action(FuncB));   //쓰레드 B 생성 후 시작
            Task taskC = Task.Factory.StartNew(FuncC);  //쓰레드 C 생성 후 시작
            Task[] tasks = {taskA, taskB, taskC};       //쓰레드 종료까지 대기
            Task.WaitAll(tasks);
            timer.Stop(); //시간 측정 끝
            Console.WriteLine(timer.ElapsedMilliseconds + "s");
        }
    }
}

cs


- 출력 결과 - 

C함수 시작

A함수 시작

B함수 시작

A함수 끝

B함수 끝

C함수 끝

3008s



4. A스래의 결과를 B스래드에 넘겨주기

A스레드를 실행하여 결과를 받아서 B스레드를 실행하면서 값을 넘겨주는 예제이다.

ContinueWith 함수를 사용한다.

TestA 함수를 StartNew 함수로 태스크를 만들어서 스래드를 실행하고 그 결과 값을 ContinueWith 함수로 TestB에 Result로 결과 값을 넘겨준다. 값을 받아 새로운 스레드를 만들어 실행한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using System;
using System.Threading;
using System.Threading.Tasks;
 
namespace T
{
    class Program
    {
        static int TestA()
        {
            Console.WriteLine("TestA Start");
            Thread.Sleep(1000);
            Console.WriteLine("TestA End");
            return 10;
        }
        static int TestB(int i)
        {
            Console.WriteLine("TestB Start");
            Thread.Sleep(1000);
            Console.WriteLine("TestB End");
            return i + 10;
        }
        static void Main(string[] args)
        {
            var test = Task.Factory.StartNew(TestA)
                                .ContinueWith(task=>TestB(task.Result));
            Console.WriteLine("result : " + test.Result);
        }
    }
}
 
cs


- 출력 결과 -

TestA Start

TestA End

TestB Start

TestB End

result : 20


5. 중첩 태스크(Task)

아래 예제는 2개의 Task가 중첩되서 실행하는 코드이다.

Inner 태스크가 3초 딜레이가 있어 Outer 태스크가 먼저 종료되고 Inner 태스크가 종료된다. 

Inner 태스크가 완료되기를 기다리지 않는다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
 
namespace T
{
    class Program
    {
        static void Main(string[] args)
        {
            
            var outer = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Outer task 시작");
                var inner = Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("Inner task 시작.");
                    Thread.Sleep(2000);
                    Console.WriteLine("Inner task 종료.");
                });            
            });
            outer.Wait();
            Console.WriteLine("Outer task 종료.");
            Console.ReadLine();
        }
    }
}
cs


- 출력 결과 -

Outer task 시작

Outer task 종료.

Inner task 시작.

Inner task 종료.


Inner 태스크가 완료되기를 기달리고 Outer 태스크가 완료되게 하려면 Inner 태스크에 TaskCrationOption의 AttachedToParent 값을 추가해준다.

아래는 그 예제이다. Inner 클래스가 먼저 종료되는 것을 볼 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
 
namespace T
{
    class Program
    {
        static void Main(string[] args)
        {
            
            var outer = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Outer task 시작");
                var inner = Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("Inner task 시작.");
                    Thread.Sleep(2000);
                    Console.WriteLine("Inner task 종료.");
                }, TaskCreationOptions.AttachedToParent);            
            });
            outer.Wait();
            Console.WriteLine("Outer task 종료.");
            Console.ReadLine();
        }
    }
}
 
cs


- 출력 결과 -

Outer task 시작

Inner task 시작.

Inner task 종료.

Outer task 종료.


멀테스레드에서 동기화하는 방법은 아래 링크를 참고하자

2018/07/17 - [닷넷,C#,.NET] - C# 멀티스레드 공유 리소스 동기화



참고 자료 : 크로스 플랫폼 개발을 위한 C# 7과 닷넷 코어 2.0 책

Posted by 꿈만은공돌
,

C#에서는 여러가지 방식으로 인코딩 할 수 있도록 Encoding 클래스를 제공해준다.


System.Text 네임스페이스 선언을 해야한다.


Encoding 클래스에 .을 누르면 아래 그림과 같이 여러가지 지원하는 인코딩 방식을 볼 수 있다.



"ABC123 abc!" 문자열을 byte 배열로 인코딩 하는 예제이다.

ASCII(아스키2) 방식의 예제이다. 9번째줄 ASCII 대신에 UTF-8이나 기타 다른 방식에 인코딩 방식을 적어주면 여러 방식에 인코딩으로 변환이 가능하다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
using System.Text;
namespace TEST
{
    class Program
    {
        static void Main(string[] args)
        {
            Encoding encoding = Encoding.UTF8;
            string str = "ABC123 abc!";
            byte[] encoded = encoding.GetBytes(str);
            foreach(byte b in encoded){
                Console.Write(b+" ");
            }
            Console.WriteLine();
        }
    }
}
 
cs



- 출력 결과 -

65 66 67 49 50 51 32 97 98 99 33

Posted by 꿈만은공돌
,

C#에서 파일 이름, 확장자. 크기, 수정일자 등을 알아낼 수 있습니다.


1. 파일 이름, 확장자 출력

네임스페이스 using System.IO를 포함시켜줍니다. Path 클래스를 사용하면 아래 예제처럼 파일이름을 알 수 있습니다. 이외에도 Path에는 임시폴더를 만들거나 랜덤으로 이름과 확장자를 만들 수도 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using System.IO;
 
namespace TEST
{
    class Program
    {
        static void Main(string[] args)
        {            
            string file = @"C:\test\aaa.txt";
            System.Console.WriteLine($"파일이름+확장자 : "
                             + Path.GetFileName(file));
            System.Console.WriteLine($"파일 이름 (확장자없이): " 
                             + Path.GetFileNameWithoutExtension(file));
            System.Console.WriteLine($"확장자 : " 
                             + Path.GetExtension(file));
        }
    }
}
 
cs

- 출력결과 - 

파일이름+확장자 : aaa.txt

파일 이름 (확장자없이): aaa

확장자 : .txt



2. 파일 크기, 접근 일자, 속성 등 출력

위의 예제와 마찬가지로 using System.IO를 포함시켜주어야 합니다.

FileInfo 를 이용해 정보를 얻어올 수 있습니다. 아래 예제와 같이 사용하면 됩니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;
using System.IO;
 
namespace TEST
{
    class Program
    {
        static void Main(string[] args)
        {            
            string file = @"C:\test\aaa.txt";
            var info = new FileInfo(file);
            System.Console.WriteLine("파일 사이즈: " + info.Length+" Bytes");
            System.Console.WriteLine("생성 시간 : " + info.CreationTime);
            System.Console.WriteLine("수정 시간 : " + info.LastWriteTime);
            System.Console.WriteLine("접근 시간 : " + info.LastAccessTime);
            System.Console.WriteLine("읽기전용 파일 어부 : " + info.IsReadOnly);
            System.Console.WriteLine("디렉터리 이름 : " + info.DirectoryName);
            System.Console.WriteLine("파일 이름 : " + info.Name);
            System.Console.WriteLine("경로+파일 이름 : " + info.FullName);
        }
    }
}
cs


- 출력결과 - 

파일 사이즈: 50 Bytes

생성 시간 : 2018-06-25 오후 8:49:50

수정 시간 : 2018-07-05 오후 9:15:04

접근 시간 : 2018-06-25 오후 8:49:50

읽기전용 파일 어부 : False

디렉터리 이름 : C:\test

파일 이름 : aaa.txt

경로+파일 이름 : C:\test\aaa.txt


아래는 C#에서 파일 및 디렉터리 생성 및 복사 삭제 등에 관한 포스팅 입니다.

2018/07/03 - [닷넷,C#,.NET] - C# 파일 및 디렉터리 생성, 삭제, 복사, 존재여부 검사 등


Posted by 꿈만은공돌
,

C#에서 디렉터리나 파일을 읽고 쓰고 하는등에 클래스에 대해서 알아보자.



1. 디렉터리 존재여부 알기


네임스페이스 using System.IO 를 포함시키고 System.IO.Directory.Exists(string path) 함수를 사용하면 된다.

함수에 이름이 너무 길다면 using static System.IO.Directory; 를 하면 Exists() 만을 사용하면 된다.


아래 예제를 보자. 해당 디렉터리가 있는지 검사하는 예제이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.IO;
using static System.IO.Directory;
 
 
namespace TEST
{
    class Program
    {
        static void Main(string[] args)
        {            
            string dir = @"C:\test";
            if (Exists(dir))
            {
                System.Console.WriteLine("해당 폴더 존재");
            }
            else
            {
                System.Console.WriteLine("해당 폴더 없음");
            }
        }
    }
}
cs



2. 디렉터리 만들기, 삭제하기


디렉터리 생성 : System.IO.Directory.CreateDirectory(string path)

삭제 : System.IO.Directory.Delete(string path) 


아래 예제는 해당 디렉터리가 있으면 삭제하고 없으면 생성하는 예제이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
using System.IO;
using static System.IO.Directory;
 
namespace TEST
{
    class Program
    {
        static void Main(string[] args)
        {
            
            string dir = @"C:\test";
            if (Exists(dir))
            {
                
                Delete(dir);
                System.Console.WriteLine("폴더 삭제");
            }
            else
            {                
                CreateDirectory(dir);
                System.Console.WriteLine("폴더 만들기");
            }
        }
    }
}
cs



3. 파일 존재여부 검사


디렉터리와 다르게 파일을 다루는 것은 좀더 간단하다. File.Exists(string path) 함수를 사용하면된다.

디렉터리와 마찬가지로 네임스페이스 using System.IO 를 포함시켜준다.


아래 예제는 파일이 존재하는지 검사하는 예제이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;
using System.IO;
 
namespace TEST
{
    class Program
    {
        static void Main(string[] args)
        {
        
            string file = @"C:\test\aaa.txt";
            if (File.Exists(file))
            {
                System.Console.WriteLine("해당 파일 존재");
            }
            else
            { 
                System.Console.WriteLine("해당 파일 없음");
            }
        }
    }
}
cs



4. 파일 생성하기, 텍스트 쓰기, 복사


파일을 생성하는 함수는 File.CreateText(string path) 를 사용하여 만들 수 있다.

원형은 다음과 같다. StreamWriter CreateText(string path);

파일복사는 copy함수를 쓰는데 아래와 같은 원형을 가진다. 마지막 파라미터인 overwrite는 true면 해당 파일이 존재하면 덮어쓰기를 한다.

void Copy(string sourceFileName, string destFileName, bool overwrite);


아래 예제는 파일을 만들어서 텍스트를 쓰고 복사를 한다. StreamWriter 객체로 파일로 생성한 파일을 받아서 WriteLine 로 파일에 내용을 쓴다. 그리고 Dispose 함수로 파일을 닫아준다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using System.IO;
 
namespace TEST
{
    class Program
    {
        static void Main(string[] args)
        {            
            string textFile = @"C:\test\text.txt";
            string copyFile = @"C:\test\copy.txt";
 
            StreamWriter textWrite = File.CreateText(textFile); //생성
            textWrite.WriteLine("abcdefghijk"); //쓰기
            textWrite.Dispose(); //파일 닫기
 
            File.Copy(textFile, copyFile, true); //복사
        }
    }
}
cs



5. 파일 읽기


StreamReader 클래스를 사용하여 파일을 읽을 수 있다. 객체를 생성하여 File.OpenText(String dir)로 파일을 연다.

읽은 내용을 모두 읽는건 ReadToEnd()를 사용한다.

파일을 닫는건 Dispose()를 사용한다.


아래 예제는 이전 예제에서 복사한 copy.txt 파일을 읽어 화면에 출력한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;
using System.IO;
 
namespace TEST
{
    class Program
    {
        static void Main(string[] args)
        {            
            string copyFile = @"C:\test\copy.txt";
            StreamReader textReader = File.OpenText(copyFile);
            System.Console.WriteLine(textReader.ReadToEnd());
            textReader.Dispose();
        }
    }
}
 
cs

Posted by 꿈만은공돌
,


C#의 LINQ를 사용하면 데이터를 가공하고 추출하는데 편리하고 빠른 장점이 있다. 기본적으로 SQL을 사용해봤다면 직관적으로 쉽게 이해할 수 있을 것이다. 코딩을 배워보지 않은 사람도 쉽게 이해할 수 있도록 만들어진 언어가 SQL 이기 때문에 쉽게 배워 사용할 수 있다. 그리고 병렬로 처리도 가능하기 때문에 일반적으로 코드를 작성해서 데이터를 추출하는 가공방식보다 많은 이점이 있다. 


LINQ에 기본적인 질의방식에 대해서는 아래링크를 참고하자


- C# LINQ 사용 방법 및 예제(from, select, where, orderby) : http://hijuworld.tistory.com/56


우선 기본적인 합(Sum), 최대값(Max), 최소값(Min), 평균값(Average), 데이터 갯수(Count) 에 대해서 알아보자.


아래 예제는 1부터 9까지의 데이터를 가지고 있는 numbers라는 변수가 존재하는데 Linq 함수를 이용하여 집계를 하는 것을 볼 수 있다. 해당 예제는 배열을 가지고 작성했지만 List와 같은 C#에서 제공해주는 다양한 제너릭 클래스들에서 사용이 가능하다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;
using System.Linq;
namespace test
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] numbers = { 12345678};
            Console.WriteLine("sum : " + numbers.Sum());
            Console.WriteLine("max : " + numbers.Max());
            Console.WriteLine("min : " + numbers.Min());
            Console.WriteLine("average : "+ numbers.Average());
            Console.WriteLine("count : " + numbers.Count());
        }
    }
}
cs


네임스페이스인 using System.Linq 를 반드시 명시해줘야 한다.




Aggregate는 사용자가 직접 해당 데이터들을 어떻게 가공할지 명시 할 수 있다.


인자들의 합이나 최대값을 구하는 것이 아닌 가장 큰수와 가장 작은 수의 합이라던지 모든 수의 곱셈이라던지 등에 다양한 방식에 집계를 사용자가 직접 작성할 수 있는 장점이 있다. 잘만 사용하면 응용하여 사용할 수 있는 곳이 정말 많다.

람다식을 사용하여 함수의 내용을 쉽게 넘겨줄 수 있다. 


아래 예제는 모든 인자들의 곱을 계산하는 예제이다. 첫번째 인자와 두번째 인자를 곱해서 다시 첫번재 인자에 놓고 다시 그다음 번째 인자를 두번째 인자로 놓고 곱하는것을 반복한다. 


1
2
3
4
5
6
static void Main(string[] args)
{
    int[] numbers = { 1234};
    var data = numbers.Aggregate((num1, num2) => num1 * num2);
    Console.WriteLine(data);
}
cs

출력결과 : 120



아래 예제는 위의 예제와 동일하지만 Aggregate 함수에 파라미터를 앞에 하나 추가하였다.


첫번째 인자 하나는 고정하는 기능을 한다.


100 * 1 * 2 * 3 * 4 * 5 의 결과가 출력되게 된다. 해당 기능은 대소 비교를 할때 가장 처음에 기준값을 정할 때에도 사용이 가능하다.


1
2
3
4
5
6
static void Main(string[] args)
{
    int[] numbers = { 1234};
    var data = numbers.Aggregate(100, (num1, num2) => num1 * num2);
    Console.WriteLine(data);
}
cs


출력결과 : 12000



아래 예제는 첫번째 글자와 두번째 글자를 콤마를 사이에두고 연결한다.


1
2
3
4
5
6
static void Main(string[] args)
{
    String[] names = { "abc""kim""lee""han""park" };
    var data = names.Aggregate((str1,str2)=>str1+", "+str2);
    Console.WriteLine(data);
}
cs

출력 결과 : abc, kim, lee, han, park




이제는 좀더 응용해서 사용해보도록 하자.


아래는 학생들의 이름과 점수를 담고있는 객체들을 List로 가지고있는 데이터에서 학생들의 평균 점수를 구하는 예제이다. 클래스를 사용하여 학생의 이름과 점수를 저장할 수 있도록 하였다. 그리고 LINQ를 사용하여 데이터를 가공하는 것을 볼 수 있다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using System;
using System.Collections.Generic;
using System.Linq;
namespace test
{
    public class Student
    {
        public string name { get; set; }
        public int score { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            List<Student> students = new List<Student>
            {
                new Student { name = "zzz", score = 88 },
                new Student { name = "kim", score = 50 },
                new Student { name = "han", score = 70 },
                new Student { name = "park", score = 45 },
                new Student { name = "peter", score = 84 },
                new Student { name = "lee", score = 98 }
            };
            var result = (from student in students
                        select student).Average(student => student.score);
            Console.WriteLine("전체학생의 평균 : " + result);
        }
    }
}
 
 
cs


결과 : 전체학생의 평균 : 72.5



아래는 좀더 응용한 것으로 학생들의 이름이 4글자 이상인 학생들만 추출하고 그 학생들의 점수의 합을 구하는 예제이다. from으로 학생 하나씩 선택하여 where로 이름이 4글자인 학생만 추출하고 select함수로 학생의 정보를 받는다. 그리고 Sum 함수로 이름이 4글자인 학생에 점수들을 모두 합한다. 복잡해보이지만 하나하나 차근차근 보면 쉽게 이해가 갈 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System;
using System.Collections.Generic;
using System.Linq;
namespace test
{
    public class Student
    {
        public string name { get; set; }
        public int score { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            List<Student> students = new List<Student>
            {
                new Student { name = "zzz", score = 88 },
                new Student { name = "kim", score = 50 },
                new Student { name = "han", score = 70 },
                new Student { name = "park", score = 45 },
                new Student { name = "peter", score = 84 },
                new Student { name = "lee", score = 98 }
            };
            var result = (from student in students
                          where student.name.Length >= 4
                          select student).Sum(student => student.score);
            Console.WriteLine("이름이 4글자 이상인 학생들의 점수 합 : " + result);
        }
    }
}
 
 
cs


출력 결과 : 이름이 4글자 이상인 학생들의 점수 합 : 129


아래 예제는 group by 키워드를 사용하여 50점이상 학생과 미만 학생으로 나눈후 해당 학생들의 숫자와 평균, 최대값을 구하는 코드이다. Group by는 특정 조건으로 데이터들을 그룹을 지어주는 기능을 한다. 예를들어 1~100까지의 수가 있어서 group by를 사용하여 50이상과 미만으로 나누어 각각의 평균을 구할 수 있다. 자주 사용되기 때문에 알아두면 좋다. 그룹으로 나눈 각각의 그룹은 into 키워드를 사용하여 g로 나타낸다. 그래서 g.key 사용하여 참인지 거짓인지를 가지고 어느 그룹의 데이터인지 알 수 있다. 그래서 각 그룹의 평균과 최대값 등을 알아 낼 수 있다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using System;
using System.Collections.Generic;
using System.Linq;
namespace test
{
    public class Student
    {
        public string name { get; set; }
        public int score { get; set; }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            List<Student> students = new List<Student>
            {
                new Student { name = "zzz", score = 88 },
                new Student { name = "kim", score = 20 },
                new Student { name = "han", score = 70 },
                new Student { name = "park", score = 45 },
                new Student { name = "peter", score = 84 },
                new Student { name = "lee", score = 98 }
            };
 
            var result = from student in students
                         group student by student.score >= 50 into g //50점을 기준으로 나눔
                         select new
                         {
                             key = g.Key==true ?"50점이상":"50점미만"//50점이상데이터인지
                             count = g.Count(), //해당 구간의 학생수
                             avr = g.Average(student => student.score), //해당 구간의 평균점수
                             max = g.Max(student => student.score) // 해당 구간에 최대값
                         };
            
            foreach (var data in result)
            {
                Console.WriteLine(data.key + " 학생 수 : " + data.count);
                Console.WriteLine(data.key + " 중 최대점수 : " + data.max);
                Console.WriteLine(data.key + " 학생들의 평균 : " + data.avr);
                Console.WriteLine("------------------------------------");
 
            }
        }
    }
}
cs

출력결과는 아래와 같다. 50이상의 학생과 미만의 학생의 최대값과 학생수 평균등을 출력한다.


Posted by 꿈만은공돌
,


LINQ란 Language Integrated Query 라고해서 특정 데이터들에서 Query를 하여 데이터를 빠르고 편리하게 추출하는 방식이라 할 수 있다. 해당기능은 C# 3.0부터 추가가 되기 시작한 문법이다. 기본적으로 람다표현식을 사용하여 간결하고 가독성 좋게 작성 가능하다. Query를 하는데에는 SQL을 사용한다. SQL 이란 Structured Query Language의 약자이다. 


SQL에서 가장 많이 사용하는 문법은 다음 4가지 이다.


from : 어떤 데이터에서 찾을 것인가


where : 어떤 조건으로 찾을 것인가


order by : 어떤 항목을 기준으로 정렬할 것인가


select : 어떤 항목을 추출할 것인가


LINQ는 이러한 SQL의 문법을 가지고 다양한 쿼리를 통해 데이터를 가공하고 집계하는 등에 사용된다. 전통적인 방싱그로 for문과 if문을 가지고 특정 데이터들을 가공하고 집계내는 것도 가능하다. 하지만 LINQ를 이용하면 빠르고 정확하게 데이터를 찾는 것이 가능하다. 그리고 더욱 중요한 가독성이 좋다. 문장을 서술 하듯 질의를 하기 때문에 for와 if문을 사용하는 방식보다 가독성이 좋아 실수를 줄이고 유지보수가 쉽다.

또한 병렬로 처리도 가능하기 때문에 일반적으로 코드를 작성해서 데이터를 추출하는 가공방식보다 속도가 빠를 수 있다.


LINQ는 기본적으로 아래에서 소개한 예제만 보면 어느정도 이해가 갈 것이다. 더욱 복잡한 예제들도 많이 존재 하고 사용방법도 복잡한 것들도 많지만 우선 아래 예제들을 보고 LINQ가 무엇인지 이해하자.


아래 예제는 1부터 9까지의 수중에 3의 배수를 찾는 예제이다. from 문으로 nums에 저장된 1부터 9까지의 숫자중 하나씩 추출하며 where 로 3의 배수인지를 검사한다. 검사결과가 참인 데이터인 num을 select 문으로 추출한다. 그 결과를 변수 numQuery에 저장한다. foreach문으로 결과를 출력한다. LINQ를 사용한 데이터 가공에 가장 기본적인 형태의 예제이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;
using System.Linq;
namespace test
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] nums = new int[9] { 12345678};
            var numQuery = from num in nums
                           where (num%3== 0
                           select num;
            foreach(int num in numQuery) //결과 출력            
                Console.Write(num +" ");            
        }
    }
}
cs

출력결과 : 3 6 9




아래 예제는 길이가 3글자인 문자열을 찾는 것이다. where에서 string.length ==3 으로 문자길이가 3인 것을 찾는다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using System.Linq;
namespace test
{
    class Program
    {
        static void Main(string[] args)
        {
            String[] names = new String[]{ "abc""kim","peter""Tistory""Z"};
            var nameQuery = from name in names
                                  where name.Length == 3
                                   select name;
            foreach(string name in nameQuery) // 결과 출력
                Console.WriteLine(name);
        }
    }
}
 
cs

출력 결과

abc 

kim




아래 예제는 60점이상 학생만 이름순으로 출력하는 것이다. student 클래스를 선언하여 이름과 점수를 저장하도록 한다. List객체를 만들어서 학생들의 정보를 저장한다. 그래서 해당 객체에 점수가 60점이상인 학생들을 where 문으로 검사하여 추출하고 orderby를 사용하여 이름순으로 정렬 시킨다. 그리고 select 에서 학생의 이름만을 추출한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using System.Collections.Generic;
using System.Linq;
namespace test
{
    public class Student
    {
        public string name { get; set; }
        public int score { get; set; }
    } 
 
    class Program
    {
        static void Main(string[] args)
        {
            List<Student> students = new List<Student>
            {
                new Student { name = "zzz", score = 88},
                new Student { name = "kim", score = 50},
                new Student { name = "han", score = 76 },
                new Student { name = "park", score = 45 },
                new Student { name = "peter", score = 84 },
                new Student { name = "lee", score = 98 }
            };
            var smart = from student in students
                        where student.score >= 60 // 60점이상만
                        orderby student.name ascending//이름순으로 정렬
                        select student.name; //이름만 추출
 
            foreach(string studentName in smart)     //결과 출력        
                Console.WriteLine(studentName);
        }
    }
}
cs

출력 결과

han

lee

peter

zzz


orderby는 orderby 정렬기준 ascending 이런식으로 사용을 한다.

기본으로 오름차순(ascending)이며 내림차순으로 하고 싶다면 descending 을 써주면 된다. 디폴트는 오름차순인 ascending이기 때문에 반드시 명시할 필요는 없다.


이외에도 LINQ를 이용하면 합(Sum), 최대값(Max), 최소값(Min), 평균값(Average), 데이터 갯수(Count)들을 쉽게 계산할 수 있다. 아래 링크를 참고하도록 하자.

C# LINQ 집계함수(Sum,MAX,MIN,Average,count,Aggregate 등) : http://hijuworld.tistory.com/57

Posted by 꿈만은공돌
,

C#에 CLR(Common Language Runtime) 에서는 객체의 크기에 따라 메모리를 크게 두가지로 나눠서 관리한다. 


- 용량이 작은 객체는 (85KB 미만) SOH(Small Object Heap) 


- 용량이 큰 객체는 (85KB 이상) LOH(Large Object Heap)


가비지 컬랙터가 두 힙(Heap)에 대해서 서로 다른 방식으로 관리는 한다.


SOH의 경우는 3가지 세대로 나눠서 가비지 컬랙션을 진행한다.


자세한 내용은 아래 링크를 참고하자.


- 세대별 가비지 컬렉션 방식 : http://hijuworld.tistory.com/41


LOH의 경우는 C와 C++의 메모리 관리 방식과 비슷하게 관리한다.


- C, C++에서 메모리 할당과 해제 원리 : http://hijuworld.tistory.com/28



아래 그림을 참고하자.



이전에 C와 c++에서 메모리 할당방식을 설명하기 위해서 사용했던 이미지 이다.


다른것은 가장 작은 객체의 사이즈가 85KB 이상이란 것이고 메모리 해제를 가비지 컬렉터가 알아서 한다는 것이다.




SOH와 LOH 가장 큰 차이점은 다음과 같다.


1. SOH는 가비지 컬렉션이 발생하면 해제된 메모리 공간들에 사용중인 메모리들로 재배치하지만 LOH는 메모리해제를 해도 해제된 공간은 그대로 둔다. 

 이는 메모리 사이즈가 큰 LOH들의 경우 메모리의 위치를 재배치하는 것은 오버해드가 크기 때문이다. 그때문에 메모리 단편화가 일어날 수 있다.


2. SOH는 0세대, 1세대, 2세대로 3가지 세대로 나눠서 메모리 관리를 하지만 LOH는 2세대밖에 존재하지 않는다. 

 그래서 2세대 가비지 컬렉션이 일어날 때에만 사용하지 않는 메모리를 해제시킨다. 하지만 이때 많은 오버헤드가 발생하게 된다.


LOH때문이라도 2세대 가비지컬렉션이 되도록 일어나지 않도록 주의해서 객체를 생성해야 한다.


그리고 메모리 단편화 때문에 용량이큰 객체는 너무자주 생성하고 해제하고를 반복하면 안된다.


최악의 경우 메모리 할당에 실패하는 경우가 발생한다.


그래서 큰 메모리를 자주 할당하는 것은 주의가 꼭 필요하다.

Posted by 꿈만은공돌
,

class 선언앞에 sealed 키워드를 붙이면 해당 클래스의 상속을 막을 수 있다.



Test2에서 Test 클래스를 상속하려 했지만 sealed 때문에 에러가 발생하는 것을 볼 수 있다.

C#에서 대표적으로 string 클래스가 sealed 클래스이다.

따라서 string 클래스는 상속을 할 수 없다.


그리고 함수선언시에 sealed 키워드를 붙이면 상속할때 해당 함수를 오버라이딩을 할 수 없다.


sealed 클래스에 대해서 메서드를 추가하고싶다면 아래 링크를 참고하자.


C#/.NET 확장 메서드 기능, String에 메서드 추가 하기 : http://hijuworld.tistory.com/45

Posted by 꿈만은공돌
,

C#에서 구조체(struct)와 클래스(class)가 어떻게 다른지 알아보자.


사용하는데 있어선 차이를 알기 힘들다. 일반적으론 구조체는 함수가 없고 변수들만 저장 가능하고 클래스는 변수이외에 함수도 포함시킬 수 있다고 알고 있다. 하지만 구조체에도 함수를 포함 시킬 수 있다. 그렇다면 무슨차이가 있는지 보도록 하자. 


아래 두개의 코드를 보자.

-구조체 코드-


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Point
{
    public int X;
    public int Y;
    public int Z;
    public Point(int x, int y, int z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }
    public static Point operator +(Point p1, Point p2)
    {
        return new Point(p1.X + p2.X, p1.Y + p2.Y, p1.Z + p2.Z);
    }
    public String toString()
    {
        return "x: " + X + ", y: " + Y + ", z: " + Z;
    }
}
cs





-클래스 코드-


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Point
{
    public int X;
    public int Y;
    public int Z;
    public Point(int x, int y, int z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }
    public static Point operator +(Point p1, Point p2)
    {
        return new Point(p1.X + p2.X, p1.Y + p2.Y, p1.Z + p2.Z);
    }
    public String toString()
    {
        return "x: " + X + ", y: " + Y + ", z: " + Z;
    }
}
cs


코드상으론 선언을 하는 부분을 제외하면 아무런 차이가 없다.

클래스와 마찬가지로 구조체도 필드로 변수와 함수들을 가질 수 있으며 생성자 역시 가질 수 있다.


하지만 큰 차이 2가지가 존재한다.

1. 구조체(struct)는 상속을 할 수 없다.

2. 클래스(class) 객체는 힙(heap)에 할당되지만 구조체(struct) 객체는 스택(stack)에 할당된다.


두 가지 차이중 2번이 핵심이다. 

스택의 경우 사용할 수 있는 메모리 크기가 작고 한정적인 반면 힙은 많은 메모리크기를 가질 수 있다.

하지만 스택의 경우 가비지컬랙션에 의해 관리되지 않기 때문에 성능상 많은 장점이 있다.


실제 메모리 할당이 어떻게 되는지 아래 실험으로 보자.

우선 Main문 안에 아래와 같이 객체 1000개를 차례로 할당 받는 코드를 작성해보자.

Sleep(100)은 100ms 즉 0.1초간 딜레이는 거는 역할을 한다.


1
2
3
4
5
6
7
8
9
static void Main(string[] args)
{
    Point []p = new Point[1000];
    for (int i=0; i< 1000; i++)
    {
        p[i] = new Point(i, i + 1, i + 2);
        System.Threading.Thread.Sleep(100);
    }
}
cs

 


그리고 디버깅 모드로 실행시킨 후에 메모리 사용량을 보도록 하자. Visual studio 2017에서는 쉽게 메모리 사용양을 확인할 수가 있다. 중간중간 원하는 지점에서 메모리를 표시해줍니다. 개인적인 생각으론 C#이 가비지컬렉션이 완벽하지 않아서인지 메모리이슈가 많아서 해당기능을 강력하게 지원해주는 것 같다.


아래 구조체(struct)를 객체로 만들때에는 아래와 같이 힙 영역에 차이가 없는 것을 볼 수 있다. 



아래 클래스(class)의 경우 시간이 증가함에 따라 객체의 갯수와 힙 메모리의 크기역시 증가하는 것을 볼 수 있다. 




하지만 구조체로 선언을 했어도 객체를 힙영역에 메모리를 할당할 때가 있다. 


1. 모든 필드의 합이 16byte를 넘는 경우이다.

2. 구조체안에 클래스 타입을 필드로 가질 경우이다.


C#에서는 위의 두경우와 상속이 필요한 경우가 아닌 경우라면 구조체로 선언해서 스택(stack) 메모리 영역을 사용하기를 권장한다. 스택은 기본적으로 가비지 컬렉션의 대상이 아니다. 선언한 함수가 종료되면 그때그때 해제가 되기때문에 heap영역을 사용하는 것보다 좋은점이 많다고 생각된다. 가비지 컬랙션이 자동으로 메모리를 해제시켜주긴 하지만 많은 오버해드가 발생할 수가 있다. 가비지 컬랙션에 동작원리를 알면 이해가 갈 것이다. 

되도록 16바이트가 넘지 않는다면 구조체를 사용하는 버릇을 들이는 것이 좋다.


C, C++에서 메모리 할당과 해제 원리에 관한 포스팅 입니다. http://hijuworld.tistory.com/28


번외로 C++에서는 C#과 다르게 클래스는 public 선언을 하지 않으면 private이 되지만 구조체는 기본적으로 public 으로 된다.

Posted by 꿈만은공돌
,