제네릭 사용하기
C# 2.0부터는 제네릭(Generic)이라고 해서 특정 형식을 지정해서 컬렉션에 저장하고 사용할 수 있습니다. 제네릭 컬렉션은 다른 데이터 형식을 추가할 수 없도록 형식 안전성을 적용합니다. 고전적인 컬렉션 클래스와 달리 System.Collections.Generic
네임스페이스에 들어 있는 제네릭 컬렉션 클래스는 요소를 다룰 때 데이터 형식 변환 등의 작업이 필요하지 않습니다.
> // 제네릭: Cup<T>를 Cup of T로 발음하여 형식 매개 변수인 T에 따른 Cup 클래스의 개체 생성
Cup of T
컵(Cup)에 들어가는 Tea(T)가 무엇인지에 따라서 오렌지 주스 컵, 커피 컵, 콜라 컵, 월드 컵(?) 등이 결정됩니다. 이처럼 넘어 오는 데이터 형식에 따라서 해당 개체의 성격이 변경되는 구조를 제네릭이라 합니다. 제네릭을 사용하면 여러 목적으로 사용될 수 있는 컬렉션 형식을 만들 수 있습니다. 제네릭을 직접 만드는 것은 나중에 알아보고, 이번에는 닷넷에서 제공되는 내장된 제네릭 클래스들을 먼저 사용해 보겠습니다.
Stack
제네릭 클래스 사용하기
우리가 앞서 학습한 Stack
클래스의 제네릭 버전은 Stack<T>
형태로 표현합니다. 영어 발음으로는 Stack of T 형태로 발음합니다. T
는 특정 형식을 받아드리는 형식 매개 변수입니다. Stack
클래스의 제네릭 버전에 대한 다음 예제는 StackOfType.cs에서 볼 수 있습니다.
(1) 먼저 Stack<T>
클래스를 사용하려면 System.Collections.Generic
네임스페이스를 포함합니다.
> using System.Collections.Generic;
(2) 제네릭 클래스의 인스턴스를 생성하려면 Stack<T>
형태로 Stack<string>
으로 문자열만 다룰 수 있는 스택 클래스를 생성할 수 있습니다.
> Stack<string> stack = new Stack<string>();
(3) Stack<string>
으로 선언된 stack
개체는 문자열만 입력 받을 수 있습니다.
> stack.Push("First");
(4) 마찬가지로 Pop()
메서드의 결과도 문자열로 바로 출력됩니다.
> stack.Pop()
"First"
일반 클래스인 Stack
과 제네릭 클래스인 Stack<T>
는 하는 일을 동일합니다. 다만, Stack
클래스는 데이터를 object
로 다루고 Stack<T>
클래스는 T
로 지정한 데이터로 다룹니다. object
로 만들어진 데이터를 실제 사용하려는 string
과 같은 형식으로 사용하려면 중간에 변환의 과정을 거치기 때문에 이 부분에서 추가적인 작업을 진행하는 비용이 발생합니다. 그래서 정확한 데이터 형식을 사용할 수 있는 Stack<T>
와 같은 제네릭 클래스의 사용을 권장합니다.
제네릭 클래스 사용의 장점
제네릭 클래스에 대한 사용의 장점을 아래 단계별 코드로 살펴보겠습니다. 프로젝트 기반 코드는 GenericPros.cs 파일에 있습니다.
(1) 먼저 제네릭 클래스가 아닌 Stack
클래스를 사용해 보겠습니다. Stack
클래스는 System.Collections
네임스페이스에 있는 오래된 클래스입니다.
> //[1] 제네릭 사용 전
> using System.Collections;
> Stack stack = new Stack();
> stack.Push(3840);
> int width = (int)stack.Pop(); // Convert 필요
> width
3840
일반 클래스를 사용하면 Push()
메서드에 object
로 값을 받고 Pop()
메서드에서도 object
로 반환하기에 (int)
코드를 붙이는 형태와 같은 형식 변환이 필요합니다.
(2) Stack<T>
클래스는 제네릭 클래스로 System.Collections.Generic
네임스페이스에 존재합니다.
> //[2] 제네릭 사용 후
> using System.Collections.Generic;
> Stack<int> stack = new Stack<int>();
> stack.Push(2160);
> int height = stack.Pop(); // Convert 필요없음
> height
2160
제네릭 클래스를 사용하면 Stack<T>
클래스에 지정된 Stack<int>
형태로 int
형식의 데이터만 받고 Pop()
메서드에서도 int
형식으로 반환하기에 따로 형식 변환이 필요없습니다.
(3) Stack이 아닌 Stack<T>
클래스를 사용하면 값형 데이터를 참조형 데이터로 변환하는 박싱(Boxing)의 작업과 그 반대로 참조형 데이터를 값형 데이터로 변환하는 언박싱(UnBoxing)의 작업이 불필요하기에 성능 향상의 장점이 있고 Stack<int>
와 같이 명확하게 int
형식 데이터만을 받게 지정하였을 때 string
형식이 들어오면 에러를 내주기에 형식 안정성을 가질 수 있습니다.
> //[3] 제네릭의 장점: 성능 향상과 형식 안정성
> //[A] 박싱과 언박싱에 대한 비용
> Stack stack = new Stack();
> stack.Push(1234); // int(값형) to object(참조형): 박싱(Boxing): 포장
> int num = (int)stack.Pop(); // 참조형 to 값형: 언박싱(UnBoxing): 포장 풀기
> num
1234
>
> //[B] 필요한 데이터 형식만 사용하여 형식 안정성
> Stack<int> stack = new Stack<int>();
> stack.Push("Hello");
(1,12): error CS1503: Argument 1: cannot convert from 'string' to 'int'
> stack.Push(5678);
> int num = stack.Pop();
> num
5678
List<T>
제네릭 클래스 사용하기
List<T>
는 List of T로 읽습니다. 즉, 무언가의 리스트를 나타냅니다. 예를 들어 List<int>
는 정수형의 리스트를 나타내고 List<string>
은 문자열의 리스트를 나타냅니다. 참고로, 제네릭 클래스 중에서 박용준 강사가 제일 많이 사용하는 클래스입니다.
배열과 List<T>
제네릭 리스트
정수형 배열을 사용하여 2개의 데이터를 담고 출력하는 코드는 다음과 같습니다.
코드: ListOfInt.cs
> //[1] 배열 사용
> int[] arrNumbers = new int[2];
> arrNumbers[0] = 10;
> arrNumbers[1] = 20;
> for (int i = 0; i < arrNumbers.Length; i++)
. {
. Console.WriteLine(arrNumbers[i]); // 10, 20
. }
10
20
위 코드와 동일한 기능을 하는 코드를 제네릭 리스트를 사용하면 다음과 같이 작성할 수 있습니다.
코드: ListOfInt.cs
> //[2] 제네릭 리스트 사용: List<T>
> List<int> lstNumbers = new List<int>();
> lstNumbers.Add(30);
> lstNumbers.Add(40);
> for (int i = 0; i < lstNumbers.Count; i++)
. {
. Console.WriteLine(lstNumbers[i]); // 30, 40
. }
30
40
리스트 제네릭 클래스
C#에서 사용되는 제네릭 클래스 중에는 List<T>
제네릭 클래스가 자주 사용됩니다. 이를 사용하면 특정 형식에 해당하는 리스트(컬렉션, 배열)를 아주 쉽게 만들고 관리할 수 있습니다.
코드: ListOfString.cs
> //[?] 리스트 제네릭 클래스: List<T> => List<int>, List<string>, List<object>
> using System.Collections.Generic;
>
> //[0] 제네릭 리스트 사용을 위한 인스턴스 생성 및 샘플 데이터 입력
> List<string> colors = new List<string>();
> colors.Add("Red");
> colors.Add("Green");
> colors.Add("Blue");
>
> //[1] for 문으로 출력 예
> for (int i = 0; i < colors.Count; i++)
. {
. Console.Write($"{colors[i]}\t");
. }
Red. Green. Blue.
>
> //[2] foreach 문으로 출력 예
> foreach (var color in colors)
. {
. Console.Write($"{color}\t");
. }
Red. Green. Blue.
List<string>
, List<int>
, List<decimal>
과 같이 특정 형태를 담을 수 있는 컬렉션 클래스를 만들 수 있습니다. 이렇게 만들어진 제네릭 컬렉션 개체는 for
문 또는 foreach
문 등을 통해서 쉽게 가져다 사용할 수 있습니다.
C#에서의 for
와 foreach
순회
C#에서는 배열이나 리스트와 같은 컬렉션을 순회할 때 주로 for
와 foreach
문을 사용합니다. 각각의 사용법과 예제에 대해서 살펴보겠습니다.
for
문을 사용한 순회
for
문은 주어진 조건에 따라 명시된 횟수만큼 반복합니다. 배열이나 리스트의 길이를 기반으로 인덱스를 사용하여 순회할 수 있습니다.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
List<string> items = new List<string> { "apple", "banana", "cherry" };
for (int index = 0; index < items.Count; index++)
{
Console.WriteLine($"{index} {items[index]}");
}
}
}
foreach
문을 사용한 순회
foreach
문은 컬렉션의 각 요소를 순회합니다. 인덱스를 직접 사용하지 않기 때문에 코드가 간결해집니다. 하지만, 인덱스를 알아야 할 경우에는 추가적인 변수를 사용하여 인덱스를 수동으로 추적해야 합니다.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
List<string> items = new List<string> { "apple", "banana", "cherry" };
int index = 0;
foreach (var item in items)
{
Console.WriteLine($"{index} {item}");
index++;
}
}
}
C#에서는 for
와 foreach
문을 사용하여 컬렉션을 효과적으로 순회할 수 있습니다. 상황과 필요에 따라 적절한 방법을 선택하여 사용하면 됩니다.
Enumerable
클래스로 컬렉션 만들기
System.Linq
네임스페이스에 들어있는 Enumerable
클래스는 Range()
메서드와 Repeat()
메서드를 제공해서 테스트 목적으로 사용할 특정 범위의 정수 컬렉션을 손쉽게 만들 수 있습니다. Enumerable.Range()
메서드는 테스트용으로 컬렉션 만들 때 유용합니다.
Enumerable.Range(1, 10)
: 1에서 10까지의 정수 컬렉션 생성Enumerable.Range(0, 10)
: 0에서 9까지의 정수 컬렉션 생성Enumerable.Repeat(-1, 10)
: -1을 10번 반복하는 정수 컬렉션 생성
다음과 같이 Enumerable.Range(1, 10)
을 요청하면 1부터 10까지의 정수 컬렉션이 만들어 집니다. C# Interactive에서는 foreach
문 또는 for
문을 사용하지 않고도 바로 결괏값을 확인할 수 있습니다. 참고로, C# Interactive의 결괏값으로 나오는 RangeIterator
는 몰라도 됩니다.
> Enumerable.Range(1, 10)
RangeIterator { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
또 다른 유용한 메서드인 Repeat()
메서드에 (100, 5)
형태로 매개 변수를 전달하면 100
이 5
개인 정수 컬렉션이 만들어 집니다.
> Enumerable.Repeat(100, 5)
RepeatIterator { 100, 100, 100, 100, 100 }
콤마가 포함된 배열을 가져오려면 string.Join()
메서드와 함께 사용하면 편합니다.
> string.Join(",", Enumerable.Range(1, 5))
"1,2,3,4,5"
다음 샘플 코드는 메서드 체인(Chain) 방식으로 1부터 100까지의 정수 중 짝수를 구한 후 거꾸로 정렬한 다음에 10개를 제외하고 5개를 가져오는 내용입니다. 참고로, 다음 장에서 이러한 스타일의 코드를 좀 더 자세히 배웁니다.
> Enumerable.Range(1, 100).Where(i => i % 2 == 0).Reverse().Skip(10).Take(5)
TakeIterator { 80, 78, 76, 74, 72 }
Enumerable
클래스의 주요 메서드 사용하기
Enumerable
클래스의 Range()
메서드와 Repeat()
메서드를 사용하는 또 다른 예제를 프로젝트 기반 소스로 살펴보겠습니다.
코드: EnumerableDemo.cs
using System;
using System.Linq;
class EnumerableDemo
{
static void Main()
{
// 1부터 5까지
var numbers = Enumerable.Range(1, 5);
foreach (var n in numbers)
Console.Write("{0}\t", n);
Console.WriteLine();
// -1을 5개
var sameNumbers = Enumerable.Repeat(-1, 5);
foreach (var n in sameNumbers)
Console.Write("{0}\t", n);
Console.WriteLine();
}
}
1 2 3 4 5
-1 -1 -1 -1 -1
1부터 n
까지의 정수 컬렉션 또는 -1이 5개인 정수 컬렉션 등을 생성하여 컬렉션 관련 메서드를 테스트할 때 유용하게 사용될 2개의 API를 살펴보았습니다.
[실습] Dictionary<T, T>
제네릭 클래스 사용하기
소개
이번에는 사전(Dictionary) 개체를 사용하여 키와 값으로 데이터를 저장하고 사용할 수 있는 Dictionary
제네릭 클래스를 사용해 보겠습니다. 처음 보는 클래스들도 나올 수 있으니, 한번 쭉 실행해보는 관점으로 살펴보길 권장합니다.
프로젝트 기반 소스는 DictionaryGenericClassDemo.cs 파일에 있습니다.
따라하기
(1) C# Interactive를 실행합니다. 다음 진행되는 코드들을 단계별로 실행해봅니다. 주석은 따로 작성하지 않습니다. 경우에 따라서는 강의(책)의 소스를 열고 복사해서 붙여넣기 식으로 실행해봐도 됩니다.
(2) Dictionary<T, T>
제네릭 클래스는 문자열과 같은 키와 값으로 쌍으로 데이터를 관리합니다.
다음 코드는 string
키와 string
값을 컬렉션으로 관리할 수 있는 data
개체를 Dictionary
제네릭 클래스로 만드는 과정을 보여줍니다.
> //[1] Dictionary 클래스의 인스턴스 생성: IDictionary 인터페이스로 받기
> IDictionary<string, string> data = new Dictionary<string, string>();
>
물론, 다음과 같이 var
를 사용하여 줄여 표현해도 됩니다. 따라하기는 아래 코드로 진행합니다.
> var data = new Dictionary<string, string>();
>
(3) Add()
메서드로 데이터를 입력하고 Remove()
메서드를 사용하여 데이터를 삭제할 수 있습니다. 또한 배열의 []
기호를 사용하여 값을 추가할 수도 있습니다. 키 값은 중복을 허용하지 않기에 이미 있는 키 값을 추가할 때에는 에러가 발생합니다.
> //[2] 데이터 입력
> data.Add("cs", "C#");
> data.Add("aspx", "ASP.NET");
>
> //[3] 데이터 삭제
> data.Remove("aspx");
>
> //[4] 인덱서를 사용해서 데이터 입력
> data["cshtml"] = "ASP.NET MVC";
>
> //[5] 키 값 중복 불가: 에러 발생
> try
. {
. data.Add("cs", "CSharp");
. }
. catch (Exception ex)
. {
. Console.WriteLine(ex.Message);
. }
동일한 키를 사용하는 항목이 이미 추가되었습니다.
>
(4) 여러 가지 방식으로 Dictionary
개체의 값을 출력해 보겠습니다.
> //[6] 출력: foreach (var item in data)로 줄여 표현 가능
> foreach (KeyValuePair<string, string> item in data)
. {
. Console.WriteLine("{0}은(는) {1}의 확장자입니다.", item.Key, item.Value);
. }
cs은(는) C#의 확장자입니다.
cshtml은(는) ASP.NET MVC의 확장자입니다.
>
> foreach (var item in data)
. {
. Console.WriteLine("{0}은(는) {1}의 확장자입니다.", item.Key, item.Value);
. }
cs은(는) C#의 확장자입니다.
cshtml은(는) ASP.NET MVC의 확장자입니다.
>
> //[7] 인덱서를 사용해서 출력 가능
> Console.WriteLine(data["cs"]);
C#
>
(5) 없는 키 값을 요청하면 에러가 발생합니다. 이 때 TryGetValue()
또는 ContainsKey()
와 같은 메서드를 사용하여 확인 또는 값을 추가할 수 있습니다.
> //[8] 없는 키 요청: 에러 발생
> try
. {
. Console.WriteLine(data["vb"]);
. }
. catch (KeyNotFoundException knfe)
. {
. Console.WriteLine(knfe.Message);
. }
지정한 키가 사전에 없습니다.
>
> //[9] cs 키가 있으면 csharp 변수에 담아서 출력
> if (data.TryGetValue("cs", out var csharp))
. {
. Console.WriteLine(csharp);
. }
. else
. {
. Console.WriteLine("cs 키가 없습니다.");
. }
C#
>
> //[10] 키 값이 없으면 입력하고 출력
. if (!data.ContainsKey("json"))
. {
. data.Add("json", "JSON");
. Console.WriteLine(data["json"]);
. }
JSON
>
(6) 경우에 따라서는 키와 값들을 따로 컬렉션으로 뽑아서 사용이 가능합니다.
> //[11] Value 값을 따로 뽑아서 출력
> var values = data.Values;
> values
Dictionary<string, string>.ValueCollection(3) { "C#", "ASP.NET MVC", "JSON" }
>
> //[12] Key 값을 따로 뽑아서 출력
> var keys = data.Keys;
> keys
Dictionary<string, string>.KeyCollection(3) { "cs", "cshtml", "json" }
>
마무리
List<T>
와 마찬가지로 Dictionary<T, T>
제네릭 클래스는 사용 빈도가 높은 편입니다. 앞으로도 반복해서 나오겠지만, 키와 값으로 데이터를 저장하는 사전 개체에 대한 의미를 확인하는 시간이었습니다.
Span<T>
와 ReadOnlySpan<T>
사용하기
C# 7.2에서 도입된 Span<T>
와 ReadOnlySpan<T>
는 메모리 할당 없이 데이터를 효율적으로 다룰 수 있도록 도와주는 구조체입니다. 특히 배열이나 문자열을 복사 없이 조작할 때 유용합니다.
Span<T>
는 배열이나 메모리를 복사 없이 조작할 수 있습니다. 즉, 기존 데이터의 특정 부분을 가리키면서도 값을 변경할 수 있습니다.
다음 예제를 Visual Studio에서 실행해보세요. 참고로, C# 대화형에서는 실행되지 않습니다
코드: SpanDemo.cs
using System;
class SpanDemo
{
static void Main()
{
int[] numbers = { 1, 2, 3, 4, 5 };
Span<int> span = numbers; // 배열을 Span으로 감싸기
span[0] = 10; // 원본 배열도 변경됨
Console.WriteLine(string.Join(", ", numbers)); // 10, 2, 3, 4, 5
}
}
10, 2, 3, 4, 5
위 코드에서 Span<int>
는 numbers
배열을 감쌉니다. span[0]
값을 변경하면 원본 배열도 함께 바뀌는 것을 확인할 수 있습니다. 즉, 불필요한 메모리 복사 없이 기존 데이터를 직접 수정할 수 있습니다.
ReadOnlySpan<T>
는 Span<T>
의 읽기 전용 버전입니다. 즉, 데이터를 조작할 수는 없지만, 복사 없이 빠르게 읽을 수 있습니다.
코드: ReadOnlySpanDemo.cs
using System;
class ReadOnlySpanDemo
{
static void Main()
{
string text = "Hello, Span!";
// 문자열을 ReadOnlySpan으로 감싸기
ReadOnlySpan<char> readOnlySpan = text.AsSpan();
Console.WriteLine(readOnlySpan[0]); // H
// readOnlySpan[0] = 'X'; // 컴파일 에러 (읽기 전용)
}
}
H
여기서 ReadOnlySpan<char>
는 문자열을 복사 없이 감싸고 있습니다. 문자열의 특정 문자를 빠르게 읽을 수 있지만, 값을 변경하려고 하면 컴파일 오류가 발생합니다.
컬렉션 표현식
C# 12에서는 컬렉션 표현식(Collection Expressions)이 도입되어, List<T>
및 배열을 더욱 간결하게 초기화할 수 있습니다. 기존에는 장황한 초기화 코드가 필요했지만, 이제는 대괄호 []
를 사용하여 간편하게 작성할 수 있습니다.
배열을 초기화할 때 기존에는 char[] array = new char[] { 'x', 'y', 'z' };
처럼 명시적으로 개체를 생성해야 했지만, C# 12에서는 char[] array = ['x', 'y', 'z'];
형태로 단순화할 수 있습니다. 리스트 초기화도 마찬가지로, 기존에는 var chars = new List<char>() { 'a', 'b', 'c' };
처럼 작성해야 했지만, C# 12에서는 List<char> chars = ['a', 'b', 'c'];
처럼 더 간결하게 표현할 수 있습니다.
> // 기존 배열 초기화 방식
> char[] array = new char[] { 'x', 'y', 'z' };
> array
char[3] { 'x', 'y', 'z' }
> // C# 12에서의 배열 초기화 방식
> char[] array = ['x', 'y', 'z'];
> array
char[3] { 'x', 'y', 'z' }
> // 기존 리스트 초기화 방식
> var chars = new List<char>() { 'a', 'b', 'c' };
> chars
List<char>(3) { 'a', 'b', 'c' }
> // C# 12에서의 리스트 초기화 방식
> List<char> chars = ['a', 'b', 'c'];
> chars
List<char>(3) { 'a', 'b', 'c' }
C# 12의 컬렉션 표현식은 배열뿐만 아니라 List<T>
같은 컬렉션에도 동일하게 적용되며, var
를 활용하면 컴파일러가 자동으로 char[]
또는 List<char>
로 추론합니다.
스프레드 연산자
C# 12.0 버전에서는 새로운 문법 요소인 스프레드 연산자(Spread Operator, ..
)가 도입되었으며, 이는 컬렉션의 요소를 펼쳐서 보다 쉽게 삽입하거나 병합할 수 있도록 도와주는 기능입니다. 특히 배열과 리스트를 조작할 때 유용하게 활용할 수 있습니다.
배열에서 스프레드 연산자를 사용하면 기존 배열의 요소를 새로운 배열로 확장하여 삽입할 수 있습니다. 예를 들어, 다음과 같이 사용할 수 있습니다.
int[] first = { 1, 2, 3 };
int[] second = { 4, 5, 6 };
int[] combined = [.. first, .. second]; // { 1, 2, 3, 4, 5, 6 }
또한, 스프레드 연산자를 활용하면 기존 컬렉션의 요소를 포함하면서 개별 요소를 추가하는 것도 가능합니다. 예를 들어, 다음과 같은 코드가 가능합니다.
int[] numbers = [0, .. first, 99]; // { 0, 1, 2, 3, 99 }
리스트에서도 동일한 방식으로 사용할 수 있으며, 다음과 같이 리스트를 병합할 수도 있습니다.
List<int> list1 = [1, 2, 3];
List<int> list2 = [4, 5, 6];
List<int> mergedList = [.. list1, .. list2]; // { 1, 2, 3, 4, 5, 6 }
코드: SpreadOperator.cs
using System;
using System.Collections.Generic;
class SpreadOperator
{
static void Main()
{
// 배열 병합 예제
int[] first = { 1, 2, 3 };
int[] second = { 4, 5, 6 };
int[] combined = [.. first, .. second];
Console.WriteLine("배열 병합 결과: " + string.Join(", ", combined));
// 개별 요소 추가 예제
int[] numbers = [0, .. first, 99];
Console.WriteLine("개별 요소 추가 결과: " + string.Join(", ", numbers));
// 리스트 병합 예제
List<int> list1 = [1, 2, 3];
List<int> list2 = [4, 5, 6];
List<int> mergedList = [.. list1, .. list2];
Console.WriteLine("리스트 병합 결과: " + string.Join(", ", mergedList));
}
}
배열 병합 결과: 1, 2, 3, 4, 5, 6
개별 요소 추가 결과: 0, 1, 2, 3, 99
리스트 병합 결과: 1, 2, 3, 4, 5, 6
스프레드 연산자는 C# 12 이상에서만 사용할 수 있으며, List<T>
나 T[]
등의 컬렉션에서만 적용 가능합니다. 또한, 컬렉션을 확장할 때 새로운 컬렉션을 생성하는 방식이므로 원본 컬렉션 자체는 변경되지 않는다는 점을 유의하셔야 합니다.
장 요약
모든 값을 담을 수 있는 ArrayList
클래스 대신에 필요한 값을 선택해서 담을 수 있는 List<string>
, List<int>
와 같은 제네릭 클래스는 성능도 빠르고 사용하기도 편리합니다. 이러한 제네릭 클래스는 List<T>
와 같이 표현하며 이 전체를 표현하는 단어로는 Cup<T>
입니다. 제네릭은 C#에서 굉장히 중요하기에 앞으로 진행되는 대부분은 제네릭을 기반으로 사용이 될 것입니다.