C# 6.0 완벽 가이드/ .NET Framework의 기초

Contents

문자열과 텍스트 처리

Char

  • .NET Framework System.Char 구조체의 별칭인 C#의 char는 유니코드 문자 하나를 나타내는 형식이다.
    • (이래서 Char로 선언하나 char로 선언하나 완전히 동등하다. string도 마찬가지. char는 예약어이다.)
  • char의 ToUpper와 ToLower는 최종 사용자의 local 설정을 존중하는데, 이 때문에 미묘한 버그가 생길 수 있다.
    • 터키어 환경에서 char.ToUpper(‘i’) == ‘I’ 는 false가 된다.
    • 이런 문제를 해결하기 위해 System.Char, System.String은 ToUpper와 ToLower에 문화권 불변 버전들도 제공한다. 그 버전들은 항상 영어권 규칙을 적용한다.
    • char.ToUpperInvariant(‘i’) 또는 char.ToUpper(‘i’, CultureInfo.InvariantCulture))
  • char를 명시적으로 정수로 캐스팅함으로써, 유니코드 문자집합에 속하지 않는 문자 값을 char 변수에 배정하는 것도 가능하다. 주어진 문자가 유효한 유니코드 문자인지 알고 싶으면 char.GetUnicodeCategory 메서드를 사용하면 된다. 이 메서드를 호출한 결과가 UnicodeCategory.OtherNotAssigned면 유효한 문자가 아닌 것이다.
  • char의 너비는 16비트이다. 이는 기본 다국어 평면 (Basic Multiilngual Plane, BMP)에 있는 모든 유니코드 문자를 표현하기에 충분한 크기이다. 그 밖의 문자를 표현하려면 유니코드 대체 쌍(surrogate pair)을 사용해야 한다.

String

문자열 생성

  • 문자열을 생성하는 가장 간단한 방법은 문자열 리터럴을 배정하는 것이다.
string s1 = "Hello";
  • 같은 문자가 여러 번 되풀이된 문자열을 원한다면 다음과 같이 인수 2개를 받는 string 생성자를 사용하면 된다.
new string ('*', 10);  // **********

널과 빈 문자열

  • 빈(empty) 문자열이란 길이가 0인 문자열이다. 빈 문자열은 리터럴을 이용해서 만들 수도 있고 정적 string.Empty 필드를 이용해서 만들 수도 있다.

문자열 내부 검색

  • 문자열 안에서 뭔가를 찾는 가장 간단한 방법은 StartsWith, EndsWith, Contains 메서드를 사용하는 것이다. 이들은 모두 true 또는 false를 돌려준다.
  • StartsWith와 EndsWith에는 대소문자 구분 여부와 문화권 구분 여부를 위해 StringComparison 열거형이나 CultureInfo 객체를 받는 중복적재 버전이 있다. 그런 인수를 받지 않는 기본 버전은 현재 지역화된 문화권 설정 규칙에 기초해서 대소문자 구분 없는 검색을 수행한다.
"abcdef".StartsWith("abc", StringComparison.InvariantCultureIngnoreCase)
  • Contains 메서드는 이런 편의용 중복적재를 제공하지 않는다. 대신 IndexOf 메서드를 이용해서 같은 결과를 얻을 수 있다.
  • IndexOf는 좀 더 강력한데, 이 메서드는 주어진 문자 또는 부분 문자열이 처음 출현한 위치의 색인을 돌려준다. 그런 부분 문자열이 없으면 -1을 돌려준다.
"abcde".IndexOf("cd");  // 2
  • IndexOf에는 StringComparison 열거형과 함께 검색 시작 위치를 지정하는 start Position 매개변수를 받는 중복적재 버전이 있다.
"abcde abcde".IndexOf("CD", 6, String.Comparison.CurrentCultureIgnoreCase));  // 8
  • LastIndexOf는 IndexOf와 같지만 문자열을 끝에서부터 거꾸로 검색한다는 점이 다르다.
  • IndexOfAny는 주어진 문자들 중 임의의 하나가 처음 등장하는 위치를 돌려준다.
"ab,cd ef".IndexOfAny(new char[] { ' ', ',' }));  // 2
"pass5w0ord".IndexOfAny("0123456789",ToCharArray()));  // 3
  • LastIndexOfAny는 같은 일을 역방향으로 수행한다.

문자열 조작

  • String 형식의 문자열은 불변이 객체이므로, 문자열을 ‘조작’하는 모든 메서드는 새로운 문자열 인스턴스를 돌려준다. 원본은 변경되지 않는다.
  • Substring 메서드는 문자열의 일부를 추출해서 돌려준다.
string left3 = "12345".Substring(1, 3);  // left3 == "234";
  • 길이를 생략하면 문자열의 나머지 부분을 얻게 된다.
string end3 = "12345".Substring(2);  // end3 == "345";
  • Insert 메서드와 Remove 메서드는 주어진 위치에서 문자들을 삽입, 삭제 한다.
string s1 = "helloworld".Insert(5, ", ");  // s1 == "hello, world"
string s2 = s1.Remove(5, 2);  // s2 == "helloworld"
  • PadLeft와 PadRight는 문자열이 주어진 길이가 될 때까지 지정된 문자열을 문자열 앞 또는 뒤에 채워 넣는다. 아무 문자도 지정하지 않으면 빈칸이 채워진다.
"12345".PadLeft(9, '*');  // ****12345
"12345".PadLeft(9); //     12345
// 입력 문자열이 지정된 길이보다 길면 원래의 문자열이 그대로 반환된다.
  • TrimStart와 TrimEnd는 문자열의 시작과 끝에서 지정된 문자들을 제거한다. 기본적으로 이 함수들은 공백 문자들(빈칸, 탭, 새줄, 그리고 이들의 유니코드 변종들)을 제거한다.
"   abc \t\r\n ".Trim().Length;  // 3
  • Replace는 첫 인수로 지정된 문자 또는 부분 문자열의 모든 출협을 둘쨰 인수로 지정된 문자열로 대체한다.
"to be done".Replace(" ", " | ");  // to | be | done
"to be done".Replace(" ", "  "); // tobedone
  • ToUpper와 ToLower는 입력 문자열의 대문자 버전과 소문자 버전을 돌려주는데, 기본적으로 최종 사용자의 현재 언어 설정을 존중한다. ToUpperInvariant, ToLowerInvariant는 항상 영어 알파벳 규칙을 적용한다.

문자열의 분할과 결합

  • Split은 하나의 문자열을 여러 조각으로 분할한다. 기본적으로 Split은 공백 문자들을 구분자로 사용하지만, char 또는 string 구분자들의 params 배열을 받는 중복적재 버전도 제공한다.
string[] words = "The quick brown fox".Split();  // The/ quick/ brown/ fox
  • Split에는 또한 StringSplitOptions 열거형 형식의 선택적 매개변수도 있는데, 이 매개변수는 빈 항목들의 제거 여부를 결정한다. 이것은 한 문자열 안의 구분자들이 연달아 나올 수 있는 문자열을 다룰 때 유용하다.
  • Join 메서드는 Split과 정반대의 일을 수행한다.
string[] words = "The quick brown fox".Split();
string together = string.Join(" ", words);  // The quick brown fox
  • 정적 Concat 메서드는 Join과 비슷하되 params 문자열 배열 하나만 받고, 구분자는 적용하지 않는다. Concat는 + 연산자와 정확히 동등하다. 실제로 컴파일러는 +를 Concat으로 바꾸어 컴파일한다.
string sentence = string.Concat("The", " quick", " brown", " fox");
string sameSentence = "The" + " quick" + " brown" + " fox";

String.Format과 복합 서식 문자열

  • 여러 변수 값을 서식 문자열의 특정 위치에 끼워 넣은 형태의 문자열을 구축할 때 편리한 수단이 정적 Format 메서드이다. 그 어떤 형식의 변수 값도 문자열에 내장할 수 있는데, Format 메서드는 그냥 주어진 값에 대해 ToString을 호출해서 문자열을 얻는다.
    • 값들을 내장할 전체 패턴에 해당하는 문자열을 복합 서식 문자열(composite format string)이라고 부른다. String.Format을 호출할 때는 값들이 내장될 위치를 나타내는 ‘변수’들이 포함된 복합 서식 문자열 다음에 그 위치 변수들에 대입할 값들을 지정한다.
string composite = "It's {0} degress in {1} on this 2} morning";
string s = string.Format(composite, 35, "Perth", DateTime.Now.DayOfWeek);
  • C# 6에서부터는 보간된 문자열 리터럴로도 이런 효과를 얻을 수 있다. 다음처럼 문자열을 $ 기호로 시작하고, 원하는 표현식을 중괄호로 감싸서 문자열 안에 내장하면 된다.
string s = $"It's hot this {DateTime.Now.DayOfWeek} morning";
  • 복합 서식 문자열 안에서 중괄호로 감싼 번호를 서식 항목(format item)이라고 부른다. 서식 항목들의 번호는 복합 서식 문자열 다음에 지정된 인수들의 위치에 대응된다.
    • 인수 번호 다음에 다음과 같은 요소를 추가할 수 있다.
      • 쉼표와 최소 너비 수치
      • 콜론(:)과 서식 문자열
    • 최소 너비는 출력의 열(column)을 정렬(alignment)하는데 유용하다. 음수를 지정하면 해당 자료가 왼쪽으로 정렬되고, 양수를 지정하면 오른쪽으로 정렬된다.
string composite = "Name = {0, -20} Credit Limit = {1,15:C}";
string.Format(composite, "Mary", 500);  // Name=Mary                    Credit Limite=               ₩500
string.Format(composite, "Elizabeth", 20000);  // Name=Elizabeth                    Credit Limite=               ₩20000
// Credit Limit 항목의 원화 표시는 서식 문자열 C 때문에 나타난 것이다.
  • 위 결과를 string.Format을 이용하지 않고 하는 방법은 아래와 같다.
string s = "Name=" + "Mary".PadRight(20) + " Credit Limit=" + 500.ToString("C").PadLeft(15);

문자열 비교

  • 두 값을 비교할 때 .NET Framework는 상등 비교(equality comparison)와 순서 비교(order comparisoin; 또는 대소 비교)를 구분한다. 상등 비교는 두 인스턴스가 의미론적으로 같은지를 판정한다. 반면 순서 비교는 두 인스턴스를 내림차순 또는 오름차순으로 나열할 때 두 인스턴스 중 어떤 것이 더 앞에 와야 하는지 판정한다. (둘 중 어떤 것도 더 앞에 오지 않는다는 결과가 날 수도 있다.)
  • 문자열 상등 비교에는 == 연산자를 사용할 수도 있고 string의 Equals 메서드를 사용할 수도 있다. 후자가 대소문자 구분 여부 같은 옵션들을 지정할 수 있다는 점에서 좀 더 유연하다.
    • 더불어 object 형식으로 캐스팅한 인스턴스들에 대해서는 == 연산자의 결과가 신뢰성이 떨어진다.
  • 문자열 순서 비교에는 인스턴스 메서드인 CompareTo나 정적 메서드인 Compare 또는 CompareOrdinal을 사용할 수 있다. 이들은 첫 문자열이 둘째 문자열보다 앞이어야 하면 양수, 뒤여야 하면 음수, 같은 위치여야 하면 0을 돌려준다.

서수 비교와 문화권 감지 비교

  • 문자열 비교 알고리즘은 서수(ordinal) 비교와 문화권 감지(culture-sensitive) 비교가 있다.
    • 서수 비교는 문자들을 그냥 수치로 해석해서 해당 유니코드 부호 수치 값에 따라 비교한다.
    • 문화권 감지 비교는 문자들을 특정 알파벳을 기준으로 해석한다. 문화권 설정에는 현재 문화권과 불변 문화권 설정이 있다.
  • 상등 비교에서는 서수 비교 알고리즘과 문화권 감지 비교 알고리즘이 모두 유용하다. 그러나 순서 비교에서는 거의 항상 문화권 감지 비교가 더 낫다.
    • “Atom”, “atom”, “Zamia”를 불변 문화권으로 비교하면 “Atom”, “atom”, “Zamia”가 되지만 서수 비교에서는 “Atom”, “Zamia”, “atom”이 된다.

문자열 상등 비교

  • string의 == 연산자는 항상 서수, 대소문자 구분 비교를 수행한다. string.Equals의 중복적재들 중 매개변수가 없는 버전 역시 그런 방식을 사용하는데, string 형식의 ‘기본’ 상등 비교 행동을 정의하는 것이 바로 그 버전이다.
    • string의 == 와 Equals에 서수 비교 알고리즘을 사용하는 것은 그 방식이 아주 효율적이고 결정론적(deterministic)이기 때문이다. 문자열 상등 비교는 근본적인 연산으로 간주되며, 순서 비교보다 훨씬 자주 수행된다.
  • Equals의 비교 옵션인 StringComparison은 다음과 같이 정의된 열거형이다.
    • CurrentCulture  // 대소문자 구분
    • CurrentCultureIgnoreCase // 대소문자 구분 없음
    • InvariantCulture // 대소문자 구분
    • InvariantCultureIgnoreCase // 대소문자 구분 없음
    • Ordinal // 대소문자 구분
    • OrdinalIgnoreCase // 대소문자 구분 없음

문자열 순서 비교

  • string의 CompareTo 인스턴스 메서드는 문화권 감지, 대소문자 구분 방식으로 순서를 비교한다. == 연산자와 달리 CompareTo는 서수 비교를 하지 않는다. 순서 비교에서는 문화권 감지 알고리즘이 훨씬 유용하다.

StringBuilder 클래스

  • StringBuilder 클래스는 가변이 문자열을 나타낸다. StringBuilder를 이용하면 문자열 전체를 새 문자열로 대체하지 않고도 문자열에 부분문자열을 추가(Append) 또는 삽입(Insert) 할 수 있고 문자열의 일부를 삭제(Remove) 할 수도 있다.
    • AppendLine을 이용하면 새 줄 문자열을 추가한다. (Windows의 경우 \r\n)
    • AppendFormat은 String.Format처럼 복합 서식 문자열을 받는다.
  • StringBuilder에는 string 처럼 Insert, Remove, Replace가 있으며, Length 속성과 개별 문자의 설정/조회를 위한 쓰기 가능 인덱서도 있다.
  • StringBuilder의 내용을 비우려면 새 StringBuilder 인스턴스를 배정하거나 Length 속성을 0으로 설정하면 된다.
    • StringBuilder의 Length 속성을 0으로 설정해도 내부 용량이 줄어들지는 않는다. 메모리를 해제하려면 새 StringBuilder 인스턴스를 생성하고 기존 인스턴스가 범위 밖으로 나가서 쓰레기 수거기가 수거할 수 있게 해야 한다.

텍스트 부호화와 유니코드

  • 문자 집합(character set)은 일단의 문자들에 수치 부호를 부여한 것으로 그런 수치부호를 부호점(code point)라고 부른다.
  • 흔히 쓰는 문자 집합은 유니코드와 ASCII이다.
    • 유니코드는 약 100만개의 문자를 담을 수 있는 주소 공간을 가지고 있는데, 현재 그 중 10만자 정도가 할당되어 있다. 전세계의 대부분의 구어(spoken language)들이 유니코드에 포함되어 있으며, 몇몇 역사적 언어들과 특수 기호들도 포함되어 있다.
    • ASCII 문자 집합은 유니코드의 처음 128자에 해당하는데, 북미 스타일 키보드에서 볼 수 있는 영묹들과 기호들이 이 ASCII 문자 집합에 속한다. ASCII는 유니코드보다 약 30년 전에 나온 것으로, 문자 하나가 1바이트에 대응된다는 단순함과 효율성 때문에 지금도 종종 쓰인다.
  • .NET의 형식 체계는 유니코드 문자 집합에 맞게 작동하도록 설계되어 있다. .NET Framework는 암묵적으로 ASCII를 지원하나, 이는 단지 ASCII가 유니코드의 부분집합이기 때문이다.
  • 텍스트 부호화(text encoding)는 문자의 수치 부호점을 그 이진 표현으로 대응시키는 것을 말한다. .NET에서 텍스트 부호화는 주로 텍스트 파일이나 스트림을 다룰 때 중요하게 쓰인다.
    • 텍스트 파일의 내용을 문자열로 읽어 들일 때 텍스트 부호화기(text encoder)가 파일의 이진 자료를 내부 유니코드 문자 표현(char와 string 형식이 기대하는)으로 변환한다.
    • 어떤 텍스트 부호화 방식을 사용하느냐에 따라 표현 가능한 문자들이 달라지며, 텍스트 저장 효율성도 달라진다.
  • .NET의 텍스트 부호화 방식들은 크게 두 범주로 나뉜다.
    • 유니코드 문자를 다른 문자 집합에 대응시키는 부호화들
    • 표준 유니코드 부호화 방식을 사용하는 부호화들
  • 첫 범주에는 IBM의 EBCDIC 같은 구식 부호화나 사우이 128자 영역에 확장 문자들이 있는 8비트 문자 집합들(유니코드 이전에 널리 쓰였던)로의 부호화가 속한다. ASCII 역시 이 범주에 해당한다.
    • ASCII 방식은 처음 128자를 부호화하며, 나머지는 폐기한다.
    • 또한 2000년부터 중국에서 작성된 응용 프로그램의 필수 표준인 비구식(nonlegacy) GB18030도 이 범주에 속한다.
  • 둘째 범주의 부호화 방식들은 UTF-8, UTF-16, UTF-32 (그리고 폐기된 UTF-7)이다.
    • UTF-8은 대부분의 텍스트에서 공간 효율성이 가장 좋은 부호화 방식이다. UTF-8은 한 문자를 최소 1바이트, 최대 4바이트로 표현한다.
      • UTF-8은 유니코드의 처음 128자를 1바이트만으로 표현하며, 그래서 ASCII와 호환된다. 텍스트 파일이나 스트림에서 가장 인기 있는 부호화가 UTF-8이다 (특히 인터넷에서)
      • UTF-8은.NET 스트림 입출력의 기본 부호화 방식이다 (스트림 입출력 뿐만 아니라, 사실 암묵적으로 부호화를 사용하는 거의 모든 것의 기본 부호화 방식이다)
    • UTF-16은 한 문자를 16비트 워드 하나 또는 두 개로 표현한다. UTF-16은 .NET 내부에서 문자와 문자열을 표현하는데 쓰인다. 파일을 UTF-16으로 기록하는 프로그램들도 있다.
    • UTF-32는 유니코드의 각 부호점을 그대로 32비트 수치에 대응시키기 때문에 공간 효율성이 가장 낮다. 그래서 거의 쓰이지 않는다. 그러나 모든 문자가 같은 수의 바이트를 차지하기 때문에 임의 접근이 아주 쉽다는 장점이 있다.

Encoding 객체 얻기

  • System.Text의 Encoding 클래스는 텍스트 부호화를 캡슐화하는 클래스들의 공통 기반 형식이다.
    • .NET Framework에는 Encoding을 상속한 파생 클래스들이 많이 있는데, 이 파생 클래스들의 목적은 비슷한 특징을 가진 부호화 부류들을 캡슐화하는 것이다.
    • 적절히 설정된 Encoding 객체를 얻는 가장 쉬운 방법은 표준 IANA(Internet Assigned Numbers Authority) 문자 집합 이름으로 Encoding.GetEncoding 메서드를 호출하는 것이다.
Encoding utf8 = Encoding.GetEncoding("utf-8");
Encoding chinese = Encoding.GetEncoding("GB18030");
  • 흔히 쓰이는 부호화들에 해당하는 Encoding 객체는 Encoding 의 정적 속성들로도 얻을 수 있다.
부호화 이름 Encoding의 정적 속성
UTF-8 Encoding.UTF8
UTF-16 Encoding.Unicode (UTF16이 아님)
UTF-32 Encoding.UTF32
ASCII Encoding.ASCII

파일 및 스트림 입출력에 대한 Encoding 객체

  • Encoding 객체의 가장 흔한 용도는 파일이나 스트림에서 텍스트를 읽거나 기록하는 방식을 제어하는 것이다. 예컨대 아래는 data.txt라는 파일에 “Testing…”이라는 문자열을 UTF-16 부호화 방식으로 기록하는 코드이다.
System.IO.File.WriteAllText("data.txt", "Testing...", Encoding.Unicode);  
// 마지막 인수를 생략하면 UTF-8 부호화를 적용한다.

바이트 배열에 대한 Encoding 객체

  • 문자열과 바이트 배열 사이의 변환에도 Encoding 객체를 사용할 수 있다. GetBytes 메서드는 string을 주어진 부호화 방식에 따라 byte[]로 변환한다. GetString은 byte[]를 string으로 변환한다.

UTF-16과 유니코드 대체 쌍

  • .NET은 문자와 문자열을 UTF-16으로 저장한다.
    • 그런데 UTF-16은 문자 하나를 16비트 워드 하나 또는 두 개로 표현하지만 char는 길이가 16비트 밖에 되지 않는다. 따라서 일부 유니코드 문자를 표현하려면 char 두 개가 필요하다. 이로부터 다음과 같은 두 가지 결과가 비롯된다.
      • 문자열의 Length 속성이 실제 문자 갯수보다 클 수 있다.
      • 하나의 유니코드 문자를 완전히 표현하는데 항상 char 하나로 충분한 것은 아니다.
  • 대부분의 응용 프로그램은 이 두 사항을 무시한다.
    • 흔히 쓰이는 거의 모든 문자는 유니코드 문자 공간 중 기본 다국어 평면(Basic Multilingual Plane, BMP)이라고 부르는 영역에 속하며, 그 평면의 모든 문자는 UTF-16에서 16비트 워드 하나로 표현할 수 있기 때문이다.
    • 이 평면에 속하지 않는 문자들로는 일부 고대 언어 문자들과 음악 표기용 기호들, 그리고 덜 자주 쓰이는 한자들이 있다.
  • 2워드 문자(워드 2개짜리 문자)를 지원해야 한다면 다음과 같은 char의 정적 메서드들이 필요하다. 첫째 것은 32비트 부호점을 char 두 개짜리 문자열로 변환하고, 둘째 것은 그 반대로 변환한다.
string ConvertFromUtf32 (int utf32)
int ConvertToUtf32 (char highSurrogate, char lowSurrogate)
  • 이처럼 한 문자를 워드 두 개로 표현한 것을 대체 쌍(surrogate pair)이라고 부른다.
    • 대체 쌍을 식별하는 것은 간단하다. 두 워드 모두 0xD800에서 0xDFFF까지의 값이기 때문이다.
    • 또는 char의 다음 정적 메서드들을 사용해서 식별할 수도 있다.
bool IsSurrogate (char c)
bool IsHighSurrogate (char c)
bool IsLowSurrogate (char c)
bool IsSurrogatePair (char highSurrogate, char lowSurrogate)
  • 대체로 BMP 바깥의 문자들은 특별한 글꼴(font)가 있어야 화면에 표시되며, 운영체제의 지원도 제한적이다.

날짜와 시간

TimeSpan

  • TimeSpan은 일정한 길이의 시간 구간(interval of time) 또는 하루 중 시간(시각)을 나타낸다.
    • 후자의 경우에는 그냥 ‘시계’ 시간 (날짜는 없는), 즉 자정에서부터 흐른 시간에 해당한다. 여기에 일광절약시간 설정은 반영되지 않는다.
    • TimeSpan의 해상도는 100ns(나노초)이고 최댓값은 1천만 일(days)에 해당하는 시간이다. 그리고 음수도 지원한다.
  • TimeSpan 객체를 구축하는 방법은 크게 3가지 이다.
    • 생성자 중 하나를 호출한다.
    • 정적 From… 메서드 중 하나를 호출한다.
    • 두 DateTime 객체를 더하거나 뺀다.
public static TimeSpan FromDays (double value);
public static TimeSpan FromHours (double value);
public static TimeSpan FromMinutes (double value);
public static TimeSpan FromSeconds (double value);
public static TimeSpan FromMilliseconds (double value);
  • TimeSpan은 <, >, +, – 연산자를 중복적재한다. 다음 표현식은 2.5 시간에 해당하는 TimeSpan으로 평가된다.
TimeSpan.FromHours(2) + TimeSpan.FromMinutes(30);
  • TimeSpan에는 일, 시, 분, 초, 밀리초에 해당하는 정수 속성이 있다. 반면 Total… 속성들은 double 형식의 값을 돌려주기 때문에 해당 요소의 값을 온전하게 표현한다.
TimeSpan nearlyTenDays = TimeSpan.FromDays(10) - TimeSpan.FromSeconds(1);
Console.WriteLine(nearlyTenDays.Days);  // 9
Console.WriteLine(nearlyTenDays.Hours); // 23
Console.WriteLine(nearlyTenDays.Minutes); // 59
Console.WriteLine(nearlyTenDays.Seconds); // 59
Console.WriteLine(nearlyTenDays.Milliseconds); // 0
Console.WriteLine(nearlyTenDays.TotalDays);  // 9.999...
Console.WriteLine(nearlyTenDays.TotalHours); // 239.999...
Console.WriteLine(nearlyTenDays.TotalMinutes); // 14399.89333..
Console.WriteLine(nearlyTenDays.TotalSeconds); // 863999
Console.WriteLine(nearlyTenDays.TotalMilliseconds); // 863999000
  • 정적 Parse 메서드는 ToString과 반대되는 일을 한다. 즉, 문자열을 TimeSpan으로 변환한다. TryParse도 같은 일을 하는데, 변환 실패시 예외를 던지는 대신 false를 돌려준다.
  • TimeSpan의 기본값은 TimeSpan.Zero이다.
  • TimeSpan으로 하루 중 시간을 나타낼 수 있다. 현재 시간을 얻으려면 DateTime.Now.TimeOfDay를 호출하면 된다.

DateTime과 DateTimeOffset

  • DateTime과 DateTimeOffset은 날짜와 시간을 나타내는 불변이 구조체들이다. 이들의 해상도는 100ns이고 범위는 0001년에서 9999년까지 포괄한다.
  • DateTimeOffset은 .NET Framework 3.5에서 추가되었는데, 근본적으로는 DateTime과 비슷하다. 특징은 UTC 오프셋도 저장한다는 것이다. 이 덕분에 서로 다른 시간대의 값들을 비교할 때 좀 더 의미있는 결과를 얻을 수 있다.

DateTime과 DateTimeOffset의 선택

  • DateTime과 DateTimeOffset은 시간대 처리 방식이 다르다. DateTime에는 DateTime 인스턴스에 담긴 시간의 기준을 나타내는 3상태 플래그가 있다. 가능한 기준 설정은 총 3가지이다.
    • 현재 컴퓨터의 지역 시간
    • UTC (협정세계시; 예전의 그리니치 표준시에 해당)
    • 기준이 명시되지 않음
  • DateTimeOffset은이보다좀더구체적인데, DateTimeOffset은 다음과 같은 UTC 시간으로부터의 오프셋을 하나의 TimeSpan 객체에 저장한다.
    • July 01 2007 03:00:00 -06:00
  • 이러한 차이는 상등 비교에 영향을 미친다. DateTime과 DateTimeOffset 중 하나를 선택할 때 주된 고려 사항이 바로 상등 비교 특징이다. 상등 비교와 관련된 둘의 차이를 좀 더 구체적으로 설명하면 다음과 같다.
    • DateTime은 비교 시 3상태 플래그를 무시하고, 만일 두 값의 연, 월, 일, 시, 분 등이 같으면 둘이 같다고 간주한다.
    • DateTimeOffset은 두 값이 시간상의 같은 지점을 가리킬 때만 같다고 간주한다. 예컨대 다음 두 값을 DateTime은 다르다고 간주하지만, DateTimeOffset은 같다고 간주한다.
      • July 01 2007 09:00:00 +00:00
      • July 01 2007 03:00:00 -06:00
  • 대부분의 경우에는 DateTimeOffset의 상등 논리가 바람직하다. 예컨대 두 국제 행사 중 어느 것이 더 최근인지 계산하는 경우 DateTimeOffset을 사용하면 별다른 처리 없이도 올바른 답을 얻을 수 있다. 마찬가지로 전 세계적인 분산 서비스 거부(DDos) 공격을 준비하는 해커라면 DateTimeOffset을 선택할 것이다.
    • DateTime으로 같은 일을 하려면 응용 프로그램 전체에서 모든 시간을 특정한 하나의 시간대로 정규화해야 한다.

DateTime 객체 생성

  • DateTime에는 연, 월, 일에 해당하는 정수들을 받는 생성자와 시, 분, 초, 밀리초 정수들까지 받는 생성자가 있다.
    • 날짜 요소들만 지정하는 경우 시간은 암묵적으로 자정(00:00)으로 설정된다.
  • DateTime은 또한 DateTimeKind 열거형 값을 받는 생성자들도 제공한다. 가능한 값은 다음 3가지 이다.
    • Unspecified, Local, Utc
  • DateTime은 또한 Calendar 객체를 받는 생성자도 제공한다. 이를 통해 System.Globalization에 정의되어 있는 임의의 Calendar 파생 클래스들을 이용해서 날짜를 지정할 수 있다.
DateTime d = new DateTime(5767, 1, 1 new System.Globalization.HebrewCalendar())l  // 2006-09-23 오전 12:00:00  // DateTime은 항상 기본 그레고리력을 사용한다.
  • long 형식의 틱 수(ticks)를 지정해서 DateTime 객체를 생성할 수도 있다. 여기서 틱 수는 0001-01-01 자정부터 주어진 시간까지 흐른 틱들의 개수이고, 1틱은 100ns(나노초)이다
  • 상호운용성을 위해 DateTime은 Windows 파일 시간 (long 형식의 값으로 지정)과의 변환을 위한 정적 FromFileTime과 FromFileTimeUtc 메서드, 그리고 OLE 자동화 날짜/시간(double 형식으로 지정)과의 변환을 위한 정적 FromOADate 메서드를 제공한다.
  • 문자열로부터 DateTime을 생성할 때는 정적 Parse 메서드나 ParseExact 메서드를 호출한다. 두 메서드 모두 선택적인 플래그들과 서식 공급자를 받는다.
    • ParseExact는 서식 문자열도 받는다.

DateTimeOffset 객체 생성

  • DateTimeOffset에도 비슷한 생성자들이 있다. 차이는 UTC 오프셋에 해당하는 TimeSpan도 지정한다는 것이다.
    • 여기서 TimeSpan은 반드시 분 단위로 떨어지는 값이어야 한다. 그렇지 않으면 예외가 발생한다.
public DateTimeOffset (int year, int month, int day, int hour, int minute, int second, TimeSpan offset);
public DateTimeOffset (int year, int month, int day, int hour, int minute, int second, int millisecond, TimeSapn offset);

현재 DateTime/DateTimeOffset

  • DateTime과 DateTimeOffset 모두 현재 날짜 및 시간을 돌려주는 Now라는 속성이 있다.
    • DateTime에는 날짜 부분만 돌려주는 Today라는 속성도 있다.
DateTime.Now;
DateTimeOffset.Now;
DateTime.Today

날짜와 시간 다루기

  • DateTime은 다양한 날짜/ 시간 요소들을 돌려주는 인스턴스 속성들을 제공한다. DateTimeOffset도 이런 인스턴스 속성들을 제공한다.
    • DateTimeOffeset에는 TimeSpan 형식의 Offset 속성도 있다.
DateTime dt = new DateTime(2000, 2, 3, 10, 20, 30);
Console.WriteLine(dt.Year);  //2000
Console.WriteLine(dt.Month);  //2
Console.WriteLine(dt.Day);  //3
Console.WriteLine(dt.DayOfWeek);  //Thursday
Console.WriteLine(dt.DayOfYear);  //34
Console.WriteLine(dt.Hour);  //10
Console.WriteLine(dt.Minute);  //20
Console.WriteLine(dt.Second);  //30
Console.WriteLine(dt.Millisecond);  //0
Console.WriteLine(dt.Ticks);  //63085170030000000
Console.WriteLine(dt.TimeOfDay);  //10:20:30 (TimeSpan을 돌려줌)
  • 두 형식 모두 날짜 계산을 위한 다음과 같은 인스턴스 메서드들을 제공한다. 이들은 모두 새로운 DateTime (또는 DateTimeOffset)을 돌려주며, 계산시 윤년 같은 요인들을 고려한다.
    • 뺄셈을 원하면 음의 값을 인수로 넣으면 된다.
AddYears, AddMonths, AddDays, AddHours, AddMinutes, AddSeconds, AddMilliseconds, AddTicks
  • Add 메서드는 DateTime이나 DateTimeOffset에 TimeSpan을 더한다. + 연산자도 동일한 연산을 수행하도록 중복 적재 되어 있다.
TimeSpan ts = TimeSpan.FromMinute(90);
Console.WriteLine(dt.Add(ts));
Console.WriteLine(dt + ts); // 위와 같음

파싱과 서식화

  • DateTime 객체에 대해 ToString을 호출하면 짧은 날짜(년, 월, 일이 숫자로 된) 다음에 긴 시간 (초 포함)이 오는 형태의 결과가 반환된다.
    • DateTimeOffset에 대한 ToString도 오프셋이 추가된다는 점만 빼고는 동일한 결과를 돌려준다.
    • 연, 원, 일의 순서나 선행 0 여부, 12시간 또는 24시간 여부 등은 운영체제의 제어판 설정을 따른다.
2015-11-11 오전 11:50:30 //DateTime.Now.ToString()
2015-11-11 오전 11:50:30 +09:00 //DateTimeOffset.Now.ToString()
  • ToShortDateString 메서드와 ToLongDateString 메서드는 날짜 부분만 돌려준다. 후자는 긴 날짜를 돌려주는데, 그 서식은 역시 제어판이 결정한다.
    • 영어권에서는 이를테면 “Wednesday, 11 November 2015” 같은 문자열이 반환된다.
    • ToShortDateString과 ToLongDateString은 이를테면 17:10:10 같은 시간 부분만 돌려준다.
  • 정적 Parse/TryParse와 ParseExact/TryParseExact 메서드들은 ToString과 반대 되는 변환을 수행한다. 즉 문자열을 DateTime이나 DateTimeOffset으로 변환한다.

널 값을 가진 DateTime과 DateTimeOffset

  • DateTime과 DateTimeOffset은 구조체이므로 그 자체로 널 가능 형식은 아니다. 널 값이 필요한 상황이라면 다음의 2가지 우회책 중 하나를 사용하면 된다.
    • Nullable 형식 (DateTime? 또는 DateTimeOffset?)을 사용한다.
    • 정적 필드 DateTime.MinValue나 DateTimeOffset.MinValue를 널처럼 사용한다.
  • 일반적으로는 널 가능 형식을 선택하는 것이 최선이다. 컴파일러가 널 관련 실수를 방지해 주기 때문이다.

날짜와 시간대

DateTime과 시간대

  • DateTime은 시간대를 아주 단순하게만 처리한다. 내부적으로 DateTime은 두 가지 정보를 저장한다.
    • 0001-01-01에서부터 흐른 틱 수를 뜻하는 62비트 수치
    • DateTimeKind를 나타내는 2비트 열거형 값 (Unspecified나 Local, Utc)
  • 두 DateTime 인스턴스를 비교할 때는 틱 수만 비교한다. DateTimeKind는 무시된다.
  • 인스턴스 메서드 ToUniversalTime과 ToLocalTime은 컴퓨터의 현재 시간대 설정에 기초해서 시간을 각각 UTC 시간과 지역 시간으로 변환한다.
    • 이 메서드들은 DateTimeKind가 Utc 또는 Local인 새 DateTime을 돌려준다.
    • 이미 Utc인 DateTime에 대해 ToUniversalTime을 호출하면 아무련 변환도 일어나지 않는다. Local인 경우도 마찬가지로 ToLocalTime을 호출하면 아무런 변환도 수행하지 않는다.
  • 정적 메서드 DateTime.SpecifyKind를 이용하면, 주어진 DateTime과 Kind만 다른 새 DateTime을 생성할 수 있다.

DateTimeOffset과 시간대

  • 내부적으로 DateTimeOffset은 DateTime 필드 하나와 16비트 정수 필드 하나로 구성된다.
    • DateTime 필드의 값은 항상 UTC 기준이며, 정수 필드의 값은 분 단위 UTC 오프셋이다.
    • 두 DateTimeOffset 인스턴스를 비교할 때는 DateTime(UTC)만 본다. Offset은 기본적으로 서식화를 위한 것이다.
  • ToUniversalTime/ToLocalTime 메서드는 시간상의 같은 지점을 나타내는 DateTimeOffset 인스턴스들을 돌려주나, 전자가 돌려주는 인스턴스는 오프셋이 UTC이고 후자는 지역 시간대이다. DateTime과는 달리 이 메서드들은 바탕 날짜/시간 값에 영향을 미치지 않고 오직 오프셋에만 영향을 미친다.

TimeZone과 TimeZoneInfo

  • TimeZone 클래스와 TimeZoneInfo 클래스는 시간대 이름, UTC 오프셋, 일광절약 시간제 규칙에 관한 정보를 제공한다. 둘 중 TimZoneInfo가 더 강력하다.
    • 두 형식의 가장 큰 차이점은 TimeZone으로는 현재 지역 시간대에 관한 정보만 얻을 수 있지만 TimeZoneInfo로는 세계의 모든 시간대에 관한 정보를 얻을 수 있다는 점이다.
    • 그리고 일광절약시간제에 관해서 TimeZoneInfo가 좀 더 풍부한 규칙 기반 모형을 제공한다.
  • (이하 TimeZone과 일광절약 시간제 적용 내용 생략)
  • DateTimeOffset이나 UTC DateTime을 사용하는 경우에는 일광절약시간제가 상등 비교에 영향을 미치지 않는다. 그러나 지역 DateTime을 사용할 때는 일광절약시간제 때문에 문제가 생길 수 있다.

서식화와 파싱

  • 서식화(formatting)은 뭔가를 문자열로 바꾸는 것이고, 파싱(parsing)은 문자열을 뭔가로 바꾸는 것이다.
  • .NET Framework는 다음과 같은 다양한 서식화 및 파싱 메커니즘을 제공한다.
    • ToString과 Parse
    • 서식 공급자
    • XmlConvert
    • 형식 변환기

ToString과 Parse

  • 가장 간단한 서식화 메커니즘은 ToString 메서드이다. 이 메서드는 모든 단순 값 형식에 대해 이해할만한 결과를 돌려준다. 그 반대의 변환에는 모든 단순 값 형식에 정의되어 있는 정적 Parse 메서드를 사용하면 된다.
  • DateTime과 DateTimeOffset 수치 형식들에 대한 Parse와 TryParse는 지역 문화권 설정을 존중한다.
    • 현재 설정된 문화권 이외의 문화권을 적용하고 싶으면 적절한 CultureInfo 객체를 지정함녀 된다. 불변 문화권을 지정하는 것이 바람직한 경우가 많다.
    • 예컨대 독일에서 “1.234”를 double로 변환하면 1234가 된다.

서식 공급자

  • 서식화나 파싱이 작동하는 방식을 좀 더 세밀하게 제어해야 하는 경우가 있다. 예를 들어 DateTime(Offset)을 서식화하는 방법은 수십 가지이다. 서식 공급자를 이용하면 서식화와 파싱을 아주 세밀하게 제어할 수 있다.
  • 서식 공급자를 사용하려면 IFormattable이라는 인터페이스를 거쳐야 한다.
    • 모든 수치 형식과 DateTime(Offset)은 이 인터페이스를 구현한다.
  • 이 인터페이스의 ToString 메서드의 첫 인수는 서식 문자열이고 둘째 인수는 서식 공급자이다. 서식 문자열은 서식화를 위한 명령들을 제공하고, 서식 공급자는 그 명령들을 해석하는 방식을 결정한다.
    • 아래의 예에서 첫 인수 C는 통화(currency)를 뜻하며 둘째 인수로 지정한 NumberFormatInfo 객체는 화폐와 기타 여러 수치 표현을 처리하는 방법을 말해주는 서식 공급자이다. 이러한 메커니즘을 이용하면 응용 프로그램을 세계화할 수 있다.
NumberFormatInfo f = new NumberFormatInfo();
f.CurrencySymbol = "$$";
Console.WriteLine(3.ToString("C", f));  // $$ 3.0
  • 서식 문자열이나 서식 공급자로 null을 지정하면 기본 서식 문자열 또는 공급자가 적용된다. 기본 서식 공급자는 CultureInfo.CurrentCulture이다. 다른 값을 배정하지 않았을 때, 이 속성은 현재 컴퓨터의 제어판 설정을 반영한다.
    • 편의를 위해 대부분의 형식은 null 공급자를 생략할 수 있는 버전의 ToString도 제공한다.
  • DateTime(Offset)이나 수치 형식에 인수 없이 ToString을 호출하는 것은 빈 서식 문자열과 기본 서식 공급자를 지정하는 것과 같다.
  • .NET Framework에는 다음 3가지 서식 공급자가 정의되어 있다 (모두 IFormatProvider를 구현한다)
    • NumberFormatInfo
    • DateTimeFormatInfo
    • CultureInfo

서식 공급자와 CultureInfo

  • 서식 공급자의 문맥 안에서 CultureInfo는 다른 두 서식 공급자에 대한 간접 메커니즘으로 작용한다. CultureInfo는 현재 문화권의 지역 설정에 적용 가능한 NumberFormatInfo 객체 또는 DateTimeFormatInfo 객체를 돌려준다.
    • 아래는 특정 문화권을 명시적으로 지정한 예이다. “en-GB”는 영국을 뜻한다.
    • 이 코드는 en-GB 문화권에 적용 가능한 기본 NumberFormatInfo 객체를 이용해서 서식화를 수행한다.
CultureInfo uk = CultureInfo.GetCultureInfo("en-GB");
Console.WriteLine(3.ToString("C", uk)); // ₤3.00
  • 불변 문화권은 컴퓨터의 설정화 무관하게 항상 동일하다. 불변 문화권은 북미 문화권에 기초하지만 다음과 같은 차이가 있다.
    • 통화 기호가 $가 아니다. (태양 아이콘)
    • 날짜와 시간에 선행 0이 붙는다 (단, 월이 일보다 앞이라는 점은 같다)
    • 시간을 표시할 때 AM/PM 표시 대신 24시간 형식을 사용한다.

NumberFormatInfo 또는 DateTimeFormatInfo의 활용

  • 다음 예제는 NumberFormatInfo 인스턴스를 생성한 후 천 단위 구분자를 쉼표에서 빈칸으로 변경한다. 그 후 주어진 수치를 소숫점 이하 3자리까지 서식화 한다.
NumberFormatInfo f = new NumberFormatInfo();
f.NumberGroupSeparator = " ";
Console.WriteLine(12345.6789.ToString("N3", f));  // 12 345.679
  • NumberFormatInfo나 DateTimeFormatInfo의 기본 설정들은 불변 문화권 설정을 따른다. 그러나 불면 문화권 이외의 문화권을 기본으로 삼는 것이 더 유용할 때가 종종 있다. 그런 경우 다음처럼 Clone 메서드를 이용해서 기존 서식 공급자를 복제하면 된다.
    • 복제된 서식 공급자는 항상 쓰기 가능이다. 원래 공급자가 읽기 전용인 경우에도 그렇다.
NumberFormatInfo f = (NumberFormatInfo) CultureInfo.CurrentCulture.NumberFormat.Clone();

복합 서식화

  • 복합 서식 문자열(composite format string)은 서식 문자열에 변수 대입(variable substitution)을 결합한 것이다. 정적 string.Format 메서드는 복합 서식 문자열을 받는다.
string composite = "Credit={0:C}";
Console.WriteLine(string.Format(composite, 500));  //Credit=$500.00

서식 공급자를 이용한 파싱

  • 서식 공급자를 이용해서 파싱을 수행하는 표준적인 인터페이스는 없다. 대신 파싱 시 서식 공급자를 지원하는 형식마다 서식 공급자를 인수로 받도록 중복적재된 Parse(그리고 TryParse) 메서드를 제공한다.
    • 또한 NumberStyles나 DateTimeStyles 열거형을 받는 버전들을 제공하기도 한다.
  • NumberStyles와 DateTimeStyles는 파싱 작동 방식을 제어한다. 이를테면 괄호나 화폐 기호를 포함한 입력 문자열의 허용 여부 등을 이들을 통해서 지정한다. (기본적으로는 둘 다 허용되지 않는다)
int error = int.Parse("(2)");  //예외 발생
int minusTwo = int.Parse("(2)", NumberStyles.Integer | NumberStyles.AllowParentheses);  // ok
decimal fivePointTwo = decimal.Parse("₤5.20", NumberStyles.Currency, CultureInfo.GetCultureInfo("en-GB"));

IFormatProvider와 ICustomFormatter

  • 모든 서식 공급자는 IFormatProvider를 구현한다.
public interface IFormatProvider { object GetFormat (Type formatType); }
  • GetFormat 메서드의 목적은 간접층을 제공하는 것이다. CultureInfo가 구체적인 작업을 적절한 NumberFormatInfo 객체나 DateTimeInfo 객체에 위임할 수 있는 것은 바로 이 덕분이다.
  • IFormatProvider를 (그와 함께 ICustomFormatter를) 구현하면 기존 형식들과 연동되는 독자만의 고유한 서식 공급자를 작성할 수 있다. ICustomFormatter는 다음과 같은 메서드 하나만 선언한다.
string Format (string format, object arg, IFormatProvider formatProvider);

표준 서식 문자열과 파싱 플래그

  • 서식 문자열은 크게 표준 서식 문자열과 커스텀 서식 문자열로 나뉜다.
  • 표준 서식 문자열은 글자 하나 또는 글자 하나와 숫자 하나로 구성된다. 예컨대 “C”나 “F2″가 표준 서식 문자열이다.
  • 커스텀 서식 문자열은 형판(template)을 통해서 모든 문자를 세밀하게 제어할 수 있다. 예컨대 “0:#.000E+00” 같은 커스텀 서식 문자열이 가능하다.

수치 서식 문자열

  • 아래 표는 표준 수치 서식 문자열에 대한 표이다.
    • 수치에 대한 서식 문자열에 이런 표준 수치 서식 문자열이 포함되어 있지 않으면 “G” 다음에 숫자가 없는 표준 서식 문자열을 지정했을 때와 같은 결과가 나온다. 특히 아래와 같은 규칙이 적용된다.
      • 10-4보다 작거나 형식의 정밀도보다 큰 수는 지수 표기법(과학 표기법)으로 표현된다.
      • float나 double의 정밀도 한계에 놓은 두 유효숫자는 버려진다(round away). 이는 바탕 이진 값에서 십진 소수로의 변환에 내재하는 부정확성을 숨기기 위한 것이다.
    • 방금 설명한 자동 버림은 일반적으로 장점으로 작용하며, 사실 이런 버림이 일어난다는 점을 눈치채지 못하는 경우가 많다. 그러나 변환 후 복원이 필요하다면, 즉 수치를 문자열로 변환한 후 그것을 다시 수치로 변환해야 한다면 (그리고 그런 일을 여러 번 되푸이 해야 할 수도 있다면) 이전과는 다른 수치가 나와서 상등 판정이 달라질 수 있다. 표준 서식 문자열 “R”과 “G17″은 바로 이러한 암묵적 버림 문제를 피하기 위해 존재하는 것이다.
      • .NET Framework 4.6에서 “R”과 “G17″은 같은 일을 한다. 그전의 .NET Framework들에서 “R”은 본질적으로 “G17″의 버그 있는 버전이므로 사용하지 말아야 한다.
글자 의미 예제 입력 결과 참고
G 또는 g 일반 (General) 1.2345, “G”
0.00001, “G”
0.00001, “g”
1.2345, “G3”
12345, “G3”
1.2345
1E-05
1e-05
1.23
1.23E04
작은 수나 큰 수일 떄 지수 표기로 전환한다.
G3은 유효 자릿수(정밀도) 전체(즉, 소수점 앞과 뒤 모두)를 3으로 제한한다.
F 고정소수점 2345.678, “F2”
2345.6, “F2”
2345.68
2345.60
F2는 주어진 수를 소수점 둘째 자리로 반올림한다.
N 그룹 구분자가 있는 고정 소수점(N은 Numeric) 2345.678, “N2”
2345.6, “N2”
2,345.68
2,345.60
위와 같되, 그룹(천 단위) 구분자가 있다 (구체적인 기호는 서식 공급자가 결정)
D 선행 0 채움 123, “D5”
123, “D1”
00123
123
정수 형식에만 적용된다.
D5는 전체 자릿수가 5가 되도록 왼쪽에 0들을 채운다. D 때문에 수치 문자열이 잘리지는 않는다.
E 또는 e 지수(exponential) 표기를 강제한다 56789, “E”
56789, “e”
56789, “E2”
5.678900E+004
5.678900e+004
5.68E+004
여섯 자리 기본 정밀도
C 통화(화폐) 1.2, “C”
1.2, “C4”
$1.20
$1.2000
C만 있고 숫자는 없을 때의 소수점 이하 유효 자릿수는 서식 공급자의 기본 설정을 따른다.
P 퍼센트 .503, “P”
.503, “P0”
50.30%
50%
구체적인 퍼센트(백분율) 기호와 서식은 서식 공급자의 설정을 따른다.
소수부를 다르게 지정할 수도 있다.
X 또는 x 16진수 47, “X”
47, “x”
47, “X4”
2F
2f
002F
16진 숫자의 영문자를 대문자로 하고 싶으면 X, 소문자로 하고 싶으면 x를 사용한다.
정수 형식에만 적용된다.
R 복원 가능(순환소수) 1f / 3f, “R” 0.333333343 float  형식이나 double 형식의 경우 R이나 G17은 정확한 순환 소수 표현을 위해 최대한 많은 유효숫자를 동원한다.

 

  • 아래 표는 커스텀 수치 서식 문자열들이다.
글자 의미 예제 입력 결과 참고
# 숫자 자리표(placeholder) 12.345, “.##”
12.345, “.####”
12.35
12.345
소수점 이하 자리수를 제한한다.
0 0 자리표 12.345, “.00”
12.345, “.0000”
99, “000.00”
12.35
12.3450
099.00
위와 같되 소수점 이전과 이후에 0들을 채운다.
. 소수점 소수점을 표시한다.
구체적인 소수점 기호는 NumberFormatInfo를 따른다.
, 그룹 구분자 1234, “#,###,###”
1234, “0,000,000”
1,234
0,001,234
구체적인 기호는 NumberFormatInfo를 따른다.
, (위와 같음) 승수(multiplier) 1000000, “#,”
1000000, “#,,”
1000
1
서식 문자열의 끝이나 소수점 앞에 있는 쉼표는 승수로 작용한다. 주어진 수치를 1,000이나 1,000,000 등으로 나눈 결과가 서식화 된다.
% 퍼센트 표기 0.6, “00%” 우선 수치에 100을 곱하고, NumberFormatInfo에서 얻은 구체적인 퍼센트 기호를 %에 대입한다.
E0, e0, E+0, e+0, E-0, e-0 지수 표기 1234, “0E0”
1234, “0E+0”
1234, “0.00E00”
1234, “0.00e00”
1E3
1E+3
1.23E03
1.23e03
\ 리터럴 문자 인용 50, @”\#0″ #50 문자열의 @접두사와 함께 쓰인다. @접두사가 없는 문자열에서는 \\를 사용하면 된다.
‘xx’ ‘xx’ 리터럴 문자 인용 50, “0 ‘…'” 50 …
; 섹션 구분자 15, “#;(#);zero”
-5, “#;(#);zero”
0, “#;(#);zero”
15
(5)
zero
(양수이면)
(음수이면)
(영이면)
그 외의 문자 그대로 출력 35.2, “$0 . 00c” $35 . 20c

 

NumberStyles 열거형

  • 모든 수치 형식은 NuberStyles 인수를 받는 정적 Parse 메서드를 제공한다. 이 열거형의 멤버들은 아래와 같다.
    • AllowLeadingWhite
    • AllowTrailingWhite
    • AllowLeadingSign
    • AllowTrailingSign
    • AllowParentheses
    • AllowDecimalPoint
    • AllowThousands
    • AllowExponent
    • AllowCurrencySymbol
    • AllowHexSpecifier
  • NumberStyles에는 또한 이 멤버들의 적절한 조합으로 이루어진 합성 멤버들도 정의되어 있다.
    • None
    • Integer
    • Float
    • Number
    • HexNumber
    • Currency
    • Any
  • None을 제외한 모든 합성 멤버는 AllowLeadingWhite와 AllowTrailingWhite를 포함한다. 이들의 구체저인 조합은 아래 그림에 나와 있다.

  • Parse를 호출할 때 이런 플래그들을 전혀 지정하지 않으면 아래 그림의 기본 값들이 적용 된다.
    • 아래 기본값들을 적용하고 싶지 않다면 NumberStyles를 명시적으로 지정해야 한다.

날짜, 시간 서식 문자열

  • DateTime이나 DateTimeOffset을 위한 서식 문자열들은 문화권과 서식 공급자 설정을 따르는지의 여부에 따라 크게 두 부류로 나뉜다.
  • 문화권과 서식 공급자 설정을 존중하는 서식 문자열은 아래 표와 같다.
    • 이 표는 new DateTime(2000, 1, 2, 17, 18, 18)를 서식화 한 것이다.
서식 문자열 의미 출력 예
d 짧은 날짜 01/02/2000
D 긴 날짜 Sunday, 02 January 2000
t 짧은 시간 17:18
T 긴 시간 17:18:19
f 긴 날짜 + 짧은 시간 Sunday, 02 January 2000 17:18
F 긴 날짜 + 긴 시간 Sunday, 02 January 2000 17:18:19
g 짧은 날짜 + 짧은 시간 01/02/2000 17:18
G (기본) 짧은 날짜 + 긴 시간 01/02/2000 17:18:19
m, M 월과 일 02 January
y, Y 연도와 일 January 2000

 

  • 아래 표는 공급자 설정을 무시하는 서식 문자열들이다.
    • 이 표는 new DateTime(2000, 1, 2, 17, 18, 18)를 서식화 한 것이다.
서식 문자열 의미 출력 예 참고
o 복원 가능 2000-01-02T17:18:19.0000000 DateTimeKind가 Unspecified가 아닌 한, 시간대 정보를 붙인다
r, R RFC 1123 표준 Sun, 02 Jan 2000 17:18:19 GMT DateTime.ToUniversalTime을 이용해서 명시적으로 UTC로 변환해야 한다.
s 정렬 가능 ISO 8601 2000-01-02T17:18:19 텍스트 기반 정렬을 지원한다.
u 정렬 가능 UTC 2000-01-02 17:18:19Z 위와 같다. 반드시 UTC로 정렬해야 한다.
U UTC Sunday, 02 January 2000 17:18:19 UTC로 변환된 긴 날짜 + 짧은 시간

 

DateTimeStyles 열거형

  • DateTimeStyles 는 DateTimeOffset에 Parse를 호출할 떄 서식에 관한 추가적인 지시사항을 제공하는데 쓰인다. 아래는 이 열거형의 멤버들이다.
    • None
    • AllowLeadingWhite
    • AllowTrailingWhite
    • AllowInnerWhite
    • AssumeLocal
    • AssumeUniversal
    • AdjustToUniversal
    • NoCurrentDateDefault
    • RoundTripKind
    • 이들 중 일부를 조합한 AllowWhiteSpace라는 복합 멤버도 있다.

열거형 서식 문자열

서식 문자열 의미 출력 예 참고
G 또는 g 일반 (“General”) Red 기본 서식
F 또는 f Flags 특성이 적용된 것처럼 취급 Red enum에 Flags 특성이 없어도 조합된 멤버들에 작동
D 또는 d 10진수 12 바탕 정수 값을 조회함
X 또는 x 16진수 0000000C 바탕 정수 값을 조회함

 

기타 변환 메커니즘

Convert 클래스

  • .NET Framework의 정적 Convert 클래스는 모든 기반 형식에서 다른 모든 기반 형식으로의 변환을 수행하는 메서드들을 정의한다.
    • 안타깝게도 그 메서드들은 대부분 쓸모가 없다. 그 메서드들은 예외를 던지거나 아니면 그냥 암묵적 캐스팅에 맡기면 되는 일을 수행할 뿐이다.
  • (이하 생략)

XmlConvert

  • XML 파일에서 비롯된 자료나 XML 파일에 저장할 자료를 다룰 때는 System.Xml 의 XmlConvert 클래스가 유용하다.
    • .NET Framework 자체도 내부적으로 XmlConvert를 많이 사용한다. XmlConvert는 또한 범용, 문화권 독립적 직렬화에도 적합하다.
  • XmlConvert의 서식화 기능은 모두 적절히 중복적재된 ToString 메서드의 형태로 제공된다.
    • 한편 파싱 메서드들은 ToBoolean, ToDateTime 같은 이름으로 되어 있다.
string x = XmlConvert.ToString(true);  // s = "true"
bool isTrue = XmlConvert.ToBoolean(s)
  • DateTime과의 변환을 수행하는 메서드들은 XmlDateTimeSerializatonMode 형식의 인수를 받는다. 이 형식은 다음과 같은 값들을 가진 하나의 열거형이다.
    • Unspecified
    • Local
    • Utc
    • RoundtripKind
  • Local이나 Utc를 지정하면 서식화 메서드는 먼저 지역 시간 또는 UTC 시간으로 변환을 수행한 후 시간대 접미사를 붙인다.
2010-02-22T14:08:30.9375  // Unspecified
2010-02-22T14:07:30.9375+09:00  // Local
2010-02-22T05:08:30.9375Z  // Utc

형식 변환기

  • 형식 변환기(type converter)는 디자인 시점 환경에서 서식화와 파싱을 수행하는데 쓰인다. 형식 변환기는 또한 WPF나 Workflow Foundation에 쓰이는 형태의 XAML (Extensible Application Markup Language) 문서에 들어 있는 값들을 파싱하는데도 쓰인다.
  • 대체로 형식 변환기들은 힌트를 제공하지 않아도 문자열을 다양한 방식으로 파싱한다.
    • 예컨대 Visual Studio에서 ASP.NET 응용 프로그램을 개발하는 도중에 어떤 컨트롤의 속성 창에서 BackColor 속성 값으로 “Beige”를 입력하면 Color 형식을 담당하는 형식 변환기는 그것이 RGB 문자열이나 시스템 색상 이름이 아닌 색상 이름임을 감지한다.
    • 이러한 유연성 때문에 디자이너나 XAML 문서 이외의 맥락에서도 형식 변환기가 유용하게 쓰이곤 한다.
  • 모든 형식 변환기는 System.ComponentModel의 TypeConverter를 상속한다. 원하는 TypeConverter 객체는 TypeDesciptor.GetConverter를 호출해서 얻을 수 있다. 다음은 Color 형식을 위한 TypeConverter를 얻는 예이다.
TypeConverter cc = TypeDescriptor.GetConverter(typeof (Color));
  • TypeConverter에는 여러 메서드들이 있는데, 지금 논의에서 중요한 것은 ConvertToString 메서드와 ConvertFromString 메서드이다. 다음은 후자를 호출하는 예이다.
Color beige = (Color) cc.ConvertFromString ("Beige");
Color purple = (Color) cc.ConvertFromString ("#800080");
Color window = (Color) cc.ConvertFromString ("Window")
  • 형식 변환기 클래스의 이름은 항상 Converter로 끝나는 것이 관례이며, 보통의 경우 지원하는 형식과 같은 이름공간에 들어 있다.
    • 형식과 해당 형식 변환기 사이의 연관 관계는 TypeConverterAttribute 특성으로 지정한다.
    • 디자이너는 이 특성을 이용해서 적절한 형식 변환기를 자동으로 선택한다.

BitConveter

  • 대부분의 기반 형식은 BitConverterGetBytes 를 호출해서 바이트 배열로 변환할 수 있다.
foreach (byte b in BitConverter.GetBytes (3.5))
  Console.Write (b + " ");                                          // 0 0 0 0 0 0 12 6
  • BitConverter는 또한 그 반대 방향의 변환을 수행하는 메서드들도 제공한다. 이를테면 ToDouble이 있다.
  • decimal 형식과 DateTime(Offset) 형식들은 BitConverter가 지원하지 않는다.
    • 그러나 decimal.GetBits를 이용하면 decimal을 int 배열로 변환할 수 있다. 그 반대 방향의 변환을 원한다면 int 배열을 받는 decimal 생성자를 사용하면 된다.
    • DateTime의 경우에는 인스턴스 메서드 ToBinary를 호출해서 long 값을 얻고, 그것에 대해 BitConverter를 사용하면 된다. 그 반대의 변환을 위한 수단으로는 정적 메서드 DateTime.FromBinary가 있다.

국제화

  • 응용 프로그램의 국제화(internationalization)에는 두 가지 측면이 있는데, 하나는 전역화(globalization, 세계화)이고 또 하나는 지역화(localization, 현지화)이다.
  • 전역화의 과제는 다음의 세가지이다.
    1. 독자의 프로그램이 다른 문화권에서 깨지지(break) 않게 한다.
    2. 지역 문화권의 서식화 규칙들을 존중한다(이를테면 날짜 표시)
    3. 문화권 고유 자료나 문자열들을 위성 어셈블리에서 가져오도록 프로그램을 설계한다.
  • 지역화는 3번 과제를 수행하는 것을 말한다.
    • 즉 특정 문화권에 맞는 위성 어셈블리들을 실제로 작성하는 것이 지역화이다. 이런 지역화는 프로그램 작성을 완료한 후에 수행할 수 있다.
  • 2번 과제를 돕기 위해, .NET Framework는 문화권 고유 규칙들을 기본으로 적용한다.
    • DateTime이나 수치 형식에 ToString을 호출하면 기본적으로 지역 서시확 규칙이 적용된다는 점을 앞에서 보았다.
    • 안타깝게도 이 때문에 오히려 1번 과제에서 문제가 생기기도 한다. 날짜나 수치가 프로그램을 작성할 때 기준으로 삼았던 것과는 다른 서식화 규칙들로 서식화 될 수 있기 때문이다.
    • 해결책은 서식화나 파싱시 항상 특정 문화권을 지정하거나, XmlConvert의 메서드들 같이 문화권에 독립적인 메서드들을 사용하는 것이다.

전역화 점검 사항

  • 유니코드와 텍스트 부호화를 이해한다
  • char나 string에 대한 ToUpper나 ToLower 같은 메서드들이 문화권을 감지해서 작동한다는 점을 염두에 두어야 한다. 문화권 감지를 정말로 원하는 것이 아닌 한 ToUpperInvariant나 ToLowerInvariant를 사용할 것
  • DateTime과 DateTimeOffset에 대해서는 ToString(“o”)나 XmlConvert 같은 문화권 독립적 서식화와 파싱 메커니즘을 사용하라
  • 또는 수치나 날짜/시간을 서식화/파싱할 때 항상 문화권을 지정하라

검사

  • 다양한 문화권에 대해 프로그램을 시험해 보려면 Thread의 CurrentCulture 속성을 적절히 변경하면 된다. 다음은 ㅎ녀재 문화권을 터키로 변경하는 예이다.
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("tr-TR");
  • 전역화를 검사하는데 터키 문화권이 좋은 이유는 다음과 같다.
    • “i”.ToUpper() != “I” 이고 “I”.ToLower() != “i’ 이다.
    • 날짜 서식이 일.월.연도이다. (날짜 요소들을 마침표로 구분한다)
    • 소수점이 쉼표이다 (마침표가 아니라)
  • 또한 Windows 제어판에서 수치와 날짜 서식 설정을 바꾸어 보는 것도 좋은 방법이다.
    • 이 설정은 기본 문화권에 반영 된다.
  • CultureInfo.GetCultures 메서드는 사용 가능한 모든 문화권의 배열을 돌려준다.

