플래그를 위한 enum 을 선언하는 경우, 각 플래그 값들로 하여금 이진수로 표현 시 단 하나의 1 값만 존재하여야 하는 플래그 특성에 부합함을 보장하기 위해 좌측 시프트 연산자 (<<) 를 사용할 수 있다.
이는 많은 수의 플래그들이 선언된 큰 크기의 enum 에 대해서도 높은 가독성을 보장하는 데에 도움을 줄 수 있다.
위의 예제에서 확인할 수 있듯이, MyEnum 이 적절한 플래그들을 설정하고 있음을 명확히 할 수 있으며 Flag30 = 1073741822 (혹은 이진수 111111111111111111111111111110) 와 같이 실제 사용에 적합하지 않은 혼란스러운 코드를 포함하지 않도록 할 수 있다.
FlagsAttribute 를 사용하면 ToString() 메소드가 매칭되는 enum 값에 대해 동작하는 방식을 변경할 수 있다:
[Flags]
enum MyEnum
{
// None = 0, 와 같은 값을 선언할 수는 있으나 비트 단위 (bitwise) 로 조합되어 사용될 수는 없다
FlagA = 1,
FlagB = 2,
FlagC = 4,
FlagD = 8// 비트 단위 (bitwise) 연산을 하용하기 위해서는// 2 의 제곱수나 2의 제곱수의 조합을 사용해야 한다
}
var twoFlags = MyEnum.FlagA | MyEnum.FlagB;
// 이는 해당 변수가 갖는 모든 플래그들을 열거해 줄 것이다: "FlagA, FlagB".
Console.WriteLine(twoFlags);
FlagsAttribute 속성은 열거형 상수들이 2의 제곱수 (혹은 2의 제곱수들의 조합) 이어야 하는 제약 조건이 있으며 enum 값은 기본적으로 숫자 형식이기 때문에, 내부 숫자 형식 타입의 크기에 제약을 받게 된다. 사용 가능한 가장 큰 숫자 형식 타입은 UInt64 로써, 이 타입을 사용할 경우 64 개의 각기 다른 (조합되지 않은) enum 플래그 상수를 선언할 수 있다. enum 키워드는 기본적으로 int 타입을 사용하게 되어 있으며, 이는 실제적으로 Int32 를 나타낸다. 컴파일러는 32 비트보다 더 큰 값을 선언할 수 있도록 허용하지만, 이러한 경우 경고 없이 wrap around 될 것이므로, enum 이 32 개 이상의 플래그 값을 표현해야 하는 경우에는, 기본보다 더 큰 타입을 명시적으로 기술해 주어야 한다:
publicenum BigEnum : ulong
{
BigValue = 1 << 63
}
플래그들은 대부분 하나의 비트만으로 표현되지만, 보다 편리한 사용을 위하여 이름이 주어진 세트로 조합될 수 있다.
enum 변수의 값이 특정 플래그 세트를 포함하고 있는지를 확인하기 위해서는 HasFlag 메소드를 사용할 수 있다. 예를 들어 다음과 같은 enum 이 있고
[Flags]
enum MyEnum
{
One = 1,
Two = 2,
Three = 4
}
변수의 값은 다음과 같이 선언되어 있을 때,
varvalue = MyEnum.One | MyEnum.Two;
HasFlag 를 이용하면 특정 플래그가 설정된 상태인지를 확인할 수 있다.
if(value.HasFlag(MyEnum.One))
Console.WriteLine("Enum has One");
if(value.HasFlag(MyEnum.Two))
Console.WriteLine("Enum has Two");
if(value.HasFlag(MyEnum.Three))
Console.WriteLine("Enum has Three");
또한 enum 의 모든 플래그들을 요소 반복하여 설정된 플래그들의 목록을 확인할 수도 있다.
var type = typeof(MyEnum);
var names = Enum.GetNames(type);
foreach (var name in names)
{
var item = (MyEnum)Enum.Parse(type, name);
if (value.HasFlag(item))
Console.WriteLine("Enum has " + name);
}
또는
foreach(MyEnum flagToCheck in Enum.GetValues(typeof (MyEnum))) {
if (value.HasFlag(flagToCheck)) {
Console.WriteLine("Enum has " + flagToCheck);
}
}
열거형 타입 (enumeration 혹은 enum 이라고도 이름붙여진) 은 변수에 할당 가능한 명명된 정수 숫자 형식의 상수 집합 을 선언할 수 있는 효율적인 방법을 제공한다.
기본적으로, enum 은 한정된 집합 내의 선택지만을 허용하는 타입이며 각 선택지는 특정 숫자에 해당하는 값을 가진다. 기본적으로, 이 숫자들은 선언된 순서대로 0 부터 하나씩 증가하는 값들이다. 사용 예를 보면, 요일을 나타내는 enum 을 다음과 같이 선언할 수 있다:
// 변수들로 하여금 특정 요일에 해당하는 값을 갖도록 정의한다
Day myFavoriteDay = Day.Friday;
Day myLeastFavoriteDay = Day.Monday;
// myFavoriteDay 에 해당하는 int 값을 얻어온다// 금요일은 숫자 4 에 해당한다int myFavoriteDayIndex = (int)myFavoriteDay;
// 숫자 5 로 표현되는 요일을 얻어온다
Day dayFive = (Day)5;
기본적으로 각 enum 요소들의 내부 타입은 int 이나, byte, sbyte, short, ushort, uint, long 그리고 ulong 타입 역시 사용될 수 있다. int 이외의 타입을 사용하고자 한다면, enum 의 이름 다음에 콜론 (:) 을 사용하여 원하는 타입을 기술하도록 한다:
publicenum Day : byte
{
// 이전과 동일한 부분
}
각 요일의 이름에 해당하는 숫자 값들은 이제 integer 가 아닌 byte 타입이 될 것이다. 특정 enum 이 내부적으로 표현되는 타입 정보는 아래와 같은 방법으로 얻어올 수 있다:
이는 네이티브 코드를 P/Invoke (플랫폼 호출) 한다거나, 데이터 소스와의 매핑을 시키는 등의 상황에서 유용하게 사용할 수 있다. 대부분의 개발자들은 enum 이 int 일 것으로 예상할 것이기 때문에, 일반적인 상황에서는 기본 타입인 int 를 사용하여야 할 것이다.
(shiftCount % array.Length) -> shift 를 시킬 값의 크기가 배열의 크기 한도 내에서만 존재하도록 값을 정규화 (normalize) 시킨다 (예를 들어 크기가 10 인 배열이라면, shift 연산을 1 만큼 수행하는 것과 11 만큼 수행하는 것은 동일한 결과를 나타낼 것이다. 이는 -1 과 -11 에 대해서도 동일하게 적용된다).
array.Length + (shiftCount % array.Length) -> 이는 좌측 방향으로의 rotate 수행 시 인덱스값이 음수가 되는 것을 방지하고, 대신에 배열의 끝쪽으로 rotate 가 수행되도록 한다. 이러한 코드가 없다면 크기가 10 인 배열에 대해서 index 가 0 이고 shift 시킬 값이 -1 이라면 계산된 인덱스값은 음수 (-1) 가 될 것이며 실제로 필요한 인덱스 값인 9 를 계산해내지 못할 것이다. (10 + (-1 % 10) = 9)
index + array.Length + (shiftCount % array.Length) -> 위에서 설명한 방식을 이용하여 각 index 에 대해 새로이 rotate 된 인덱스 값을 계산해 낸다. (0 + 10 + (-1 % 10) = 9)
index + array.Length + (shiftCount % array.Length) % array.Length -> 두번째 정규화 (normalization) 를 통해 새로운 인덱스 값이 배열이 범위를 벗어나지 않고 배열의 앞쪽으로 rotate될 수 있도록 한다. 이는 우측으로 rotate 되는 경우를 위함으로, 이 코드가 없다면 배열의 크기가 10 이고 shift 시킬 값이 1 인 상황에서 index 가 9 가 될 경우 새로운 인덱스 값은 10 이 될 것이며 실제로 필요한 인덱스 값인 0 을 계산해내지 못할 것이다. ((9 + 10 + (1 % 10)) % 10 = 0)
모든 배열들은 non-generic 버전의 IList 인터페이스를 (더불어 상위 인터페이스인 non-generic 버전의 ICollection 과 IEnumerable 를 포함하여) 구현 (implement) 하게 되어 있다.
더욱 중요한 점은, 1차원 배열들의 경우 해당 배열의 데이터 타입에 대하여 IList<> 와 IReadOnlyList<> generic 인터페이스를 (더불어 이들의 상위 인터페이스를 포함하여) 구현 (implement) 하게 되어 있다는 점이다. 이것이 의미하는 바는 이러한 배열들이 generic 한 열거형 (enumerable) 타입으로 취급될 수 있다는 것으로, 배열이 아닌 다른 형태로 변환시킬 필요 없이 다양한 함수들의 파라미터로 바로 사용될 수 있게 된다.
int[] arr1 = { 3, 5, 7 };
IEnumerable<int> enumerableIntegers = arr1; //배열이 IEnumerable<T> 를 구현 (implement) 하므로 변환이 허용된다
List<int> listOfIntegers = new List<int>();
listOfIntegers.AddRange(arr1); // List 의 내용물을 채우기 위해 배열에 대한 reference 를 사용할 수 있다.
위 코드 실행 시, listOfIntegers 는 3, 5, 그리고 7 의 값을 포함하는 List<int> 를 갖게 될 것이다.
IEnumerable<> 로의 변환 지원을 통해, 배열 내용을 LINQ 를 사용하여 조회할 수도 있다.