제네릭(Generic)
제네릭(Generic)은 특정 데이터 타입에 의존하지 않고 다양한 타입을 다룰 수 있는 방법이다.
제네릭을 사용하면 코드의 재사용성이 높아지고, 컴파일 시 타입 안정성을 제공하며, 런타임 오류를 줄일 수 있다.
제네릭은 주로 클래스, 메서드, 인터페이스에서 사용된다고 한다.
제네릭 : 하나의 클래스가 다양한 형태의 자료형을 처리할수 있게됨.
C#에서 제네릭을 사용하는 예를 들어보면 다음과 같다.
제네릭 클래스
public class GenericClass<T>
{
private T data;
public GenericClass(T value)
{
data = value;
}
public T GetData()
{
return data;
}
}
위의 코드는 그냥 기본 틀이다. 아래의 작성된 코드를 가지고 예를 들어보자.
컵은 액체를담는다. 그러나 이것저것 다넣을수잇다. 라는걸 예시로 담기위해 아래 클래스를 만들었다.
class Milk
{
public string _ExpireDate;
public Milk(string ExpireDate)
{
_ExpireDate = ExpireDate;
}
public Milk() { }
}
class Coffee
{
public string CoffeeType;
}
class Water
{
public string WaterName;
}
class PaperClip
{
public string Material;
}
이제 이 위에것들이 아래 클래스에 사용될수 있게될것이다.
class Cup<T> where T : class// T에 class만 들어올수 있다.
{
T _contents;
float _fillAmount;
public T GetContents()
{
return _contents;
}
}
Cup<Milk> milkCup = new Cup<Milk>();
Cup<Milk, int> milkIntCup = new Cup<Milk, int>();
Cup<Water> waterCup = new Cup<Water>();
milkCup.GetContents()._ExpireDate = "asdf";
waterCup.GetContents().WaterName = "sdaf";
위에있는것처럼 T 부분에 사용할 클래스명을 넣고 바로바로 쓸수있다.
우리가 일일이 만들 필요가 없어지는것이다.
그리고 아래처럼 쓸수도 있다.
class Cup<T, U>
where T : class , new()//T에 class형식만 들어오고 ,
//인자값을 요구하지 않는 생성자가 있는 클래스만 들어올수 있게끔 제약.
where U : struct //이렇게도 된다. 다만 where는 하나당 하나씩쓰는게 좋다.
{
T _contents = new T();
U _fillAmount;
public T GetContents()
{
return _contents;
}
public void SetFillAmount(U amount)
{
_fillAmount = amount;
}
}
음 이렇게 쓰면 복잡해질수 있으므로 추천은 하지 않는단다.
제네릭 메서드
public class GenericMethodClass
{
public void ShowData<T>(T data)
{
Console.WriteLine(data);
}
}
제네릭 인터페이스
public interface IGenericInterface<T>
{
T ProcessData(T data);
}
제네릭을 사용하면 데이터 타입을 명시적으로 정의하지 않고도 다양한 타입의 데이터를 처리할 수 있다.
예를 들어, List<T>는 다양한 타입의 리스트를 관리할 수 있다고 한다.
제네릭은 코드를 더 유연하고 재사용 가능하게 하며, 타입 안전성을 제공하여 개발자의 실수를 줄이는 데 큰 도움이 된단다.
자 그럼 이걸 어떤때 쓰냐는걸 요약해보자.
여러 데이터 형식에 동일한 로직을 적용할때. 라고 요약을 해볼수 있겠다.
물론 내가 모르는 사용용도가 더 있다고 하는데 읽어는 봤지만 아직 써본 코드가 없기 때문에 나중에 써먹으면 추가로 C#코드 란에 추가할 예정이다.
아래는 면접때 제네릭에 대해서 자주 묻는 질문을 강사님이 준비 해주셔서 Copilot를 돌려서 물어본 내용이다.
이후 다른 블로그들도 찾아봤지만 이게 제일 깔끔해서 가져와 보았다.
1. 제네릭(Generic)은 어디에 왜 쓰나요? 어떤 이점을 제공하죠?
제네릭은 프로그래밍에서 매우 중요한 역할을 하며, 여러 면에서 이점을 제공합니다.
다음은 제네릭이 사용되는 주요 영역과 그 이유, 그리고 제공하는 이점들입니다:
- 타입 안전성: 컴파일 시점에 타입 검사를 수행하여 런타임 오류를 줄일 수 있습니다.
- 코드 재사용성: 동일한 코드를 다양한 데이터 타입에 대해 재사용할 수 있습니다.
- 가독성 향상: 명확하고 유지보수하기 쉬운 코드를 작성할 수 있습니다.
예를 들어, List<T>는 다양한 타입의 리스트를 지원합니다.
2. 제네릭에서 타입 제약 조건은 무엇이고, 왜 사용하나요?
제네릭 타입에 특정 제약 조건을 걸어 해당 타입이 특정 인터페이스를 구현하거나 특정 베이스 클래스를 상속받도록 할 수 있습니다. 이는 제네릭 클래스나 메서드가 해당 타입의 멤버를 안전하게 사용할 수 있도록 합니다.
예시:
public class Repository<T> where T : IEntity
{
public void Add(T item)
{
// IEntity 인터페이스의 멤버를 안전하게 사용 가능
}
}
위 코드에서 T는 IEntity 인터페이스를 구현해야 하며, 이는 Repository 클래스가 IEntity의 멤버를 사용할 수 있게 해줍니다.
3. 제네릭과 박싱/언박싱의 관계는 무엇인가요? 제네릭이 박싱을 어떻게 줄여주나요?
박싱(Boxing)은 값 타입을 객체 타입으로 변환하는 과정이며, 언박싱(Unboxing)은 그 반대 과정입니다.
이는 성능에 부담을 줄 수 있습니다.
제네릭은 박싱과 언박싱을 줄이는 데 도움을 줍니다.
제네릭을 사용하면 값 타입을 포함한 어떤 타입이든 타입 안전성을 유지하면서 컬렉션에 저장할 수 있으며, 이 과정에서 박싱이 필요하지 않습니다. 예를 들어, List<int>는 int 값을 박싱 없이 저장할 수 있습니다.
박싱과 언박싱의 예:
ArrayList list = new ArrayList();
list.Add(1); // 박싱 발생
int value = (int)list[0]; // 언박싱 발생
제네릭을 사용한 경우:
List<int> list = new List<int>();
list.Add(1); // 박싱 발생하지 않음
int value = list[0]; // 언박싱 발생하지 않음
이로 인해 제네릭을 사용하면 성능이 향상되고, 코드가 더 안전하고 효율적으로 작성될 수 있습니다.
개인적으론 제네릭을 사용하는이유는
여러 클래스들의 모음을 사용하는게 제일 클거같다.
우리가 코딩을 하다보면 비슷하지만 다른 코드들을 뭉쳐야할때가 있는데 저걸 일일이 생각하면서 찾고 하는것보단 이렇게 T 를 써서 임의로 사용할수 있게 해주는게 정말 좋은것같다.
그리고 제약조건 같은경우는 안하면 제대로 사용을 하지못하는데 , 아마 이게 코드의 안정성을 올려주는 것같다.
박싱 언박싱으로 사용하던걸 ms 에서 안전하게 사용할수있게 만들어 준것 같은데 정말 고맙게 잘쓸듯하다 ㅎㅎ
아무튼 오늘도 머리아프지만 신기하고 좋은 사용방법을 하나 더 배웠다...