수치 다루기

변환

과제 함수
십진수 파싱 Parse
TrtOarse
double d = double.Parse(“3.5”);
int i;
bool ok = int.TryParse(“3”, out i);
2, 8, 16진수 파싱 Convert.To정수형식 int i = Convert.ToInt32(“1E”, 16);
십육진수로 서식화 ToString(“X”) string hex = 45.ToString(“X”);
무손실 수치 변환 암묵적 캐스팅 int i = 23;
double d = i;
버림(절단) 수치 변환 명시적 캐스팅 double d = 23.5;
int i = (int) d;
반올림 수치 변환(실수에서 정수로) Convert.To정수형식 double d = 23.5;
int i = Convert.ToInt32 (d);

 

Math 클래스

  • Round 메서드를 호출할 떄는 반올림할 자릿수를 지정할 수 있으며, 중간값 처리 방식도 지정할 수 있다.
  • Floor와 Ceiling은 가장 가까운 정수를 돌려주는데, Floor는 항상 내림을 적용하고 Ceiling은 항상 올림을 적용한다. ‘항상’에는 음수도 포함된다.
  • Max와 Min은 인수 2개만 받는다. 만일 배열이나 수열의 최댓값이나 최솟값을 구해야 한다면 System.Linq.Enumerable에 있는 Max, Min 확장 메서드를 사용하면 된다.

 

범주 메서드
반올림 Round, Truncate, Floor, Ceiling
최대/최소 Max, Min
절댓갑과 부호 Abs, Sign
제곱근 Sqrt
거듭제곱 Pow, Exp
로그 Log, Log10
삼각함수 Sin, Cos, Tan
Sinh, Cosh, Tanh
Asin, Acos, Atan

 

BigInteger 구조체

  • BigInteger 구조체는 특화된 수치 형식으로, .NET Framework 4.0에 새로 도입되었다. System.Numerics.dll의 새로운 System.Nuerics 이름공간에 있는 이 형식을 이용하면 아무리 큰 정수도 정밀도 손실 없이 표현할 수 있다.
  • BigInteger는 C#의내장형식이아니기때문에, C# 코드에서 BigInteger 리터럴을 직접 표기하는 방법은 없다. 그러나 임의의 정수 형식에서 BigInteger로의 암묵적 변환은 허용된다.
BigInteger twentyFive = 25;  // 정수에서 BigInteger로의 암묵적 변환
  • 1구골(10100)처럼 더욱 큰 수를 표현하고 싶다면 BigInteger의 정적 메서드 중 하나인 Pow(주어진 지수만큼의 거듭제곰)을 사용하면 된다.
BigInteger googol = BigInteger.Pow(10, 100);
  • 아니면 문자열을 Parse로 파싱하는 방법도 있다.
BigInteger googol = BigInteger.Parse("1".PadRight(100, '0'));
  • BigInteger와 표준 수치 형식 사이의 변환 시 저옵가 손실될 간으성이 있을 때는 다음처럼 명시적 캐스팅 연산자를 사용해야 한다.
double g2 = (double) googol; 
BigInteger g3 = (BigInteger) g2;
  • BigInteger는 나머지 연산자를 포함한 모든 산술 연산자를 중복적재하며, 비교 연산자들과 상등 연산자들도 중복적재한다.
  • 바이트 배열로 BigInteger 를 구축하는 것도 가능하다. 다음 코드느느 암, 복호화에 사용할 수 있는 32비트 난수를 생성해서 BigInteger 변수에 배정한다.
    • 이런 수치를 바이트배열 대신 BigInteger에 담으면 값 형식 의미론을 얻게 된다는 장점이 생긴다. BigInteger를 다시 바이트 배열로 변환하려면 BigInteger의 ToByteArray 메서드를 호출하면 된다.
RandomNumberGenerator rand = RandomNuberGenerator.Create();  // 이 코드는 System.Security.Cryptography 이름공간을 사용함
byte[] bytes = new byte[32];
rand.GetBytes(bytes);
var bigRandomNumber = new BigInteger(bytes)  // BigInteger로 변환

Complex 구조체

  • Complex 구조체도 특화된 수치 형식으로, 역시 .NET Framework 4.0에 새로 도입되었다. 이 형식은 복소수를 표현하는데, 실수부와 허수부의 형식은 double이다.
    • Complex는 System.Numerics.dll 어셈블리에 있다. (BigInteger와 함꼐)
  • Complex 인스턴스를 생성하는 기본적인 방법은 시룻부와 허수부를 지정해서 생성자를 호출하는 것이다.
    • Complex는 표준 수치 형식들로부터의 암묵적 변환도 지원한다.
var c1 = new Complex (2, 3.5);
var c2 = new Complex (3, 0);
  • Complex 구조체는 실수부와 허수부에 해당하는 속성들과 함께 복소평면 상의 위상(phase)과 크기(magnitude)에 해당하는 속성들도 노출한다.
Console.WriteLine(c1.Real);  // 2
Console.WriteLine(c1.Imaginary);  // 3.5
Console.WriteLine(c1.Phase);  // 1.05165021254837
Console.WriteLine(c1.Magnitude);  // 4.03112887414927
  • 다음 예처럼 위상과 크기를 지정해서 Complex 인스턴스를 생성할 수도 있다.
Complex c3 = Complex.FromPolarCoordinates(1.3, 5);
  • Complex는 표준적인 산술 연산자들을 복소수에 맞게 중복적재한다.
Console.WriteLine(c1 + c2);  // (5, 3.5)
Console.WriteLine(c1 * c2);  // (6, 10.5)
  • 또한 Complex 구조체는 좀 더 고급의 복소수 연산을 위한 정적 메서드들도 제공하는데, 이를테면 다음과 같은 것들이 있다.
    • 삼각함수(Sin, Asin, Sinh, Tan  등)
    • 로그와 지수
    • 켤레 복소수(Conjugate)

Random 클래스

  • Random 클래스는 byte나 integer, double 형식의 난수들로 이루어진 의사난수열(pseudorandom sequence)을 생성한다.
  • Random을 사용하려면 먼저 인스턴스를 생성해야 하는데, 이때 난수열 생성에 쓰이는 종잣값(seed)을 지정할 수 있다.
    • 같은 종자값을 지정하면 항상 같은 난수열이 나온다.(같은 CLR 버전에서 실행한다고 할 때)
    • 재현성(reproducibility)이 필요한 경우에는 그런 난수열이 유용할 수 있다.
Random r1 = new Random(1);
Random r2 = new Random(1);
Console.WriteLine(r1.Next(100) + ", " + r1.Next(100));  // 24, 11
Console.WriteLine(r2.Next(100) + ", " + r2.Next(100));  // 24, 1
  • 재현성이 필요 없을 때는 종잣값 없이 Random 생성자를 호출하면 된다. 그러면 Random은 현재 시스템 시간을 이용해서 종잣값을 만든다.
    • 시스템 클록의 해상도가 그리 세밀하지 않기 때문에, Random 인스턴스 두 개를 짧은 간격 (보통 10ms 이내) 으로 생성하면 같은 난수열이 만들어질 위험이 있다.
    • 특히, 같은 Random 객체를 재사용하는 것이 아니라 난수가 필요할 때마다 새로운 Random 객체를 생성하다보면 그런 함정에 빠지기 쉽다.
    • 바람직한 패턴은 정적 Random 인스턴스를 하나만 선언해서 재사용하는 것이다.
    • 그러나 Random이 스레드에 안전하지 않으므로 다중 스레드 환경에서는 이런 접근 방식이 문제가 될 수 있다.
  • Next(n)을 호출하면 0에서 n-1 사이의 정수 난수가 생성된다.
    • NextDouble은 0이상 1미만의 double 형식 난수를 생성한다.
    • NextBytes는 바이트 배열에 무작위한 값들을 채워 넣는다.
  • Random이 암, 복호화처럼 보안 요구 수준이 높은 응용에 적합할 정도로 무작위한 것은 아니다. 그래서 .NET Framework는 암호학적으로 강한 난수 발생기를 제공한다. (System.Security.Cryptography에 있다)
    • 아래는 그 난수 발생기를 사용하는 예이다.
var rand = System.Security.Cryptography.RandomNumberGenerator.Create();
byte[] bytes = new byte[32];
rand.GetBytes (bytes)
  • 이 발생기의 단점은 유연성이 떨어진다는 것이다. 바이트 배열을 채우는 것이 이 발생기로 난수를 얻는 유일한 방법이다. 정수 난수를 얻으려면 BitConverter를 사용해야 한다.
byte[] bytes = new byte[4];
ran.GetBytes(bytes);
int i = BitConverter.ToInt32(bytes, 0)

열거형과 System.Enum

  • .NET Framework는 열거형에 대한 C#의 지원을 System.Enum 형식을 통해 더욱 확장한다. 이 형식의 역할은 다음의 2가지이다.
    • 모든 enum 형식을 통합하는 기준이 된다.
    • 편의용 정적 메서드들을 정의한다.
  • 여기서 형식 통합(type unification)은 임의의 열거형 멤버를 System.Enum 인스턴스로 암묵적으로 변환할 수 있음을 뜻한다.
  • System.Enum에 대한 편의용 정적 메서드들은 기본적으로 변환 수행과 멤버 목록 얻기에 관련된 것들이다.
enum Nut { Walnut, Hazelnut, Macadamia }
enum Size { Small, Medium, Large }

static void Main()
{
  Display(Nut.Macadamia);  // Nut.Macadamia
  Display(Size.Large);  // Size.Large
}

static void Display(Enum value)
{
  Console.WriteLine(value.GetType().Name + "." + value.ToString());
}

열거형의 변환

  • 코드에서 열거형 값을 표현하는 방법은 다음 3가지이다.
    • enum의 한 멤버로 표현한다.
    • 바탕 정수 값으로 표현한다.
    • 문자열로 표현한다.
  • (이하 열거형 변환 내용 생략)

열거형 값들의 열거

  • Enum.GetValues 메서드는 주어진 enum 형식의 모든 멤버를 담은 배열을 돌려 준다.
    • 결과 배열에는 LeftRight = Left | Right 같은 조합된 멤버들도 포함된다.
    • Enum.GetNames도 같은 일을 하되, 문자열 배열을 돌려 준다.
foreach (Enum value in Enum.GetValues (typeof (BorderSides)))
  Console.WriteLine(value);

열거형의 작동 방식

  • enum의 의미론은 대부분 컴파일러가 강제한다. 실행시점에서 CLR은 enum 인스턴스(박싱되지 않은)와 바탕 정수 형식을 전혀 구별하지 못한다.
    • 더 나아가서, CLR에서 enum은 그냥 System.Enum의 한 하위 형식(각 멤버마다 정수 형식 정적 필드가 하나 있는)으로 정의될 뿐이다.
    • 이 덕분에 enum의 통상적인 용도는 고도로 효율적이다. 실행시점 비용이 정수 상수의 것에 비견할 정도이다.
  • 이러한 전략의 단점은 enum이 정적 형식 안전성을 제공하긴 하지만 강한 형식 안정성을 제공하지는 못한다는 것이다.
    • 컴파일러가 열거형 연산의 유효성을 검증하지 못하는 상황에서는 CLR 역시 예외를 던져서 문제를 알려주지 못한다.

튜플

  • .NET Framework 4.0에는 서로 다른 형식의 요소들을 튜플이라는 단위로 묶는 제네릭 클래스들이 도입되었다.
    • 다음과 같이 총 8개의 제네릭 Tuple 클래스들이 존재한다.
    • 각 클래스에는 Item1, Item2 등의 읽기 전용 속성들이 있다.
public class Tuple <T1>
public class Tuple <T1, T2>
public class Tuple <T1, T2, T3>
public class Tuple <T1, T2, T3, T4>
public class Tuple <T1, T2, T3, T4, T5>
public class Tuple <T1, T2, T3, T4, T5, T6>
public class Tuple <T1, T2, T3, T4, T5, T6, T7>
public class Tuple <T1, T2, T3, T4, T5, T6, T7, TRest>
  • 튜플을 생성하는 방법은 여러 가지인데, 생성자를 이용할 수도 있고 정적 메서드 Tuple.Create를 이용할 수도 있다.
    • 후자에 대해서는 제네릭 형식 추론을 활용할 수도 있다.
var t = new Tuple<int, string> (123, "Hello");
Tuple<int, string> t = Tuple.Create(123, "Hello");
  • 튜플은 한 메서드에서 둘 이상의 값을 돌려줄 때 유용하며, 값 쌍#(pair)들의 컬렉션을 만들 때에도 유용하다.
  • 튜플 대신 object 배열을 사용할 수도 있다.
    • 그러나 그렇게 하면 정적 형식 안전성이 사라지고, 값 형식에 대해서는 박싱/언박싱 비용이 발생하며, 컴파일러가 검증할 수 없는 지저분한 캐스팅들이 끼어들게 된다.

