Last active
October 15, 2018 06:12
-
-
Save Curookie/ba8a4a0343537108393d8c87082fe83a to your computer and use it in GitHub Desktop.
C# 기초
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[소개] | |
C/C++을 참조한 언어, 닷넷 전용언어 | |
약간 모든 언어의 짬뽕이라는 소리를 듣기도 한다. | |
[특징] | |
가비지 컬렉션 기능이 있다. | |
다중상속을 지원하지 않는다. | |
클래스간의 프렌드 지정을 할 수 없다. | |
디폴트 인수를 지원하지 않는다. | |
자료형에 대해 엄격하다. | |
자바에 JVM이 있다면 C#은 CLR(Common Language Runtime)에서 돌아간다. | |
CLR이 중간 언어(MS IL)를 기계어로 번역해준다. | |
오버라이딩 시 virtual 키워드를 붙여야한다. | |
가. 주요 개념 | |
1. 박싱(Boxing) / 언박싱(Unboxing) | |
C#은 2가지의 타입(Reference, Value)을 제공한다. | |
값 형식 : structs, 열거형, 숫자형식, bool 등 | |
참조 형식 : class, interface, delegate, dynamic, object, string 등 | |
박싱(Boxing) 이란? | |
Value 타입의 객체를 타입이 구체적으로 정의되어 있지 않는 | |
Reference 타입의 객체 내부로 포함하여 Reference 타입으로 사용이 가능하게끔 해주는 기능 | |
int i = 123; | |
object o = i; // c#에서는 자동 형변환이 가능 | |
박싱을 수행하게 되면 힙상에 새로운 참조 타입의 객체가 생성이 되고, (boxed 된 i) | |
값 타입 내부의 값이 참조 타입의 객체 내부에 복사된다. | |
언박싱(Unboxing) 이란? | |
언박싱은 박싱이 되어 있는 참조 타입의 객체 복사본을 다시 값 타입으로 돌리면서 다시 값 타입을 복사하여 전달하는 과정을 말한다. | |
int i = 123; | |
object o = i; | |
int j = (int)i; | |
위의 코드는 박싱된 코드를 언박싱하는 과정을 나타낸다. | |
박싱된 i의 객체에 대해서 객체의 인스턴스가 지정한 값 형식이 박싱된 값인지를 확인한 뒤에 인스턴스의 값을 값 형식 변수에 복사한다. | |
박싱을 고려할 때 아래의 코드를 생각해 봐야한다. 아래의 코드는 콘솔창에 Test Code : 10020을 출력하기 위한 코드. | |
Console.WriteLine("Test Code : {0}", 10020); | |
이 코드를 수행하면 컴파일러는 WriteLIne 함수의 인자로 변환할 때 WriteLIne은 항상 Object 형만 받기 때문에 10020이 object형으로 박싱됨. | |
// WriteLine 입력시 | |
int i = 10020 | |
object o = i; // boxing | |
그리고 WriteLIne을 수행하기 위해 Object 형을 String으로 변환하기 위해서 아래의 코드가 수행. | |
// WriteLine 내부 함수에서 UnBoxing 및 ToString() 적용 | |
int i = (int)o; // Unboxing | |
string output = i.ToString(); | |
즉 WriteLine에 숫자형을 문자열로 변경하기 위해서 총 박싱 1회, 언박싱 1회, ToString() 함수 호출 1회가 발생을 한다. | |
이러한 컴파일러의 수행 성능을 보완하기 위해서 사용자는 아래와 같은 코드를 고려해야한다. | |
Console.WriteLine("Test Code : {0}", 10020.ToString()); | |
2. as 와 is 키워드 | |
강제 형변환(cast),is 와 as는 비슷한 개념 하지만 cast보다 as를 사용하는게 더 좋다. | |
void EventHandler(object obj, EventArgs e) | |
{ | |
MyClass myClassByCast = (MyClass)obj; // cast사용 | |
MyClass myClassByAs = obj as MyClass; // as 사용 | |
} | |
as나 is 연산자는 런타임에 객체의 형 변환이 가능한지를 확인하기 위해서 사용자가 정의한 형 변환 연산자를 고려하지 않는다. | |
애초에 형 변환이 불가능 한 경우(객체가 변환 요청된 타입이거나 변환 요청타입으로부터 상속된 타입이 아닌 경우)에는 컴파일 때 에러를 발생한다. | |
반면 cast 연산의 경우 컴파일러는 객체를 변환 요청된 타입으로 변경할 수 있는 다양한 형 변환 연산자를 고려하여 형 변환을 수행하는 코드를 만들어낸다. | |
‘강제 형 변환의 경우 형 변환이 수행될 수도 있고 그렇지 않을 수도 있지만, as 키워드를 사용하면 형 변환이 100% 수행될 것이라 판단할 수 있다.’ | |
try | |
{ | |
MyClass myClassByCast = (MyClass)obj; // cast사용 | |
if (myClassByCast != null) | |
{ | |
// 객체 사용 | |
} | |
else | |
{ | |
// null일 경우 오류 보고 | |
} | |
} | |
catch | |
{ | |
// 형 변환 오류 보고 | |
} | |
MyClass myClassByAs = obj as MyClass; // as 사용 | |
if (myClassByAs != null) | |
{ | |
// 객체 사용 | |
} | |
else | |
{ | |
// null일 경우 오류 보고 | |
} | |
그렇다면 is키워드는 언제 사용하는 것 인가? | |
as키워드는 변환할 타입이 Reference형일 때만 사용할 수 있다. | |
즉 value타입은 as키워드를 사용하여 형 변환할 수 없으며, 이럴 때 강제 형 변환을 사용하게 된다. | |
하지만 위 내용과 같이 강제 형 변환은 형 변환 수행에 대한 보장을 하지 않기 때문에, 형 변환 가능 여부를 미리 살피기 위해 is키워드를 사용한다. | |
예를 들어 object형을 int로 변환하려 한다면 as키워드는 동작하지 않는다.(컴파일 에러) | |
object o = MyClass.GetValue(); | |
int i = (int)o; // 동작 | |
int j = o as int; // 에러 | |
이런 경우에 is연산자를 사용할 수 있다. | |
is 연산자를 사용함으로써 얻을 수 있는 이점은 위에서 말한 것과 같이 형 변환 수행동작을 보장할 수 있기 때문에 | |
굳이 try / catch를 사용하지 않아도 된다는 점이다. | |
object o = MyClass.GetValue(); | |
int i; | |
if(o is int) | |
i = (int)o; // 동작 | |
가. Reference 타입의 형 변환을 할 경우 as연산자를 사용하여 형 변환을 보장하자! | |
나. 강제 형 변환을 사용할 경우 is키워드를 사용하여 형 변환을 보장하자! | |
3. IComparable 인터페이스, IComparer (사용자 지정 정렬) | |
내가 비교할 객체보다 작을 경우, 음수를 리턴해야 한다. 보통 -1을 사용 한다. | |
내가 비교할 객체보다 같을 경우, 0을 리턴해야 한다. | |
내가 비교할 객체보다 클 경우, 양수를 리턴해야 한다. 보통 1을 사용 한다. | |
사용자 지정 정렬 할 때 사용하는 인터페이스로 자신과 입력 인자를 비교하여 결과를 반환하는 CompareTo 메서드를 약속하고 있다. | |
interface IComparable | |
{ | |
int CompareTo(object obj); | |
} | |
IComparer 인터페이스에는 두 개의 개체를 입력 인자로 받는 Compare 메서드를 약속하고 있다. | |
결국 비교를 담당하는 부분을 별도의 형식으로 정의하는 것. | |
interface IComparer | |
{ | |
int Compare(object x, object y); | |
} | |
[우선순위 큐 - MinHeap만든거] | |
public class TestItem : IComparable { | |
public int Weight { get; private set; } | |
public TestItem (int weight) { | |
Weight = weight; | |
} | |
public int CompareTo (object other) { | |
if ((other is TestItem) == false) return 0; | |
return Weight.CompareTo((other as TestItem).Weight); | |
} | |
} | |
. | |
. | |
. | |
PriorityQueue<TestItem> priorityQueue = new PriorityQueue<TestItem>(); | |
priorityQueue.Add(new TestItem(5)); | |
priorityQueue.Add(new TestItem(3)); | |
priorityQueue.Add(new TestItem(7)); | |
priorityQueue.Add(new TestItem(2)); | |
priorityQueue.Add(new TestItem(6)); | |
priorityQueue.Add(new TestItem(9)); | |
priorityQueue.Add(new TestItem(1)); | |
priorityQueue.Add(new TestItem(4)); | |
priorityQueue.Add(new TestItem(8)); | |
while (priorityQueue.Count > 0) Debug.Log(priorityQueue.Pop().Weight); | |
5. in, out, ref 키워드 | |
(Call by Reference) | |
out과 ref는 둘다 참조형(c++의 얕은복사)이지만, ref는 초기화 해야하고, 지정된 인자를 받는 메서드는 반드시 변수에 값을 넣어서 반환해야 한다. | |
6. enum 열거형 | |
보통 상태를 나타내는 플래그 변수를 표현할 때 유용하다. | |
enum Day {Sat, Sun, Mon, Tue, Wed, Thu, Fri}; | |
7. 연산자 오버라이딩(operator) | |
c++ 처럼 됨. | |
8.readonly 와 const 차이 | |
const 필드는 필드를 선언할 때만 초기화될 수 있습니다. | |
readonly 필드는 필드를 선언할 때 또는 생성자에서 초기화될 수 있습니다. | |
나. C# 기능 | |
1. 프로퍼티(Property) = 속성 | |
프로퍼티는 Get,Set을 손쉽게 만들 수 있는 방법(변수)이다. 함수 선언방식에서 ()만 빼면 프로퍼티 변수의 선언이다. | |
선언 부에 get {return ~; } set {~= value; } 이런식으로 선언한다. | |
자동 선언을 이용하려면 set; get; 이렇게 쓰면 됨. | |
사용할때는 | |
인스턴스.프로퍼티 명; //Get을 실행 | |
인스턴스.프로퍼티 명 = 값; //Set을 실행 | |
객체 생성시에 손쉽게 프로퍼티 변수를 초기화 할 수 있으며 변수처럼 사용할 수 있다. | |
(변수처럼 사용하는게 곧 Get을 호출하는것과 같으므로) | |
2. 델리게이트(Deligate) | |
메서드를 변수로서 사용하기 위한 개념. (=함수 포인터) 메서드를 가리킬 수 있는 타입이다. | |
delegate로 선언한 타입이 메서드를 가리키기 때문에 | |
그 메서드를 직접 호출하는 것 대신에 delegate로 그 메서드를 호출할 수 있다. | |
delegate가 어떤 메서드를 가리키기 때문에 그 메서드와 동일한 매개변수와 리턴타입으로 선언해야한다. | |
delegate void Type1(void); // void func1(void) | |
delegate int Type2(int,int); // int func2(int, int) | |
delegate string Type3(double); // string func3(double) | |
delegate float Type4(int); // float func4(int) | |
Type1 F1Delegate; | |
F1Delegate = new Type1(func1); | |
Type2 F2Delegate; | |
F2Delegate = new Type2(func2); | |
Type3 F3Delegate = func3; // C# 2.0부터 사용가능 | |
* 타입명에 관례적으로 접미사 Delegate를 붙임. 안 붙여도 상관없으나 다 이유가 있다. | |
'콜백(CallBack)메서드'에서 활용 시 유용. | |
Delegate 체인 | |
delegate 인스턴스는 하나의 메서드만 가리키고 있었는데 여러개를 가리키는 것도 가능하다. | |
특이하게 += 연산자를 이용해 delegate 인스턴스를 추가한다. | |
마찬가지로 -= 연산자를 이용해 인스턴스를 삭제할 수있다. | |
3. 람다(Lamda) | |
무명 메소드, 무명 델리게이터를 단순한 계산식으로 표현한 것, | |
[구현] | |
(매개변수_목록) => 식 | |
MyDelegate A; | |
A = delegate(int a, int b) { return a+b; }; //이런 식을 | |
A = (int a, int b) => a+b; //로 표현 A = (a,b) => a+b; 이렇게도 가능 | |
() => Write("No"); | |
ex) | |
delegate int Method(int a, int b); | |
static void Main(string[] args) | |
{ | |
Method Add= (a, b) => a+b; | |
Console.WriteLine(Add(3, 4)); | |
Method Minus = (a, b) => | |
{ | |
Console.WriteLine("{0} - {1} 의 결과는?", a, b); | |
return a - b; | |
}; | |
Console.WriteLine(Minus(5, 3)); | |
} | |
4.Linq(LINQ) - 쿼리를 사용한 리스트 생성 | |
SQL같이 리스트에서 특정 요소를 빼서 새로운 리스트를 만드는 방법. | |
Linq 질의는 from in where orderby select로 이루어 진다. | |
5. 리플렉션(Reflection) | |
리플렉션 객체는 런타임에 형식정보를 얻는데에 이용됩니다. | |
이 기능을 사용하여 실핼 중에 객체의 형식 이름, 포로퍼티, 메소드, 필드, 이벤트 목록을 모두 볼수 있고, | |
해당 메소드를 호출하거나 필드, 프로퍼티에 접근하는 것도 가능합니다. | |
프로그램의 metadata에 접근할 수 있도록 해주는 이 클래스는 System.Refelection namespace에 정의되어 있습니다. | |
6. 튜플(Tuple) | |
튜플을 사용하면 사용자 정의 클래스를 만들지 않고도 가능한 여러 유형의 여러 값을 단일 객체로 결합 할 수 있다. | |
예를 들어 세 개의 관련 값을 리턴하지만 새 클래스를 작성하지 않으려는 메소드를 작성하려는 경우에 유용 할 수 있다. | |
1) 튜플은 Class 타입이고 인스턴스들은 변경할 수 없다. | |
2) 튜플은 int, string, double 등 서로 다른 자료형들을 내부에 함께 담을 수 있다. | |
3) Tuple.Create() 생성자도 여러 인자를 사용할 수 있습니다 (string, int, bool) 등. | |
4) 튜플은 클래스 타입이라 튜플 객체는 GC가 관리한다. | |
5) KeyValuePair을 이용하면 성능 향상에 도움이 된다. | |
Item1 , Item2 , Item3 이런 속성으로 각각의 값에 접근할 수 있다. | |
C# 7 이전 버전에서는 메서드에서 하나의 값만을 리턴할 수 있었지만, | |
C# 7 부터는 튜플(Tuple)을 사용하여 메서드로부터 복수 개의 값들을 리턴할 수 있게 되었다. | |
using System; | |
using System.Collections.Generic; | |
class Program | |
{ | |
static void Main() | |
{ | |
List<Tuple<int, string>> list = new List<Tuple<int, string>>(); | |
list.Add(new Tuple<int, string>(1, "cat")); | |
list.Add(new Tuple<int, string>(100, "apple")); | |
list.Add(new Tuple<int, string>(2, "zebra")); | |
// Use Sort method with Comparison delegate. | |
// ... Has two parameters; return comparison of Item2 on each. | |
list.Sort((a, b) => a.Item2.CompareTo(b.Item2)); | |
foreach (var element in list) | |
{ | |
Console.WriteLine(element); | |
} | |
} | |
} | |
== | |
(100, apple) | |
(1, cat) | |
(2, zebra) | |
[C# 7.0 이후 패치노트] | |
(int count, int sum, double average) Calculate(List<int> data) //튜플 리턴타입 | |
{ | |
int cnt = 0, sum = 0; | |
double avg = 0; | |
foreach (var i in data) | |
{ | |
cnt++; | |
sum += i; | |
} | |
avg = sum / cnt; | |
return (cnt, sum, avg); //튜플 리터럴 | |
} | |
private void Run() | |
{ | |
var list = new List<int> { 1, 2, 3, 4, 5 }; | |
var r = Calculate(list); // 튜플 결과 | |
Console.WriteLine($"{r.count}, {r.sum}, {r.average}"); | |
Console.WriteLine($"{r.Item1}, {r.Item2}, {r.Item3}"); | |
} | |
다. 자료구조 | |
1. ArrayList 클래스 | |
ArrayList는 모든 배열 요소가 object 타입인 Non-generic 동적 배열 클래스이다. | |
.NET의 Non-generic 클래스들은 System.Collections 네임스페이스 안에 있으며, | |
단점으로 박싱 / 언박싱이 일어나게 된다. ArrayList는 배열 요소를 읽어 사용할 때 | |
object를 리턴하므로 일반적으로 원하는 타입으로 먼저 캐스팅(Casting)한 후 사용하게 된다. | |
ArrayList myList = new ArrayList(); | |
myList.Add(90); | |
myList.Add(88); | |
myList.Add(75); | |
// int로 casting | |
int val = (int) myList[1]; | |
2. List<T> 클래스 | |
List<T>는 배열요소가 T 타입인 Generics로서 동적 배열을 지원하는 클래스이다. | |
.NET의 Generic 클래스들은 System.Collections.Generic 네임스페이스 안에 있다. | |
List클래스는 내부적으로 배열을 가지고 있으며, 동일한(Homogeneous) 타입의 데이타를 저장한다. | |
만약 미리 할당된 배열 크기(Capacity라 부른다)가 부족하면 내부적으로 배열을 2배로 늘려 동적으로 배열을 확장한다. | |
ArrayList와 다르게 캐스팅을 할 필요가 없으며, 박싱 / 언박싱의 문제를 발생시키지 않는다. | |
List<int> myList = new List<int>(); | |
myList.Add(90); | |
myList.Add(88); | |
myList.Add(75); | |
int val = myList[1]; | |
3. SortedList<TKey,TValue> 클래스 | |
SortedList클래스는 Key값으로 Value를 찾는 Map ADT 타입 (ADT: Abstract Data Type)을 내부적으로 배열을 이용해 구현한 클래스이다. | |
.NET에서 MAP ADT를 구현한 클래스로는 해시테이블을 이용한 Hashtable/Dictionary클래스, 이진검색트리를 이용한 SortedDictionary, | |
그리고 배열을 이용한 SortedList 등이 있다. SortedList클래스는 내부적으로 키값으로 소트된 배열을 가지고 있으며, | |
따라서 이진검색(Binary Search)가 가능하기 때문에 O(log n)의 검색 시간이 소요된다. | |
만약 미리 할당된 배열 크기(Capacity라 부른다)가 부족하면 내부적으로 배열을 2배로 늘려 동적으로 배열을 확장한다. | |
SortedList<int, string> list = new SortedList<int, string>(); | |
list.Add(1001, "Tim"); | |
list.Add(1020, "Ted"); | |
list.Add(1010, "Kim"); | |
string name = list[1001]; | |
foreach (KeyValuePair<int, string> kv in list) | |
{ | |
Console.WriteLine("{0}:{1}", kv.Key, kv.Value); | |
} | |
// 출력 | |
//1001:Tim | |
//1010:Kim | |
//1020:Ted | |
외관상 SortedList가 Dictionary와 비슷하게 보일 수 있다. 하지만 이 둘은 근본적인 여러 차이점이 있다. | |
우선 Dictionary는 키값으로 소트되어 있지 않은 반면, SortedList는 키값으로 소트되어 있다. | |
Dictionary는 해쉬테이블을 구현한 클래스로 Key에 의해 즉시 Value를 찾을 수 (즉, O(1)) 있지만, | |
SortedList는 비록 문법적으로 Dictionary와 동일하게 값을 찾지만 내부적으로 Binary Search 방식 (O(log N))으로 찾기 때문에 | |
Dictionary 보다 느리다. 그리고 참고로 내부 구현을 보면 전혀 다른 구조를 갖는다. | |
4. ConcurrentBag 클래스 | |
.NET 4.0 부터 멀티쓰레딩 환경에서 리스트를 보다 간편하게 사용할 수 있는 새로운 클래스인 ConcurrentBag<T> 가 제공되었다. | |
ConcurrentBag<T> 클래스는 리스트와 비슷하게 객체들의 컬렉션을 저장하는데, List<T> 와는 달리 입력 순서를 보장하지는 않는다. | |
ConcurrentBag 에 데이타를 추가하기 위해 Add() 메서드를 사용하고, 데이타를 읽기 위해서는 foreach문 혹은 TryPeek(), TryTake() 메서드를 사용한다. | |
TryPeek()은 ConcurrentBag에서 데이타를 읽기만 하는 것이고, TryTake()는 데이타를 읽을 후 해당 요소를 ConcurrentBag에서 삭제하게 된다. | |
ConcurrentBag는 멀티쓰레드가 동시에 엑세스할 수 있는데, 예를 들어 ThreadA와 ThreadB가 ConcurrentBag에 데이타를 쓸 때, | |
ThreadA가 1,2,3 을 넣고, ThreadB가 4,5,6 을 넣으면, ThreadA는 ConcurrentBag을 다시 읽을 때, | |
자신이 쓴 1,2,3을 우선순위로 먼저 읽은 다음, 나머지 다른 쓰레드에 의해 입력된 요소들 (4,5,6)을 읽게 된다. | |
아래 예제에서 첫번째 쓰레드는 100개의 숫자를 ConcurrentBag에 넣게 되고, | |
동시에 두번째 쓰레드는 1초마다 10회에 걸쳐 해당 ConcurrentBag의 내용을 출력하는 것이다. | |
using System; | |
using System.Collections; | |
using System.Collections.Concurrent; // ConcurrentBag | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace ConcurrentApp | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var bag = new ConcurrentBag<int>(); | |
// 데이타를 Bag에 넣는 쓰레드 | |
Task t1 = Task.Factory.StartNew(() => | |
{ | |
for (int i = 0; i < 100; i++) | |
{ | |
bag.Add(i); | |
Thread.Sleep(100); | |
} | |
}); | |
// Bag에서 데이타를 읽는 쓰레드 | |
Task t2 = Task.Factory.StartNew(() => | |
{ | |
int n = 1; | |
// Bag 데이타 내용을 10번 출력함 | |
while (n <= 10) | |
{ | |
Console.WriteLine("{0} iteration", n); | |
int count = 0; | |
// Bag에서 데이타 읽기 | |
foreach (int i in bag) | |
{ | |
Console.WriteLine(i); | |
count++; | |
} | |
Console.WriteLine("Count={0}", count); | |
Thread.Sleep(1000); | |
n++; | |
} | |
}); | |
// 두 쓰레드가 끝날 때까지 대기 | |
Task.WaitAll(t1, t2); | |
} | |
} | |
} | |
5. LinkedList<T> 클래스 | |
링크드 리스트는 특정 노드에서 노드를 삽입, 삭제하기 편리 하지만 ( O(1) ), 특정 노드를 검색하기 위해서는 O(n)의 시간이 소요된다. | |
.NET에는 링크드 리스트를 구현한 LinkedList<T> 클래스가 있다. | |
이 LinkedList 클래스는 이중 링크드 리스트로 구현되어 있으며, 리스트 노드는 LinkedListNode 클래스로 표현된다. | |
노드의 추가는 AddFirst, AddLast, AddBefore, AddAfter 등의 메서드들을 호출하여 | |
처음 또는 끝, 혹은 특정 노드의 앞, 뒤에 새 노드를 추가할 수 있다. 아래 예는 Banana 노드 뒤에 Grape노드를 추가하는 예이다. | |
LinkedList<string> list = new LinkedList<string>(); | |
list.AddLast("Apple"); | |
list.AddLast("Banana"); | |
list.AddLast("Lemon"); | |
LinkedListNode<string> node = list.Find("Banana"); | |
LinkedListNode<string> newNode = new LinkedListNode<string>("Grape"); | |
// 새 Grape 노드를 Banana 노드 뒤에 추가 | |
list.AddAfter(node, newNode); | |
// 리스트 출력 | |
list.ToList().ForEach(p => Console.WriteLine(p)); | |
// Enumerator를 이용한 리스트 출력 | |
foreach(var m in list) | |
{ | |
Console.WriteLine(m); | |
} | |
위에 p => 는 .. | |
C# 3.0부터 지원하는 => 연산자는 C#에서 람다 식(Lambda Expression)을 표현할 때 사용한다. | |
람다식은 무명 메서드와 비슷하게 무명 함수(anonymous function)를 표현하는데 사용된다. | |
람다식은 아래와 같이 입력 파라미터(0개 ~ N개)를 => 연산자 왼쪽에, 실행 문장들을 오른쪽에 둔다. | |
람다 Synyax : (입력 파라미터) => { 문장블럭 }; | |
예를 들어 하나의 문자열을 받아 들여 메시지 박스를 띄운다면 다음과 같이 간단히 쓸 수 있다 | |
str => { MessageBox.Show(str); } | |
입력 파라미터는 하나도 없는 경우부터 여러 개 있는 경우가 있다. | |
다음 예는 파라미터가 없는 케이스 부터 두개 있는 케이스까지 보여준다. | |
마지막 예는 입력 파라미터의 타입이 애매한 경우 이를 써줄 수 있음을 보여준다. 일반적으로 입력타입은 컴파일러가 알아서 찾아낸다. | |
() => Write("No"); | |
(p) => Write(p); | |
(s, e) => Write(e); | |
(string s, int i) => Write(s, i); | |
6. Queue 클래스 | |
.NET에는 큐를 구현한 Queue클래스와 이의 Generic 형태인 Queue<T> 클래스가 있다. | |
이 Queue클래스는 내부적으로 순환 배열 (Circular Array)로 구현되어 있는데, | |
배열의 마지막 요소에 다다른 경우 다시 배열 처음 요소로 순환하는 구조(next % arrsize)를 가지고 있다 | |
Queue는 내부적으로 head와 tail 포인터를 가지고 있는데, tail에 데이타를 추가하고(Enqueue) head에서 데이타를 읽고 제거(Dequeue)한다. | |
만약 데이타 양이 많아 순환 배열이 모두 찰 경우, Queue는 Capacity를 2배로 (디폴트 Growth Factor가 2이다) 증가시키고, | |
모든 배열 요소를 새 순환 배열에 자동으로 복사하여 큐를 확장한다. | |
Queue<int> q = new Queue<int>(); | |
q.Enqueue(120); | |
q.Enqueue(130); | |
q.Enqueue(150); | |
int next = q.Dequeue(); // 120 | |
next = q.Dequeue(); // 130 | |
7. ConcurrentQueue 클래스 | |
멀티쓰레딩 환경에서 위의 Queue 클래스를 사용하기 위해서는 전통적인 방식인 lock 을 사용하는 방법과 | |
Queue.Synchronized() 를 사용하여 Thread-safe한 Wrapper 큐를 사용하는 방법이 있다. | |
.NET 4.0 부터 멀티쓰레딩 환경에서 큐를 보다 간편하게 사용할 수 있는 새로운 클래스인 ConcurrentQueue<T> 가 제공되었다. | |
Queue 클래스와 비슷하게 ConcurrentQueue 의 기본 동작 메서드는 Enqueue() 와 TryDequeue() 인데, | |
ConcurrentQueue 에는 Dequeue() 메서드가 없고 대신 TryDequeue() 메서드를 사용한다. | |
또한 마찬가지로 ConcurrentQueue에서는 Peek() 메서드 대신 TryPeek() 메서드를 사용한다. | |
아래 예제는 하나의 쓰레드가 큐(ConcurrentQueue)에 0부터 99까지 계속 집어 넣을 때, | |
동시에 다른 쓰레드에서는 계속 큐에서 데이타를 빼내 읽어 오는 작업을 하는 샘플 코드이다 | |
using System; | |
using System.Collections.Concurrent; // ConcurrentQueue | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace ConcurrentApp | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var q = new ConcurrentQueue<int>(); | |
// 데이타를 큐에 넣는 쓰레드 | |
Task tEnq = Task.Factory.StartNew(() => | |
{ | |
for (int i = 0; i < 100; i++) | |
{ | |
q.Enqueue(i); | |
Thread.Sleep(100); | |
} | |
}); | |
// 데이타를 큐에서 읽는 쓰레드 | |
Task tDeq = Task.Factory.StartNew(() => | |
{ | |
int n = 0; | |
int result; | |
while (n < 100) | |
{ | |
if (q.TryDequeue(out result)) | |
{ | |
Console.WriteLine(result); | |
n++; | |
} | |
Thread.Sleep(100); | |
} | |
}); | |
// 두 쓰레드가 끝날 때까지 대기 | |
Task.WaitAll(tEnq, tDeq); | |
} | |
} | |
} | |
자료구조 : 스택 (Stack) | |
스택 (Stack)은 가장 나중에 추가된 데이타가 먼저 출력 처리되는(LIFO, Last In First Out) 자료 구조로서 | |
가장 최신 입력된 순서대로 처리해야 하는 상황에 이용된다. 스택은 개념적으로 한 쪽 끝에서만 자료를 넣거나 뺄 수 있는 구조로 되어 있다. | |
자료는 스택에 저장하는 것은 Push라 하고, 가장 최근 것부터 꺼내는 것은 Pop이라 한다. | |
Stack의 Push와 Pop은 일반적으로 O(1)의 시간이 소요되지만, Push의 경우 만약 스택이 Full되어 동적 확장이 일어난다면 O(n)의 시간이 소요된다. | |
8. Stack 클래스 | |
.NET에는 스택을 구현한 Non-generic인 Stack클래스와 이의 Generic 형태인 Stack<T> 클래스가 있다. | |
Queue와 마찬가지로 .NET의 Stack클래스는 내부적으로 순환 배열 (Circular Array)으로 구현되어 있으며, | |
스택이 가득 차면 자동으로 배열을 동적으로 확장하게 된다. | |
Stack<double> s = new Stack<double>(); | |
s.Push(10.5); | |
s.Push(3.54); | |
s.Push(4.22); | |
double val = s.Pop(); //4.22 | |
9. ConcurrentStack 클래스 | |
.NET 4.0 부터 멀티쓰레딩 환경에서 스택을 보다 간편하게 사용할 수 있는 새로운 클래스인 ConcurrentStack<T> 가 제공되었다. | |
Stack 클래스와 비슷하게 ConcurrentStack 의 기본 동작 메서드는 Push() 와 TryPop() 인데, | |
ConcurrentStack 에는 Pop() 메서드가 없고 대신 TryPop() 메서드를 사용한다. | |
아래 예제는 하나의 쓰레드가 ConcurrentStack 에 0부터 99까지 계속 집어 넣을 때, | |
동시에 다른 쓰레드에서는 계속 그 스택에서 데이타를 빼내 읽어 오는 작업을 하는 샘플 코드이다. | |
스택을 Pop 하는 속도를 약간 늦춤으로 해서 0 부터 99까지 순차적으로 출력하지 않을 가능성이 더 커지게 하였다. | |
using System; | |
using System.Collections; | |
using System.Collections.Concurrent; // ConcurrentStack | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace ConcurrentApp | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var s = new ConcurrentStack<int>(); | |
// 데이타를 스택에 넣는 쓰레드 | |
Task tPush = Task.Factory.StartNew(() => | |
{ | |
for (int i = 0; i < 100; i++) | |
{ | |
s.Push(i); | |
Thread.Sleep(100); | |
} | |
}); | |
// 데이타를 스택에서 읽는 쓰레드 | |
Task tPop = Task.Factory.StartNew(() => | |
{ | |
int n = 0; | |
int result; | |
while (n < 100) | |
{ | |
if (s.TryPop(out result)) | |
{ | |
Console.WriteLine(result); | |
n++; | |
} | |
Thread.Sleep(150); | |
} | |
}); | |
// 두 쓰레드가 끝날 때까지 대기 | |
Task.WaitAll(tPush, tPop); | |
} | |
} | |
} | |
자료구조 : 해시테이블 (Hash Table) | |
해시(Hash)는 키 값을 해시 함수(Hash function)으로 해싱하여 해시테이블의 특정 위치로 직접 엑세스하도록 만든 방식이다. | |
키 값을 통해 직접 엑세스하기 위해서 모든 가능한 키 값을 갖는 배열을 만들면, 배열크기가 엄청나게 커지게 된다. | |
예를 들어, 주민등록번호를 키 값으로 하는 경우, 000000-0000000 부터 999999-9999999까지 10의 13승의 배열 공간이 필요한데, | |
만약 회원수가 1000명인 경우, 1000명을 저장하기 위해 10^13의 엄청난 배열 공간이 필요하게 된다. | |
이렇게 낭비되는 공간을 줄이기 위해 해시 함수를 사용하게 되는데, 이 함수는 적은 공간 안에서 모든 키를 직접 찾아갈 수 있도록 해준다. | |
하지만 경우에 따라 서로 다른 키가 동일한 해시테이블 버켓 위치를 가리킬 수 있는데, | |
이를 해결하기 위해 여러 Collision Resolution 방식이 사용된다. | |
Collision Resolution의 방식으로 Linear Probing(클러스터 현상이 잘 일어난다. | |
클러스터는 여러개가 연결되어 하나처럼된듯..), Quadratic Probing, Rehashing (Double Hashing), Chaining 등 여러가지가 있다. | |
해시테이블 자료구조는 추가, 삭제, 검색에서 O(1)의 시간이 소요된다. | |
10. Hashtable 클래스 | |
.NET에 해시테이블을 구현한 Non-generic 클래스로 Hashtable 클래스가 있다. | |
Hashtable은 Key값과 Value값 모두 object 타입을 받아들이며, 박싱/언박싱을 하게 된다. | |
Hashtable은 Rehashing (Double Hashing)방식을 사용하여 Collision Resolution을 하게 된다. | |
즉, 해시함수를 H1(Key) 부터 Hk(Key) 까지 k개를 가지고 있으며, 키 충돌(Collision)이 발생하면, | |
차기 해시함수를 계속 사용하여 빈 버켓을 찾게된다. 이 자료구조는 추가, 삭제, 검색에서 O(1)의 시간이 소요된다. | |
Hashtable ht = new Hashtable(); | |
ht.Add("irina", "Irina SP"); | |
ht.Add("tom", "Tom Cr"); | |
if (ht.Contains("tom")) | |
{ | |
Console.WriteLine(ht["tom"]); | |
} | |
11. Dictionary<Tkey,TValue> 클래스 | |
.NET에 Generic방식으로 해시테이블을 구현한 클래스로 Dictionary<Tkey,TValue> 클래스가 있다. | |
Dictionary는 Key값과 Value값 모두 Strong type을 받아들이며, 박싱/언박싱을 일으키지 않는다. | |
Dictionary는 Chaining 방식을 사용하여 Collision Resolution을 하게 된다. 이 자료구조는 추가, 삭제, 검색에서 O(1)의 시간이 소요된다. | |
느슨한 타입은 타입 없이 변수를 선언하는 것이다. | |
반면에 강력한 타입(strong typing)을 사용하는 언어는 타입과 함께 변수를 선언해야만 한다. 다음의 예제를 살펴보자: | |
/* JavaScript Example (loose typing) */ | |
var a = 13; // Number 선언 | |
var b = "thirteen"; // String 선언 | |
/* Java Example (strong typing) */ | |
int a = 13; // int 선언 | |
String b = "thirteen"; // String 선언 | |
Dictionary<int, string> emp = new Dictionary<int, string>(); | |
emp.Add(1001, "Jane"); | |
emp.Add(1002, "Tom"); | |
emp.Add(1003, "Cindy"); | |
string name = emp[1002]; | |
Console.WriteLine(name); | |
12. ConcurrentDictionary<Tkey,TValue> 클래스 | |
.NET 4.0 부터 멀티쓰레딩 환경에서 Dictionary를 보다 간편하게 사용할 수 있는 새로운 클래스인 ConcurrentDictionary<T> 가 제공되었다. | |
ConcurrentDictionary 클래스에서는 기본적으로 데이타를 추가하기 위해 TryAdd() 메서드를 사용하고, | |
키값을 읽기 위해서는 TryGetValue() 메서드를 사용한다. 또한 기존 키값을 갱신하기 위해서 TryUpdate() 메서드를, | |
기존 키를 지우기 위해서는 TryRemove() 메서드를 사용한다. | |
아래 예제는 하나의 쓰레드가 ConcurrentDictionary 에 Key 1부터 100까지 계속 집어 넣을 때, | |
동시에 다른 쓰레드에서는 계속 그 해시테이블에서 Key 1부터 100까지의 데이타를 빼내 (순차적으로) 읽어 오는 작업을 하는 샘플 코드이다. | |
using System; | |
using System.Collections; | |
using System.Collections.Concurrent; // ConcurrentDictionary | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace ConcurrentApp | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var dict = new ConcurrentDictionary<int, string>(); | |
Task t1 = Task.Factory.StartNew(() => | |
{ | |
int key = 1; | |
while (key <= 100) | |
{ | |
if (dict.TryAdd(key, "D" + key)) | |
{ | |
key++; | |
} | |
Thread.Sleep(100); | |
} | |
}); | |
Task t2 = Task.Factory.StartNew(() => | |
{ | |
int key = 1; | |
string val; | |
while (key <= 100) | |
{ | |
if (dict.TryGetValue(key, out val)) | |
{ | |
Console.WriteLine("{0},{1}", key, val); | |
key++; | |
} | |
Thread.Sleep(100); | |
} | |
}); | |
Task.WaitAll(t1, t2); | |
} | |
} | |
} | |
자료구조 : 이진 트리 (Binary Tree) | |
트리(Tree)에서 많이 사용되는 특별한 트리로서 이진트리를 들 수 있는데, 이진 트리는 자식노드가 0개 ~ 2개인 트리를 말한다. | |
따라서 이진트리 노드는 데이타필드와 왼쪽노드 및 오른쪽노드를 갖는 자료 구조로 되어 있다. | |
이진 트리는 루트 노드로부터 출발하여 원하는 특정 노드에 도달할 수 있는데, 이때의 검색 시간(Search Time)은 O(n)이 소요된다. | |
자료구조 : 이진검색트리 (Binary Search Tree) | |
이진트리(Tree)의 특수한 형태로 자주 사용되는 트리로서 이진검색트리 (Binary Search Tree)가 있다. | |
이진검색트리는 이진트리의 모든 속성을 가짐과 동시에 중요한 또 하나의 속성을 가지고 있는데, | |
그것은 특정 노드에서 자신의 노드보다 작은 값들은 모두 왼쪽에 있고, 큰 값들은 모두 오른쪽에 위치한다는 점이다. | |
또한 중복된 값을 허락하지 않는다. 따라서 전체 트리가 소트되어 있는 것과 같은 효과를 같게 되어 검색에 있어 배열이나 | |
이진트리처럼 순차적으로 모든 노드를 검색하는 것(O(n))이 아니라, 매 검색마다 검색영역을 절반으로 줄여 O(log n)으로 검색할 수 있게 된다. | |
하지만 노드들이 한쪽으로 일렬로 기울어진 Skewed Tree인 경우, 검색영역을 n-1로만 줄이기 때문에 O(n)만큼의 시간이 소요된다. | |
즉, 예를 들어 소트된 데이타를 이진검색트리에 추가하게 되면, 이렇게 한쪽으로 치우쳐 진 트리가 생겨 검색시간이 O(n)으로 떨어지게 되는데, | |
이러한 현상을 막기 위하여 노드 추가/갱신시 트리 스스로 다시 밸런싱(Self balancing)하여 검색 최적화를 유지할 수 있다. | |
이러한 트리를 Self-Balancing Binary Search Tree 또는 Balanced Search Tree라 하는데, | |
가장 보편적인 방식으로 AVL Tree, Red-Black Tree 등을 들 수 있다. | |
NOTE: 참고로 Search Tree에는 최대 2개의 자식노드를 갖는 Binary Search Tree 이외에, | |
여러 개의 자식노드들을 갖는 N-Way 검색 트리 (n-way Search Tree)가 있는데, | |
대표적으로 B-Tree (혹은 이의 변형인 B* Tree, B+ Tree)가 있으며 흔히 SQL Server와 같은 관계형 DB 인덱스로 주로 사용된다. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment