Transcript 06a 컬렉션
C#
06a장. 컬렉션
04장. 제어문과 예외처리
컬렉션 : 변수들의 조직적인 집합
컬렉션 클래스 : 컬렉션 집합을 저장하고 관리하는 클래스
컬렉션의 종류 : 배열, 배열 리스트, 해시 테이블, 큐, 스택
컬렉션의 네임 스페이스 : System.Collections
비제네릭 컬렉션 ; System.Collections
제네릭 컬렉션 : System.Collections.Generic
c#
2 / 38
04장. 제어문과 예외처리
제너릭
제너릭(Generic)
: 타입 인수를 사용하여 일반화된 클래스나 메서드를 정의하는 기법
C# 2.0 부터 지원
C++의 템플릿과 유사
c#
3 / 38
04장. 제어문과 예외처리
제너릭이 필요한 이유
1. using System;
2. class WrapperInt
1. int 형을 정의한 클래스
3. {
4.
int Value;
5.
public WrapperInt() { Value = 0; }
6.
public WrapperInt(int aValue) { Value = aValue; }
7.
public int Data
8.
{
9.
get { return Value; }
10.
set { Value = value; }
11. }
12. public void OutValue()
13. {
14.
Console.WriteLine(Value);
15. }
16.}
16.class WrapperString
2. string 형을 정의한 클래스
17.{
18. string Value;
19. public WrapperString() { Value = null; }
20. public WrapperString(string aValue) { Value = aValue; }
21. public string Data
22. {
23.
get { return Value; }
24.
set { Value = value; }
25. }
26. public void OutValue()
27. {
28.
Console.WriteLine(Value);
29. }
30.} c#
31.class CSTest
32.{
33. static void Main()
34. {
35.
WrapperInt gi = new WrapperInt(1234);
36.
gi.OutValue();
37.
WrapperString gs = new WrapperString("문자열");
38.
gs.OutValue();
39. }
40.}
2 개의 클래스(WrapperInt,
WrapperString)가 모두 내부 코드는 동일
하다.
제네릭으로 간단히 정의 가능
4 / 38
04장. 제어문과 예외처리
제너릭이 필요한 이유
using System;
class Wrapper<T>
{
T Value;
public Wrapper() { Value = default(T); }
public Wrapper(T aValue) { Value = aValue; }
public T Data
{
get { return Value; }
set { Value = value; }
}
public void OutValue()
{
Console.WriteLine(Value);
}
}
1. 1개의 클래스(Wrapper<T>)로 정의
2. 선언문의 <T>가
Parameter)임
타입 인수(Type
3. T는 실제 타입을 위한 자료 표시이며
실제 타입은 객체를 생성할 때 지정된
다.
4. 타입 인수는 모든 곳에 사용 가능함
(필드, 프로퍼티의 타입, 메서드의 리
턴값, 메서드의 인수 타입 등)
5. 예제에서는 3군데에서 사용됨
- Value 필드
- 생성자의 인수 aValue
- Data 프로퍼티의 타입
class CSTest
{
static void Main()
{
Wrapper<int> gi = new Wrapper<int>(1234);
gi.OutValue();
Wrapper<string> gs = new Wrapper<string>("문자열");
gs.OutValue();
}
c#
5 / 38
04장. 제어문과 예외처리
• T의 실제 타입 지정 하는 곳 : 객체 생성문의<>괄호 안에 지정
• 앞의 예에서 Wrapper<int>, Wrapper<string>으로 정의하였다. 이
와 같이 Wrapper<double>, Wrapper<double> 같이 많은 클래스를
정의할 수 있다.
• 제네릭은 클래스를 찍어내는 형틀이다.
int
class Wrapper<T>
{
T Value;
public Wrapper() { Value = default(T); }
public Wrapper(T aValue) { Value = aValue; }
public T Data
string
double
class Wrapper<int>
{
int Value;
public Wrapper() { Value =0; }
public Wrapper(string aValue) { Value = aValue; }
public int Data
class Wrapper<string>
{
string Value;
public Wrapper() { Value =null; }
public Wrapper(string aValue) { Value = aValue; }
public string Data
class Wrapper<double>
{
double Value;
public Wrapper() { Value =0.0; }
public Wrapper(double aValue) { Value = aValue; }
public double Data
c#
6 / 38
04장. 제어문과 예외처리
• 개방형 타입 : 아직 타입이 결정되지 않은 Wrapper<T>
• 폐쇄형 타입 : 타입이 결정된 Wrapper<int>
• 개방형 타입은 클래스를 만드는 도구일 뿐 실제 클레스는 아니므로
객체를 생성하지 못한다.
• 제네릭 타입 구체화(Generic Type Instantiation)
: 개방형 타입의 타입 인수를 지정하여 폐쇄형 타입인 클래스를 생
성하는 것
• T가 값 타입일 경우 : 컴파일러가 각 타입별로 구체화 한다.
• T가 참조 타입일 경우 : 하나의 클래스만 생성되고 모든 참조 타입에
대해 생성된 클래스를 재사용한다.
• default 키워드 : T의 기본값을 정의함
• 제네릭에서는 T 가 어떤 타입이 될지 미리 알 수 없으므로
Value=0,Value=null식으로 상수를 대입할 수 없다.
• default(T)라는 표현식으로 T 에 따른 기본값을 표현한다.
c#
7 / 38
04장. 제어문과 예외처리
제너릭을 이용한 2 개의 값 교환
using System;
class CSTest
{
static void Swap(ref int a, ref int b)
{
int t;
t = a; a = b; b = t;
using System;
class CSTest
{
static void Swap<T>(ref T a, ref T b)
{
T t;
t = a; a = b; b = t;
}
}
static void Main()
{
int i1 = 3, i2 = 4;
Console.WriteLine("i1 = {0}, i2 = {1}", i1, i2);
Swap(ref i1, ref i2);
//Swap<int>(ref i1, ref i2); //<int> 생략 가능
Console.WriteLine("i1 = {0}, i2 = {1}", i1, i2);
static void Swap(ref string a, ref string b)
{
string t;
t = a; a = b; b = t;
}
static void Main()
string s1 = "멍멍", s2 = "꼬꼬댁";
Console.WriteLine("s1 = {0}, s2 = {1}", s1, s2);
Swap(ref s1, ref s2);
//Swap<string>(ref s1, ref s2);
Console.WriteLine("s1 = {0}, s2 = {1}", s1, s2);
{
int i1 = 3, i2 = 4;
Console.WriteLine("i1 = {0}, i2 = {1}", i1, i2);
Swap(ref i1, ref i2);
Console.WriteLine("i1 = {0}, i2 = {1}", i1, i2);
}
}
string s1 = "멍멍", s2 = "꼬꼬댁";
Console.WriteLine("s1 = {0}, s2 = {1}", s1, s2);
Swap(ref s1, ref s2);
Console.WriteLine("s1 = {0}, s2 = {1}", s1, s2);
}
c#}
8 / 38
04장. 제어문과 예외처리
제약 조건
• 제너릭 타입 인수 T는 별다른 지정이 없으면 모든 타입을 적용할 수
있다.
• 제약 조건은 제네릭 선언문에 where 와 함께 지정하며 다음과 같은
종류가 있다.
제약 조건
설명
where T:struct
T는 값 타입이어야 하며 참조 타입을 쓸 수 없다. 단, Nullable 타입은
값 타입이지만 예외적으로 이 제약 조건에서 허용되지 않는다.
where T:class
T는 참조 타입이어야 하며 값 타입을 쓸 수 없다.
where T:new()
디폴트 생성자가 있어야 한다. new T() 형태로 객체를 생성할 수 있어
야 한다. 다른 조건과 함께 쓸 때는 제일 뒤에 지정해야 한다.
where T:base
T는 base로부터 파생된 클래스여야 한다.
where T:Ibase
T는 Ibase 인터페이스를 반드시 구현해야 한다. 클래스와는 달리 여
러 개의 인터페이스를 지정할 수도 있다.
where T:U
두 타입 인수 사이의 과계가 파생 관계여야 한다. T가 U의 파생 클래
스여야 한다.
c#
9 / 38
04장. 제어문과 예외처리
값 타입만 가능한 예제(제네릭)
using System;
class Wrapper<T> where T : struct
{
T Value;
public Wrapper() { Value = default(T); }
public Wrapper(T aValue) { Value = aValue; }
public T Data
{
get { return Value; }
set { Value = value; }
}
public void OutValue()
{
Console.WriteLine(Value);
}
}
class CSTest
{
static void Main()
{
Wrapper<int> gi = new Wrapper<int>(1234);
gi.OutValue();
//Wrapper<string> gs = new Wrapper<string>("문자열");
//gs.OutValue();
}
}
c#
왼쪽의 마지막 2줄을 실행 시켰을 때 메시지
10 / 38
04장. 제어문과 예외처리
• 앞의 예는 Wrapper 제네릭 T는 값 타입만 가능하다.
• Wrapper<int>는 가능하지만 Wrapper<string>이나
Wrapper<Human>은 사용할 수 없다.
• where T:class 로 바꾸면 T는 참조 타입만 사용할 수 있
고 값 타입은 사용할 수 없게 된다.
• 제약 조건 중 가장 실용적인 것은 where T: base 형식이
다. 이 조건은 T를 base나 base 파생 클래스로 제한한다
.
c#
11 / 38
04장. 제어문과 예외처리
제약 조건 where T:base
using System;
class Human //1. Human 클래스 정의
{ public virtual void Intro() { Console.WriteLine("나 사람"); } }
class Student : Human // 2. Student 파생 클래스 정의
{ public override void Intro() { Console.WriteLine("나 학생"); } }
class CSTest //3. 제너릭 메서드 정의
{ public static void OutValue<T>(T man) where T : Human
{ man.Intro(); }
// 타입 인수 T 의 객체 man 을 인수로 받아 man.Intro 호출
// T 가 Human의 후손이라는 제약 조건이 있기 때문에 호출 가능
static void Main()
{ Human A = new Human();
Student B = new Student();
string C = "나 문자열";
OutValue(A);
OutValue(B);
//OutValue(C);
c#
}
C 객체는 string 타입이며 Human과는 관계가 없기 때문에
컴파일 되지 않는다.
string 클래스는 Intro 메서드를 가지고 있지 않기 때문에 이
타입의 객체로는 OutValue가 동작하자 않아 컴파일 거부가
12 / 38
된다.
04장. 제어문과 예외처리
제약 조건 where T:base 제약 조건 제거 시
using System;
class Human
{ public virtual void Intro() { Console.WriteLine("나 사람"); } }
class Student : Human
{ public override void Intro() { Console.WriteLine("나 학생"); } }
class CSTest
{
public static void OutValue<T>(T man)
{
Human t = man as Human;
if (t != null) { t.Intro(); }
}
static void Main()
{ Human A = new Human();
Student B = new Student();
string C = "나 문자열";
OutValue(A);
OutValue(B);
OutValue(C);
1.
제약 조건 없이 제네릭으로 작성한 예제임
2.
man 을 Human으로 캐스팅하여 성공하면 호출
하고 그렇지 않으면 아무런 동작도 하지 않는다
3.
그렇기 때문에 문자열 같은 잘못된 타입이 전달
되어도 호출되었다가 그냥 리턴 함.
제약 조건은 컴파일할 때 타입을 체크하여 불가능
한 호출을 원천적으로 차단하고 캐스팅을 쵷소화
하는 역할을 한다.
}
}
c#
13 / 38
04장. 제어문과 예외처리
제너릭 컬렉션
• 제네릭은 원래 C# 언어의 스펙에 포함되어 있던 기능이
아니다.
• 제네릭은 문법을 복잡하게 만들고 컴파일을 느리게 만드
는 주범인데다 컴파일러까지 복잡해져 비용이 많이 든다
.
• C#이 이런 비용을 감수해 가며 2.0 부터 제네릭을 지원
하는 가장 큰 이유는 제네릭 컬렉션 클래스를 지원하기
위함이다.
c#
14 / 38
04장. 제어문과 예외처리
제너릭 컬렉션
• 기본적인 자료의 집합을 관리하는 컬렉션은 모든 응용
프로그램에 필수적인 자료 구조 이다.
• C#은 처음부터 컬렉션 클래스를 지원 하였지만, 제네릭
이전에는 일반 클래스였으며 일반 클래스에는 문제점이
있었다.
c#
15 / 38
04장. 제어문과 예외처리
일반 클래스의 제네릭
using System;
using System.Collections;
1.
2.
일반 컬렉션의 요소 타입은 object 이므로 임의의 요소를 저장
할 수 있다.
어떤 객체든지 컬렉션에 넣을 수 있으며 이를 막을 수 있는 문
법적인 방법이 전혀 없다.
예제에서 정수, 실수, 문자열을 하나의 배열에 넣을 수 있다.
class CSTest
3.
{
static void Main()
빼 낼 때는 과다한 캐스팅이 발생함
{
ArrayList ar = new ArrayList(10);
ar.Add(1);
ar.Add(2.34);
ar.Add("string");
int i = (int)ar[0];
double d = (double)ar[1];
string str = (string)ar[2];
Console.WriteLine("{0}, {1}, {2}", i, d, str);
}
}
c#
16 / 38
04장. 제어문과 예외처리
일반 클래스의 컬렉션 관련 문제점
• 컬렉션에 저장된 정보를 읽을 때 object 타입으로 리턴
되므로 원하는 타입으로 캐스팅해야 한다.
• 그러기 위해서는 컬렉션에 어떤 타입의 객체가 저장되어
있는지 일일이 기억해 놓거나 아니면 실행 중에 타입을
조사해야 하는데 이 작업이 아주 번거롭다. 부모는 자식
을 가리킬 수 있기 때문에 넣을 때는 아무 것이나 넣을
수 있지만 빼낼 때는 그렇지 못한 것이다. 위 코드에서
캐스트 연산자를 빼고 int I = ar[0]; 로 수정하면 에러가
난다.
c#
17 / 38
04장. 제어문과 예외처리
일반 클래스의 컬렉션 관련 문제점
• 캐스팅을 잘못하면 위험해진다. ar[0]를 string 타입으로
캐스팅하여 읽으면 정수가 가리키는 번지를 읽으려고 시
도할 것이므로 잘못하면 다운될 수도 있다.
• 컴파일러가 이런 위험한 문장을 에러로 처리할 수 없는
이유는 ar[0]에 어떤 타입의 객체가 저장될지 컴파일 중
에는 알 방법이 없기 때문이다.
• C#은 이런 문제를 해결하기 위하여 is, as 같은 연산자
를 제공하기는 하지만 이 방법은 실행 중에만 쓸 수 있어
불편할 뿐만 아니라 완전하지도 않다.
c#
18 / 38
04장. 제어문과 예외처리
일반 클래스의 컬렉션 관련 문제점
• 값 타입을 컬렉션에 저장할 때는 object 타입으로 변환하는
박싱이 필요하고 꺼낼 때는 언박싱이 필요한다. 이 처리는 컴
퓨터가 자동으로 해 주지만 성능상의 불이익은 피할 수없다.
• 정수 값 하나늘 넣어도 object로 바꾼 후에 넣기 때문에 메모
리도 많이 소모되고 속도도 느리다.
• 이러한 문제가 발생하는 근본 원인은 컬렉션에 저장될 수 있
는 타입이 너무 일반적이어서 컴파일러가 잘못된 코드를 적발
해 낼 수 있는 정보가 충분하지 않기 때문이다.
• 제너릭을 사용하면 타입 인수로 처리 대상을 지정할 수 있으
므로 위의 문제를 해결할 수 있다.
c#
19 / 38
04장. 제어문과 예외처리
일반 클래스의 컬렉션 관련 문제점
• 닷넷는 기존의 컬렉션 클래스를 대체할 수 있는 제네릭
컬렉션을 제공한다.
• ArrayList의 제네릭 버전은 List<T> 이다.
c#
20 / 38
04장. 제어문과 예외처리
ArrayList의 제네릭 버전인 List<T> 예제
(문자열의 컬렉션 관리)
using System;
using System.Collections.Generic;
class CSTest
1. 네임 스페이스 : System.Collections.Generic
{
2. ar 은 List<string>타입으로 선언되었으므로 문자열만 저장할
수 있다.
static void Main()
{
List<string> ar = new List<string>(10);
ar.Add("이승만");
ar.Add("박정희");
ar.Add("최규하");
//ar.Add(1234);
//ar.Add(5.678);
foreach (string s in ar) Console.Write(s + ",");
}
}
c#
21 / 38
04장. 제어문과 예외처리
제네릭의 장점
• 컬렉션에 저장 가능한 타입이 선언 시점에 명시되므로
컴파일러는 어떤 타입이 안전하게 저장될 수 있는지 분
명하게 알 수 있다.
• 실행 중이 아닌 컴파일 타임에 수행할 수 있다는 점이 제
네릭의 장점이다.
• 저장되는 타입이 정해져 있으므로 꺼낼 때도 캐스팅을
할 필요가 업으며 실수를 할 잠재적인 위험도 없다.
c#
22 / 38
04장. 제어문과 예외처리
제네릭의 장점
• List의 프로퍼티나 메서드는 ArrayList와 거의 동일하다.
• ArrayList 는 비제네릭 버전이고 List는 이 클래스를 새로
만든 제네릭 버젼이기 때문에 인터페이스가 비슷하다
• 닷넷 공식 문서에서는 가능하면 제레릭 버전을 쓸 것을
권장한다.
• List가 가장 자주 사용되는 범용적인 컬렉션이지만 이외
에도 이중 연결 리스트를 제공하는 LinkedList가 있고,
Queue, Stack, Dictionary 같은 제네릭 컬렉션도 제공한
다.
c#
23 / 38