본문 바로가기

진리는어디에/C#

[C#] 문자열(string) 완벽 가이드

System.String 클래스

C#에서는 문자열을 처리하기 위해 System.String 클래스를 제공한다. String 클래스는 문자열을 안전하게 작성, 조작 및 비교할 수 있도록 다양한 메서드들을 제공하며 문자열 관련 작업을 단순화하기 위해 연산자 오버로드도 제공한다.

C# 프로그래밍 예제에 흔히 등장하는 소문자로 시작하는 string 클래스는 System.String 클래스의 별칭으로 System.String 클래스와 완전히 동일하다. 본 포스트에서는 System.String 대신 string 키워드를 사용할 것이며 이는 System.String으로 대체해도 무방하다.

※ 설명의 편의를 위해 본 포스트에서는 String 객체 자체를 지칭할 때는 '스트링'이란 용어를 사용하고, 문자의 배열 또는 텍스트 문자열등을 지칭할 때는 '문자열'이라는 용어를 사용하고 있다.

문자열 선언 및 초기화

String 클래스 생성자는 여러 버전으로 오버로드 되어 있어 스트링 변수를 여러 가지 방법으로 생성할 수 있다.

생성자 설명
string(char[] str) 입력한 문자열을 갖는 스트링 객체 생성
string(char ch, int n) 입력한 문자를 n번 반복하는 스트링 객체 생성
string(char[] str, int start, int count) 입력한 문자열의 start 번째 문자를 시작으로 count 만큼의 길이를 갖는 스트링 객체 생성

아래 예제는 다양한 방법으로 스트링 객체를 선언하고 초기화 하는 방법을 다르고 있다.

// 초기화 없이 단순 선언
string message1;

// null로 초기화
string message2 = null;

// 빈 스트링 객체 생성 1
string message3 = "";

// 빈 스트링 객체 생성 2. 위 방법 보다는 이 방법을 사용할 것!
string message4 = System.String.Empty;

// 문자열로 초기화
string oldPath = "C:\\Path\\To\\SomeWhere";

// 이스케이프 문자('\')를 사용하지 않아도 되는 문자열 초기화
string newPath = @"C:\Path\To\SomeWhere";

// System.String을 이용한 초기화(string을 사용하는 것과 동일하다)
System.String greeting = "hello world";

// var 키워드를 이용한 묵시적 스트링 객체 생성
var temp = "I'm still a strongly-typed System.String!";

// const 키워드를 이용한 상수 스트링 객체 생성
const string message5 = "You can't get rid of me!";

// 문자열
char[] letters = { 'A', 'B', 'C' }; 

// ABC, 문자 배열을 이용해 스트링 객체 생성
string alphabet1 = new string(letters);

// BC, start번째 문자를 시작으로 count 만큼의 길이를 갖는 스트링 객체 생성
string alphabet2 = new string(letters, 1, 2);

// AAA, 입력한 문자를 n번 반복하는 스트링 객체 생성
string alphabet3 = new string('A', 3);

위 방법들은 일반적으로 스트링을 생성하고 초기화하는 방법들이다. 많다고 당황하거나 겁먹을 필요 없다. 시도 때도 없이 자주 쓰는 내용들이므로 외우고 싶지 않아도 자연스레 손이 외워서 쓰게 된다. 지금은 저런 방법들이 있다는것만 알고 넘어 가도록 하자.

String 클래스의 메소드

String 클래스는 C# 프로그램에서 문자열을 쉽고 안전하게 다룰수 있게 하기 위해 만들어졌고 그만큼 문자열을 다루는데 유용한 메소드들을 다양하게 제공한다. 아래 표는 String 클래스가 가지고 있는 메소드중 자주 사용되는 유용한 몇가지들을 요약했다.

메소드 설명
Clone 스트링 객체에 대한 참조를 반환
Compare 지정된 두 개의 스트링을 비교
CompareTo 스트링을 다른 객체와 비교
Concat 두 개의 스트링을 하나로 결합
Contains 지정된 문자열이 스트링 내에 있는지 체크
Copy[Obsolete] 지정된 스트링과 동일한 값을 갖는 새로운 스트링 생성(더 이상 사용하지 않는다)
EndsWith 스트링의 마지막 부분을 확인
Equals 두 개의 스트링이 같은 값을 가지는지 비교
IndexOf 스트링 내에서 지정한 문자열이 처음으로 발생하는 위치를 반환
Insert 스트링 내의 지정한 위치에 새로운 문자열을 삽입
Intern 스트링에 대한 시스템 참조값을 반환
Join 두 개 스트링 사이를 지정된 구분자를 삽입해서 하나로 결합
LastIndexOf 스트링 내에서 지정한 문자열이 마지막으로 발생하는 위치를 반환
PadLeft 스트링을 오른쪽으로 정렬하고 빈 곳에는 공백을 삽입
PadRight 스트링을 왼쪽으로 정렬하고 빈 곳에는 공백을 삽입
Replace 스트링 내의 지정된 문자열을 다른 문자 혹은 문자열로 치환
Split 지정된 구분자를 사용하여 스트링을 분리
StartsWith 스트링이 시작된 부분을 확인
ToCharArray 스트링 내의 문자열을 문자 배열로 복사해 리턴
ToLower 스트링의 모든 문자들을 소문자로 바꾸어 리턴
ToString 객체를 스트링 객체로 변경해 리턴
ToUpper 스트링의 모든 문자들을 대문자로 바꾸어 리턴
Trim 스트링의 시작과 끝 부분에 반복되는 특정 문자들을 제거
TrimEnd 스트링의 끝 부분에 반복되는 특정 문자들을 제거
TrimStart 스트링의 시작 부분에 반복되는 특정 문자들을 제거

Clone()

Clone 메소드는 객체의 참조를 반환한다. 아래 예제를 통해 스트링 객체의 주소가 같음을 확인 할 수 있다. (아래의 unsafe 블록 안에 있는 내용들은 주소를 출력하기 위한 코드다. 지금 단계에서는 중요하지 않으니 주소가 같다는 것에 집중하고 unsafe 블록에 대해서는 신경 끄자)

string message1 = "Hello World";
string message2 = (string)message1.Clone(); // 참조를 리턴, 이제 부터 message1과 message2는 같다.

// 스트링 객체 주소 출력
unsafe
{
    TypedReference tr1 = __makeref(message1);
    TypedReference tr2 = __makeref(message2);
    
    IntPtr ptr1 = **(IntPtr**)(&tr1);
    IntPtr ptr2 = **(IntPtr**)(&tr2);
    
    Console.WriteLine("message1 : " + message1 + " " + ptr1.ToString());
    Console.WriteLine("message2 : " + message2 + " " + ptr2.ToString());
}

// message1 : Hello World 2592012807320
// message2 : Hello World 2592012807320

Copy()

.NET Core 3.0부터 이 메서드는 사용되지 않는다.

Concat()

Concat 메서드는 지정된 스트링들을 결합하여 새로운 스트링 객체를 리턴한다. Concat 메소드는 여러 오버로드 버전이 있으며 필요에 따라 선택해 사용하면 된다. 일반적으로 사용되는 오버로드 메소드 중 일부는 아래와 같다.

  • Concat(String, String)
  • Concat(String, Stirng, String)
  • Concat(String, String, String, String)
  • Concat(Object)
  • Concat(Object, Object)
  • Concat(Object, Object, Object)
  • Concat(Object, Object, Object, Object)
string a = "Hello";
string b = "World";
Console.WriteLine(string.Concat(a,b));

// HelloWorld

Contains()

Contains 메서드는 지정된 문자열이 스트링 클래스에 포함되어 있는지 여부를 검사하여 True 또는 False를 리턴한다.

string a = "HelloWorld";
string b = "World";
Console.WriteLine(a.Contains(b));

// True

Equals()

Equals 메서드는 주어진 두 문자열이 동일한지 여부를 확인하는 데 사용 된다. 두 문자열 모두 동일한 값을 가지고 있는 경우 이 메서드는 true를 반환하고 서로 다른 값을 가지는 경우 false를 반환한다.

string a = "Hello";
string b = "World";
Console.WriteLine(a.Equals(b));

// False

참고로 값이 아닌 참조가 같은지를 비교하기 위해선 Equals 메서드가 아니라 '==' 연산자를 사용하도록 한다.

IndexOf()

스트링 내에 지정된 문자열이 처음으로 발생하는 위치를 반환 한다. 위치는 0부터 시작한다. 아래 예제를 보면  "Hello"에서 'o'가 나타나는 위치는 다섯번째, 0 부터 시작하므로 4가 된다.

string a = "Hello";
int b = a.IndexOf('o');
Console.WriteLine(b);

// 4

대상 문자열에 해당하는 문자나 문자열을 찾을 수 없으면 -1을 반환한다.

Insert()

스트링 내의 지정한 인덱스에 새로운 문자열을 삽입한다. Insert 메서드는 0부터 시작하는 인덱스와 삽입할 문자열을 인자로 받아 문자열이 삽입된 새로운 문자열을 리턴한다.

string a = "Hello";
string b = a.Insert(2, “_World_”);
Console.WriteLine(b);

// He_World_llo

Replace()

스트링 객체의 지정된 문자열을 다른 문자열로 치환한다. 첫번째 인자는 치환 대상이 될 문자열, 두번째는 치환할 문자열이다. 아래 예제에서는 문자열 "Hello" 의 "lo"를 "World"로 변경하고 있다.

string a = "Hello";
string b = a.Replace(“lo”, “World”);
Console.WriteLine(b);

// HelWorld

SubString()

스트링의 문자열 일부를 추출하는데 사용된다. 0부터 시작하는 인덱스를 인자로 받아 해당 인덱스 부터 뒤의 뒤의 문자열을 가진 새로운 스트링 객체를 리턴한다.

string a = "Hello";
string b = a.Substring(2);
Console.WriteLine(b);

// llo

StringBuilder 클래스

C#에서 문자열은 한번 생성되어 메모리에 할당 되면 더 이상 변경되지 않는다. 만일 스트링 값에 변경이 필요한 경우, 기존의 스트링이 변경 되는 것이 아니라 변경된 값을 가지는 새로운 스트링을 생성한다. 이렇게 되면 기존 스트링의 메모리 영역은 참조값이 없어지고 이후 가비지 컬렉터에 의해 시스템에게 반환된다.

.Net의 문자열 작업은 고도로 최적화 되어 있기 때문에 대부분의 경우 성능에 큰 영향을 주지 않지만, 타이트 루프에서 스트링 변경 작업이 빈번할 경우 추가적인 메모리 오버헤드와 가비지 콜렉팅으로 인한 성능 저하가 발생할 가능성이 있다. 이를 보완하기 위해서 마이크로소프트에서는 오버헤드 없이 스트링 객체를 조작하기 위한 또 하나의 클래스, StringBuilder를 제공한다.

아래 예는 StringBuilder를 이용하여 새 문자열을 만들지 않고 문자열 내의 문자를 변경한다.

System.Text.StringBuilder sb = new System.Text.StringBuilder("Rat: the ideal pet");
sb[0] = 'C';
System.Console.WriteLine(sb.ToString());
//Outputs Cat: the ideal pet

@ 문자열

@ 심볼을 문자열 앞에 사용하면 해당 문자열 안의 이스케이프 문자를 무시하고 문자 그대로 인식하도록 한다. 예를 들어 파일 패스를 지정할 때 역슬래쉬(\)는 이스케이프 문자로 인식 되기 때문에 연속 백슬래쉬(\\)를 사용해야만 해서 코드가 읽기 거북해진다. 하지만 @ 심볼을 사용하면 백슬래쉬를 백슬래쉬 그대로 인식하기 때문에 깔끔한 코드로 가독성을 높일 수 있다.

// 일반 문자열
string oldPath = "C:\\Path\\To\\SomeWhere";

// 이스케이프 문자('\')를 사용하지 않아도 되는 @ 사용 문자열 초기화
string newPath = @"C:\Path\To\SomeWhere";

또, 여러줄에 걸쳐있는 문자열이 필요한 경우 @를 사용하면 편리하다.

// + 연산자를 이용한 멀리라인 문자열
string code1 = "" +
    "1st line string\n" +
    "2nd line string\n" +
    "3rd line string\n";

Console.WriteLine(code1);

// 1st line string
// 2nd line string
// 3rd line string

// @ 심볼을 이용한 멀티라인 문자열
string code2 = @"
1st line string
2nd line string
3rd line string
";

Console.WriteLine(code2);

// 1st line string
// 2nd line string
// 3rd line string

결과는 둘다 같으나 @ 심볼을 사용한 쪽이 코드가 깔끔해지는 경향이 있다. 다만 멀티라인 사이의 공백도 그대로 처리하므로 코드 정리를 위해 들여쓰기는 사용하기 힘들다.

$ 문자열

문자열을 변수들과 다이나믹하게 결합하기 위해선 string.Format을 이용해 변수들이 결합될 위치를 지정할 수 있다.

string str1 = string.Format("Hello {0}, {1}", "World", 123);
Console.WriteLine(str1);

// Hello World, 123

하지만 위 방법은 항상 순서를 신경써야 하기 때문에 결합할 문자열이 길어지는 경우 실수할 여지가 다분히 있다.

Format 메서드 대신 문자열 앞에 $ 심볼을 표기해주면 변수를 문자열 사이에 다이나믹하게 삽입할 수 있다.

string world = "World";
int num = 123;
string str2 = $"Hello {world}, {num}";
Console.WriteLine(str1);

// Hello World, 123

같은 결과를 출력하지만 $ 심볼을 이용하여 보다 가독성이 높게 코드를 작성할 수 있다.

마치며

이상 C# string의 생성과 초기화 그리고 자주 사용되는 메서드 들에 대해 살펴 보았다. 아래에 소개 되는 C#의 다양한 기능들에 대해서도 살펴 보면 여러분들에게 작게나마 도움이 되리라 생각한다.

부록 1. 같이 읽으면 좋은 글

유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!