튜플의 비교

  • 튜플은 클래스이다(따라서 참조 형식이다) 그래서 서로 다른 인스턴스를 상등 연산자로 비교하면 false가 반환된다. 그러나 Equals는 개별 요소를 비교하도록 재정의되어 있다.
    • 튜플의 Equals에는 둘째 인수로 커스텀 상등 비교자를 받는 버전도 있다. (이는 튜플이 IStructuralEquatable 을 구현하기 때문이다)
var t1 = new Tuple<int, string> (123, "Hello");
var t2 = Tuple.Create(123, "Hello");
Console.WriteLine(t1 == t2);  // false
Console.WriteLine(t1.Equals(t2));  // true

Guid 구조체

  • Guid 구조체는 전 지구적으로 고유한 식별자 (Globally Unique Identifier, GUID)를 나타낸다.
    • 구체적으로 말하면 이 구조체로부터 생성된 인스턴스는 전 세계에서 고유할 것이 거의 확실한 16바이트 값이다.
    • 흔히 Guid는 응용 프로그램이나 데이터베이스에서 다양한 종류의 키로 쓰인다.
    • 고유한 Guid 값은 총 2128개이다.
  • 정적 Guid.NewGuid 메서드는 고유한 Guid 인스턴스를 생성한다.
Guid g = Guid.NewGuid();
Console.WriteLine(g.ToString());  // 0d57629c-7d6e-4847-97cd-9e2fc25083fe
  • 이미 알고 있는 값을 가진 인스턴스를 생성하려면 생성자를 사용하면 된다. 다음은 가장 유용한 두 생성자이다.
public Guid (byte[] b);  // 16바이트 배열을 받는다.
public Guid (string g);  // 서식화된 문자열을 받는다
  • 둘째 생성자는 십육진 숫자 32개로 이루어진 GUID 문자열을 인식한다. 또한 8, 12, 16, 20번째 숫자 다음에 빼기 기호가 포함되어 있거나 전체를 중괄호나 대괄호로 감싼 문자열도 잘 인식한다.
Guid g1 = new Guid("{0d57629c-7d6e-4847-97cd-9e2fc25083fe}");
Guid g2 = new Guid("0d57629c-7d6e-4847-97cd-9e2fc25083fe");
Console.WriteLine(g1 == g2);  // true
  • Guid는 구조체이므로 값 형식 의미론을 따른다. 위의 예에서 상등 연산자가 두 인슨턴스를 같다고 판정한 것은 그 때문이다.
  • ToByteArray 메서드는 Guid를 바이트 배열로 변환한다.
  • 정적 Guid.Empty 속성은 빈 Guid(모든 바이트가 0인)를 돌려준다. 빈 Guid를 흔히 null 값 대신 사용한다.

상등 비교

값 상등 대 참조 상등

  • 기본적으로 값 형식은 값 상등을 사용하고, 참조 형식은 참조 상등을 사용한다.
값 상등 (value equality) 어떤 의미로든 두 값이 동치(equivalent)이다
참조 상등 (referential equality) 두 참조가 정확히 같은 객체를 가리킨다

 

  • 사실 값 형식은 값 상등만 사용할 수 있다 (박싱 되지 않았다고 할 때)
    • 값 상등의 전형적인 예는 두 수치의 상등을 판정하는 것이다.
  • 참조 형식은 기본적으로 참조 상등을 따른다.
    • 아래 예에서 f1과 f2는 비록 같은 내용의 객체를 가리키지만 서로 같다고 간주되지 않으며, f3과 f1은 같은 객체를 가리키므로 상등이다.
Foo f1 = new Foo { X= 5 };
Foo f2 = new Foo { X = 5 };
Console.WriteLine(f1 == f2);  // false

Foo f3 = f1;
Console.WriteLine(f1 == f3);  // tru

표준 상등 프로토콜

  • 상등 비교를 지원하기 위해 형식이 구현할 수 있는 표준 상등 프로토콜은 다음 3가지이다.
    • == 연산자와 != 연산자
    • 가상 Equals 메서드
    • IEquatable<T> 인터페이스

== 연산자와 != 연산자

  • 표준 ==, != 연산자를 이용한 상등/부등 비교에 관한 미묘한 문제들은 이들이 연산자이기 때문에 그 의미가 정적으로 결정된다는 사실(이들은 static 함수로 구현된다)에서 비롯된다.
  • 즉 ==나 !=를 이용해서 비교를 수행하는 코드에 대해 C# 컴파일러는 그 비교를 수행할 형식을 컴파일 시점에서 결정하며, 여기에는 동적 다형성이 전혀 끼어들지 않는다.

가상 Objecte.Equals 메서드

  • 앞의 예제에서 x와 y가 같다는 판정을 얻는 한 가지 방법은 가상 Equals 메서드를 사용하는 것이다. Equals는 System.Object에 정의되어 있으므로 모든 형식이 이 메서드를 제공한다.
  • Equals의 의미는 실행시점에서 해당 객체의 실제 형식에 근거해서 결정된다. 지금 에에서 런타임은 Int32의 Equals를 호출하는데, 그 메서드는 피연산자들에게 값 상등을 적용하며 따라서 true를 돌려준다.
    • 구조체에 대한 Equals는 구조체 상등을 적용한다. 즉 구조체 인스턴스들의 각 필드에 대해 Equals를 호출한다.
  • C# 설계자들이 ==를 Equals와 동일한 방식으로 작동하지 않게 만든 이유
    • 만일 첫 피연산자가 널이면 NullReferenceException 예외가 발생해서 Equals의 호출이 실패한다. 그러나 정적 연산자는 그렇지 않다.
    • == 연산자는 정적으로 결정되므로 실행 속도가 대단히 빠르다.
    • ==와 Equals에 각자 다른 의미의 상등 비교를 수행하게 하는 것이 유용한 경우가 있다.
  • 따라서 가상 Equals는 형식에 구애받지 않고 두 객체를 비교하는데 적합하다.
    • 그런데 만일 비교하는 첫 인수가 null 이면 NullReferenceException 예외가 발생한다. 이는 아래와 같은 방법으로 해결할 수 있다.
public static bool AreEqual (object obj1, object obj2)
{
  if (obj1 == null) return obj2 == null;
  return obj1.Equals(obj2);
}

// 위 식을 다음과 같이 좀 더 간결하게 표기할 수도 있다.
public static bool AreEqual (object obj1, object obj2)
 => obj1 == null ? obj2 == null : obj1.Equals(obj2)

정적 object.Equals 메서드

  • object 클래스는 앞의 AreEqual 예제와 동일한 일을 수행하는 편의용 정적 메서드를 하나 제공한다.
    • 그 메서드의 이름은 가상 메서드와 같지만, 인수를 2개 받기 때문에 이름 충돌은 일어나지 않는다.
    • 이 메서드를 이용하면 형식들을 컴파일 시점에서 미리 알 수 없는 상황에서도 널에 안전한 방식으로 상등을 비교할 수 있다.

정적 object.ReferenceEquals 메서드

  • 경우에 따라서는 참조 상등 비교를 강제해야 할 수도 있다. 이를 위해 .NET Framework는 정적 object.ReferenceEquals 메서드를 제공한다.

IEqutable<T> 인터페이스

  • object.Equals를 호출하면 값 형식의 피연산자들에 대해 반드시 박싱이 적용된다. 성능이 민감한 상황에서 이는 바람직 하지 않다. 이 문제를 해결하기 위해 IEquatable<T>라는 인터페이스가 C# 2.0에 도입되었다.
  • 이 메커니즘의 핵심은 IEquatable<T>를 구현한 형식에 대해 Equals를 호출하면 object의 가상 Equals를 호출할 떄와 같은 결과가 나올 뿐만 아니라 그 속도가 더 빠르다.
  • .NET Framework의 기본 형식들은 대부분 IEquatable<T>를 구현한다.
    • IEquatable<T>를 제네릭 형식의 한 제약조건 으로 둘 수도 있다.

Equals와 == 연산자가 같지 않은 경우

  • == 와 Equals가 서로 다른 의미의 상등을 적용하는 것이 유용한 때가 있다.
    • 아래 예에서 double 형식의 == 연산자는 NaN을 그 어떤 것과도 같지 않다고 판정한다. 심지어 두 NaN도 상등이 아니다.
    • 이는 수학적 관점에서 가장 자연스러운 방식이며, 바탕 CPU의 행동 방식을 잘 반영한다.
double x = double.NaN;
Console.WriteLine (x == x); // false
Console.WriteLine (x.Equals(x));  // true

상등과 커스텀 형식

  • (상등 의미를 재정의 하는 부분 생략)

구조체 상등 비교 속도 높이기

  • 구조체에 기본적으로 적용되는 구조체 상등 비교 알고리즘은 비교적 느리다. Equals를 적절히 재정의하면 상등 비교 속도를 5배 높일 수 있다.
    • == 연산자를 중복적재하고, IEquatable<T>를 구현하면 박싱 없는 상등 비교가 가능해지며 이를 통해 또다시 5배 성능을 높일 수 있다.
  • 다소 독특한 경우의 상등 커스텀화도 존재한다. 바로 구조체의 해싱 알고리즘을 개선함으로써 해시테이블의 성능을 높이는 것이다.

상등 의미론의 재정의 방법

  • 상등의 의미를 변경하는 과정을 요약하면 다음과 같다.
    1. GetHashCode와 Equals를 재정의 한다.
    2. (생략 가능) !=와 ==를 중복적재한다.
    3. (생략 가능) IEquatable<T>를 구현한다.

GetHashCode의 재정의

  • System.Object가 멤버 수가 많지 않은 형식이라는 점을 생각하면, 용도가 국한된 특별한 메서드가 존재한다는 것이 다소 이상하게 느껴진다. Object의 가상 메서드인 GetHashCode가 바로 그러한 메서드에 해당한다. 이 메서드는 기본적으로 다음 두 형식에만 유용하다.
    • System.Collections.Hashtable
    • System.Collections.Generic.Dictionary<TKey, TValue>
  • 이들은 해시테이블이라고 부르는 자료구조에 해당한다.
    • 해시테이블의 각 요소에는 조회와 저장에 쓰이는 키가 있다. 해시테이블은 각 요소를 그 키에 기초해서 효율적으로 할당하며, 이를 위해 아주 구체적인 전략을 사용한다.
    • 좀 더 구체적으로 각 키에는 해시 코드라고 부르는 Int32 값이 있다. 각 키의 해시 코드가 고유할 필요는 없다. (즉, 해시 코드가 같은 키가 여러 개 있을 수도 있다.) 그러나 해시테이블의 성능이 좋으려면 해시 코드들이 충분히 다양해야 한다.
    • .Net Framework가 해시테이블을 아주 중요시 하기 때문에, GetHashCode 메서드를 아예 System.Object에 집어 넣었다. 이 덥군에 모든 형식이 이 해시 코드를 제공할 수 있다.
  • 해시테이블의 성능을 극대화하려면 서로 다른 두 값에 대해 같은 해시코드를 돌려줄 확률이 최소가 되도록 GetHashCode를 작성해야 한다.
    • 이로부터 구조체에 대해 Equals와 GetHashCode를 재정의하는 세 번째 이유가 나온다. 구조체에 대한 기본 해싱 알고리즘보다 효율이 높은 커스텀 알고리즘을 제공한다는 것이 바로 세 번째 이유이다.
    • 구조체에 대한 기본 GetHashCode 구현은 런타임이 결정하는데, 구조체의 모든 필드를 해싱하는 방식의 구현이 적용될 수도 있다.
  • 해시코드를 키로 해서 객체를 사전에 추가한 후 객체의 해시코드가 변경되면 사전에서 더는 그 객체에 접근할 수 없게 된다. 객체 불변이 필드에 기초해서 해시코드를 생성하면 이런 문제를 피할 수 있다.
  • (구조체의 상등 비교가 느리기 때문에 Hash 값을 재정의해서 상등 비교를 하는 것 같다.)

IEquatable<T>의 구현

  • 너비와 높이를 맞바꿀 수 있는 직사각형 영역(area)을 나타내는 구조체를 작성한다고 하자. 너비와 높이를 맞바꿀 수 있다는 것은 5×10과 10×5가 같다고 간주한다는 뜻이다.
    • 아래는 그러한 구조체인 Area의 전체 정의이다.
public struct Area : IEquatable<Area>
{
  public readonly int Measure1;
  public readonly int Measure2;

  public Area (int m1, int m2)
  {
    Measure1 = Math.Min(m1, m2);
    Measure2 = Math.Max(m1, m2);
  }

