Immutable 타입으로 인한 문제 : 하기 코드에서 2번째 요소 문자열인 "- 0.0002E+0" 의 '-' 와 0 사이의 공백 지우고자 한다. (계측기로부터 제공받은 값인데, string에서 double 변환 시 0으로 변경됨. 원인은 공백으로 인해 변환 실패)
var values = "100.000E+8,- 0.0002E+0";
var tmpArray = values.Split(',');
foreach(var item in tmpArray)
{
item.Trim();
item.Replace(" ", "");
} // string 은 immutable(불변) 타입이라서, Trim() 이나 Replace() 를 호출해도 원본 문자열은 변하지 않는다.
double.TryParse(tmpArray[0], out double irValueRt);
double.TryParse(tmpArray[1], out double voltageValueRt); // 0으로 됨
그러나 위와 같은 코드로 Replace를 해도 값이 변하지 않고 공백이 유지된다.
C#의 string 은 immutable(불변) 이라서 한 번 만들어진 문자열의 내용은 바꿀 수 없다.
내용을 “수정” 하고 싶을 때는 보통 StringBuilder 를 사용하거나, Span<char> / Memory<char> 같은 버퍼를 쓴다.
string rt = "";
foreach(var item in tmpArray)
{
item.Trim();
rt = item.Replace(" ", "");
}
string 타입을 하나 만들어서 해당 rt 같은 string 타입을 생성 및 할당 해 문제 해결할 수 있다.
var values = "100.000E+8,- 0.0002E+0";
values = values.Replace("\r", "").Replace(" ", "").Trim();
var tmpArray = values.Split(',');
double.TryParse(tmpArray[0], out double irValueRt);
double.TryParse(tmpArray[1], out double voltageValueRt); //문제 해결
상기 코드는 문자열 파싱 처리 후 values에 재 할당 (새 객체 생성)을 하여 문제를 해결했다. (\r 은 상기 코드에선 존재하지 않으나 계측기로부터 시리얼 통신으로부터 문자열 받을 시 \r도 함께 제공할 수 있기에, 넣은 거다. )
다만 상황에 따라, 불필요한 객체 생성 줄이기 위해선 다음과 같이 사용한다.
1. StringBuilder 사용
문자열을 여러 번 조작해야 할 때 가장 간단하다.
var sb = new StringBuilder("- 0.0002E+0");
for (int i = sb.Length - 1; i >= 0; i--)
{
if (char.IsWhiteSpace(sb[i]))
sb.Remove(i, 1); // 공백 제거
}
var cleaned = sb.ToString();
double value = double.Parse(cleaned, CultureInfo.InvariantCulture); // -0.0002
2. Span<char> / Memory<char>
성능을 더 신경 써야 한다면 Span<char> 를 이용해 “버퍼” 위에서 직접 수정 가능하다.
string raw = "- 0.0002E+0";
Span<char> buffer = stackalloc char[raw.Length];
raw.AsSpan().CopyTo(buffer);
int j = 0;
foreach (var ch in buffer)
{
if (!char.IsWhiteSpace(ch))
buffer[j++] = ch;
}
string cleaned = new string(buffer.Slice(0, j));
double value = double.Parse(cleaned, CultureInfo.InvariantCulture);
결론
string 자체를 mutable 로 바꿀 순 없다.
다만, 조작하려면 StringBuilder 나 Span<char> 처럼 mutable 한 버퍼를 사용 → 최종적으로 다시 string 생성 하는 방식으로 처리해야 한다.
참고
[객체 생성 여부]
string 은 완전히 불변이라, 한 글자만 바꿔도 항상 새로운 string 인스턴스를 만든다.
StringBuilder 와 Span<char> 은 “버퍼” 개념이 있어서, 내부 배열의 요소를 바꾸지만 객체 자체는 그대로 유지한다.
조금 더 구체적으로 보면:
| 타입 | 변경 시 동작 | 새 객체 생성 여부 |
| string | 내용을 바꿀 수 없으므로, 변경 작업 → 새 string 반환 | 항상 새 객체 |
| StringBuilder | 내부 char[] 버퍼에서 글자를 수정/추가/삭제 | 버퍼 크기 한도 내에서는 동일 객체 (버퍼가 꽉 차면 더 큰 배열을 할당 후 복사) |
| Span<char> | 스택/힙에 있는 기존 메모리 위에서 직접 수정 | 새 객체 안 만듦 |
정리
- 문자열을 자주 편집해야 한다면 StringBuilder(또는 Span<char>/Memory<char>) 를 써야, 매번 새 string 을 만드는 비용을 줄일 수 있다.
- 하지만 StringBuilder.ToString() 을 호출하면 그 시점에 새 string 을 한 번 생성한다. (string 자체는 불변이라 어쩔 수 없음)
결국 수정 중엔 버퍼 재사용, 최종 결과를 문자열로 쓸 때만 한 번 새 string 을 만든다고 이해하면 된다.