  public override bool Equals (object other)
  {
    if (!(other is Area)) return false;
    return Equals ((Area) other);  // 아래의 메서드를 호출
  }

  public bool Equals (Area other)
    => Measure1 == other.Measure1 && Measure2 == other.Measure2;
 
  public override int GetHashCode()
    => Measure2 * 31 + Measure1;  // 31은 임의로 선택한 prime number

  public static bool operator == (Area a1, Area a2) => a1.Equals(a2);
  public static bool operator != (Area a1, Area a2) => !a1.Equals(a2);
}

// 위 구조체를 이용한 비교 결과
Area a1 = new Area (5, 10);
Area a2 = new Area (10, 5);
Console.WriteLine (a1.Equals(a2));  // true
Console.WriteLine (a1 == a2);  // true
  • GetHashCode의 구현에서는 두 치수 중 더 큰 것에 어떤 소수를 곱한 후 두 치수를 합한다. 이는 고유한 해시코드가 생성될 가능ㅎ성을 높이기 위한 것이다.
    • 구조체에 필드가 이보다 더 많다면 조슈아 블로크가 제안한 다음과 같은 패턴을 따르면 비교적 괜찮은 성능으로 좋은 결과를 얻을 수 있다.
int hash = 17;  // 17 = 임의의 소수
hash = hash * 31 + field1.GetHashCode();  // 31 = 임의의 또 다른 소수
hash = hash * 31 + field2.GetHashCode();
hash = hash * 31 + field3.GetHashCode();
return hash;

순서 비교

  • C#과 .NET Framework는 상등을 위한 표준 프로토콜들 뿐만 아니라 두 객체의 상대적 순서를 결정하는데 쓰이는 표준 프로토콜들도 정의한다. 기본적인 프로토콜들은 다음과 같다.
    • IComparable 인터페이스들
    • > 연산자와 < 연산자
  • IComparable 인터페이스들은 범용 정렬 알고리즘에 쓰인다. 정적 Array.Sort 메서드가 정렬을 제대로 수행하는 것은 System.String이 IComparable 인터페이스들을 구현하고 있기 때문이다.
  • <, > 연산자는 좀 더 특화된 프로토콜인데, 주로는 수치 형식들을 윟나 것이다. 이들은 그 의미가 정적으로 결정되기 때문에 고도로 효율적인 바이트코드로 컴파일 될 수 있다. 따라서 계산량이 많은 알고리즘에 적합하다.

IComparable

  • IComparable 인터페이스들은 다음과 같이 정의되어 있다.
public interface IComparable { int CompareTo (object other); }
public interface IComparable<in T> { int CompareTo (T other); }
  • 이 두 인터페이스는 같은 기능성을 제공한다. 값 형식의 경우에는 형식에 안전한 제네릭 인터페이스가 비제네릭 인터페이스보다 빠르다. 두 인터페이스 모두 CompareTo 메서드는 다음과 같이 작동한다.
    • 만일 a가 b보다 뒤에 와야 하면 a.CompareTo(b)는 양수를 돌려준다.
    • 만일 a가 b와 같은 위치여야 하면 a.CompareTo(b)는 0를 돌려준다.
    • 만일 a가 b보다 앞에 와야 하면 a.CompareTo(b)는 음수를 돌려준다.
Console.WriteLine("Beck".CompareTo("Anne"));  // 1
Console.WriteLine("Beck".CompareTo("Beck"));  // 0
Console.WriteLine("Beck".CompareTo("Chris"));  // -1

IComparable 대 Equals

  • Equals를 재정의할 뿐만 아니라 IComparable 인터페이스들도 구현한 형식이 있다고 하자. 만일 그 형식의 두 인스턴스에 대해 Equals가 true를 돌려준다면, CompareTo 역시 0을 돌려주어야 할 것으로 생각할 것이다. 맞는 말이다. 그러나 false를 돌려줄 때는 상황이 좀 다르다.
    • Equals가 false를 돌려줄 때 CompareTo는 어떤 결과라도 돌려줄 수 있다.
  • 다른 말로 하면, 상등은 비교보다 더 까다로울 (fussier) 수 있지만, 비교가 상등보다 더 까다로울 수는 없다. (이를 위반하면 정렬 알고리즘이 오작동하게 된다)
    • 즉 CompareTo가 ‘모든 객체가 상등이다’라고 말하는 상황이라도 Equals는 ‘그러나 어떤 객체는 다른 객체보다 더 상등이라’라고 말할 수 있다.
  • 좋은 예가 System.String이다.
    • String의 Equals 메서드와 == 연산자는 서수 비교를 이용해서 각 문자의 유니코드 부호점 값을 비교한다. 반면 CompareTo 메서드는 덜 까다로운 문화적 의존적 비교를 수행한다.

< 연산자와 > 연산자

  • 일반적으로 <와 >는 IComparable과 모순되지 않는 결과를 내도록 구현된다. 이것이 .NET Framework 전반에 쓰이는 표준적이 ㄴ관행이다.
  • 또한 <와 >를 중복적재하는 경우에는 IComparable 인터페이스들도 구현한다는 것 역시 표준적인 관행이다.
    • 그러나 그 역은 참이 아니다.
    • 실제로 IComparable을 구현하는 대부분의 .NET Framework 형식은 <와 >를 구현하지 않는다. 이는 Equals를 재정의한다면 ==도 중복적재하는 것이 관례인 상등 비교와의 차이점이다.
  • 대체로 >와 <는 다음과 같은 경우에서만 중복적재한다.
    • 형식에 고유한 ‘초과’와 ‘미만’ 개념이 아주 강하다.
    • 순서 비교를 수행하는 방법 또는 문맥이 단 하나이다.
    • 결과가 문화권과 관계없이 동일하다.
  • System.String은 마지막 조건을 만족하지 않기 때문에 >와 < 연산자를 지원하지 않는다.
  • (IComparable 인터페이스 구현 예제 생략)

편의용 클래스들

Console 클래스

  • 정적 Console 클래스는 콘솔 기반 응용 프로그램의 표준 입, 출력을 처리한다.
  • 명령줄(콘솔) 응용 프로그램에서 사용자가 키보드로 입력한 내용을 이 클래스의 Read나 ReadKey, ReadLine 메서드를 이용해서 읽어들 일 수 있으며, Write나 WriteLine 메서드를 이용해서 텍스트를 콘솔 창에 출력할 수 있다.
  • Write 메서드와 WriteLine 메서드에는 복합 서식 문자열을 받는 중복적재 버전도 있다. 그러나 서식 공급자를 받는 중복적재는 없다. 즉 항상 CultureInfo.CurrentCulture가 적용된다.
    • 물론 우회책은 string.Format을 명시적으로 호출하는 것이다.
  • Console.Out 속성은 텍스트 기록자(text writer)를 나타내는 TextWriter 객체를 돌려준다. TextWriter를 인수로 받는 메서드에 Console.Out을 넘겨주는 것은 진단 (diagnostic) 메시지들을 콘솔 창에 출력할 때 유용한 방법이다.
  • 또한 Console의 입출력 스트림을 SetIn과 SetOut 메서드를 이용해서 다른 곳으로 재지정(redirection) 할 수도 있다.
// 우선 기존의 출력 기록자(writer)를 저장해 둔다.
System.IO.TextWriter oldOut = Console.Out;

// 콘솔의 출력을 파일로 재지정한다.
using (System.IO.TextWriter w = System.IO.File.CreateText("e:\\output.txt"))
{
  Console.SetOut(w);
  Console.WriteLine("Hello World");
}

// 표준 콘솔 출력을 복원한다.
Console.SetOut(oldOut);

// ouput.txt 파일을 메모장으로 연다.
System.Diagnostics.Process.Start("e:\\output.txt");

Environment 클래스

  • 정적 System.Environment 클래스는 다음과 같은 유용한 속성을 제공한다.
    • 파일과 폴더
      • CurrentDirectory, SystemDirectory, CommandLine
    • 컴퓨터와 운영체제
      • MachineName, ProcessorCount, OSVersion, NewLine
    • 사용자 로그인
      • UserName, UserInteractive, UserDomainName
    • 진단
      • TickCount, StackTrace, WorkingSet, Version
  • 위의 것들 외에 시스템 특수 폴더들의 경로도 얻을 수 있는데, 이 클래스의 GetFolderPath 메서드를 사용하면 된다.
  • 운영체제의 환경 변수들 (명령 프롬프트에서 set을 실행하면 나오는 것들)은 세 메서드 GetEnvironmentVariable, GetEnvironmentVariables, SetEnvironmentVariable로 얻거나 설정할 수 있다.
  • ExitCode 속성을 이용하면 프로그램 명령줄이나 일괄 실행 파일(batch file)에서 실행했을 때 운영체제애 반환될 프로그램 종료 코드를 설정할 수 있다.
  • FailFast 메서드는 프로그램을 즉시 종료한다. 이 경우 객체 해제 등의 마무리 작업은 수행되지 않는다.

Process 클래스

  • System.Diagnostics 이름공간의 Process 클래스는 새 프로세스를 띄우는 기능을 제공한다.
  • 정적 Process.Start 메서드에는 여러 중복적재 버전이 있는데, 가장 간단한 버전은 파일 이름 하나와 선택적 인수 하나를 받는다.
Process.Start("notepad.exe");
Process.Start("notepad.exe", "e:\\file.txt");
  • 다음처럼 실행 파일이 아닌 파일 이름 하나만 지정하면 파일 확장자에 해당하는 등록된 프로그램이 실행된다.
Process.Start("e:\\file.txt");
  • 가장 유연한 버전은 ProcessStartInfo 인스턴스를 받는 버전이다.
    • 이 버전을 이용하면 실행된 프로세스의 입력과 출력, 오류 출력을 갈무리 및 재지정할 수 있다. (이를 위해서는 UseShellExecute를 false로 설정해야 한다)
    • 다음은 ipconfig의 출력을 갈무리하는 예이다.
ProcessStartInfo psi = new ProcessStartInfo
{
  FileName = "cmd.exe",
  Arguments = "/c ipconfig /all",
  RedirectStandardOutput = true,
  UseShellExecute = false
};
Process p = Process.Start(psi);
string result = p.StandardOutput.ReadToEnd();
Console.WriteLine(result);
  • 이러한 기능을 이용해서 csc(C# 컴파일러)를 실행할 수도 있다. Filename 속성을 다음과 같이 설정하면 된다.
psi.FileName = System.IO.Path.Combine(System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDIrectory(), "csc.exe");
  • 출력을 재지정하지 않으면 Process.Start는 주어진 프로그램을 호출자와 병렬로 실행한다. 새 프로세스가 종료되길 기다리고 싶다면 Process 객체에 대해 WaitForExit를 호출하면 된다. 이때 선택적 인수로 만료 대기시간을 지정할 수 있다.
  • 보안 문제 때문에 Windows 스토어 앱에서는 Process 클래스를 사용할 수 없다.
    • 애초에 Windows 스토어 앱에서는 보통의 프로그램에서 다른 프로세스를 직접 실행하는 것이 불가능하다.
    • 대신 Windows.System.Launcher를 이용해서 접근 권한이 있는 URI나 파일을 ‘실행해야’ 한다.

AppContext 클래스

  • System.AppContext는 .NET Framework 4.6에 새로 도입된 클래스이다. 이 클래스는 문자열 키들과 부울 값들로 이루어진 전역 사전(dictionary) 하나를 제공한다.
    • 이 사전은 라이브러리의 소비자가 새 기능들을 선택적으로 켜고 끌 수 있도록 라이브러리를 작성하고 싶을 때 사용할 수 있는 표준적인 메커니즘 역할을 한다.
    • 이런 선택적 기능 활성화 접근방식은 대다수의 사용자에게는 아직 알리고 싶지 않은 실험적인 기능을 라이브러리에 추가하려고 할 때 유용하다.
  • 라이브러리 소비자는 다음과 같이 특정 ‘스위치’를 설정함으로써 특정 기능을 활성화 한다.
AppContext.SetSwitch("MyLibrary.SomeBreakingChange", true);
  • 라이브러리 안의 코드에서는 다음과 같은 방식으로 해당 스위치가 켜져 있는지 점검한다.
bool isDefined, switchValue;
isDefined = AppContext.TryGetSwitch("MyLibrary.SomeBreakingChange", out switchValue);
  • 모순적이게도 TryGetSwitch는 잘못 설계된 API의 예이다. 사실 out 매개변수는 없어도 된다. 대신 반환 형식을 널 가능 bool로 해서 스위치가 정의되어 있으면 해당 값을 돌려주고 정의되어 있지 않으면 null을 돌려주면 된다.
bool? switchValue = AppContext.GetSwitch("MyLibrary.SomeBreakingChange") ?? false;
[ssba]

The author

지성을 추구하는 디자이너/ suyeongpark@abyne.com

댓글 남기기

This site uses Akismet to reduce spam. Learn how your comment data is processed.