[C#] String.Format과 문자열 연결을 사용하는 것이 언제 더 낫습니까?

Excel에 대한 셀 입력을 결정하기 위해 인덱스 값을 구문 분석하는 작은 코드가 있습니다. 생각하게 해 …

차이점은 무엇입니까

xlsSheet.Write("C" + rowIndex.ToString(), null, title);

xlsSheet.Write(string.Format("C{0}", rowIndex), null, title);

하나가 다른 것보다 “좋은”것입니까? 그리고 왜?



답변

C # 6 이전

솔직히 말해서 첫 번째 버전이 더 간단하다고 생각합니다.

xlsSheet.Write("C" + rowIndex, null, title);

나는 다른 답변 성능 저하에 대해 이야기 할 수 있다고 생각 하지만 솔직히 말하면 전혀 존재한다면 최소한 일 것입니다. 이 연결 버전은 형식 문자열을 구문 분석 할 필요가 없습니다.

형식 문자열은 지역화 등의 목적에 적합하지만 이와 같은 경우에는 연결이 더 간단하고 잘 작동합니다.

C # 6 사용

문자열 보간을 사용하면 C # 6에서 많은 것을 더 쉽게 읽을 수 있습니다.이 경우 두 번째 코드는 다음과 같습니다.

xlsSheet.Write($"C{rowIndex}", null, title);

아마도 가장 좋은 옵션 인 IMO입니다.


답변

나의 초기 환경 설정 (C ++ 배경에서 유래)은 String.Format이었습니다. 다음과 같은 이유로 나중에 삭제했습니다.

  • 문자열 연결은 “안전”합니다. 매개 변수를 제거하거나 실수로 매개 변수 순서를 엉망으로 만드는 일이 나에게 일어났습니다 (그리고 다른 개발자들에게도 발생했습니다). 컴파일러는 형식 문자열에 대해 매개 변수를 확인하지 않고 런타임 오류가 발생합니다 (즉, 오류 로깅과 같은 모호한 메서드에 포함되지 않을만큼 운이 좋은 경우). 연결을 사용하면 매개 변수를 제거 할 때 오류가 덜 발생합니다. 오류 가능성이 매우 적다고 주장 할 수 있지만 발생할 수 있습니다 .

-문자열 연결은 null 값을 허용하지만 허용 String.Format하지 않습니다. ” s1 + null + s2“을 작성 하면 중단되지 않고 null 값을 String.Empty로 처리합니다. 글쎄, 이것은 특정 시나리오에 따라 다를 수 있습니다. null FirstName을 자동으로 무시하는 대신 오류를 원하는 경우가 있습니다. 그러나이 상황에서도 개인적으로 null을 확인하고 String.Format에서 얻은 표준 ArgumentNullException 대신 특정 오류를 던지는 것을 선호합니다.

  • 문자열 연결이 더 잘 수행됩니다. 위의 게시물 중 일부는 이미 이것을 언급했습니다 (실제로 이유를 설명하지 않았 으므로이 게시물을 작성하기로 결정했습니다 :).

아이디어는 .NET 컴파일러가이 코드 조각을 변환하기에 충분히 똑똑하다는 것입니다.

public static string Test(string s1, int i2, int i3, int i4,
        string s5, string s6, float f7, float f8)
{
    return s1 + " " + i2 + i3 + i4 + " ddd " + s5 + s6 + f7 + f8;
}

이에:

public static string Test(string s1, int i2, int i3, int i4,
            string s5, string s6, float f7, float f8)
{
    return string.Concat(new object[] { s1, " ", i2, i3, i4,
                    " ddd ", s5, s6, f7, f8 });
}

String.Concat의 내부에서 일어나는 일은 추측하기 쉽습니다 (Reflector 사용). 배열의 객체는 ToString ()을 통해 문자열로 변환됩니다. 그런 다음 총 길이가 계산되고 하나의 문자열 만 할당됩니다 (총 길이 포함). 마지막으로, 각 문자열은 안전하지 않은 일부 코드에서 wstrcpy를 통해 결과 문자열로 복사됩니다.

이유 String.Concat가 훨씬 빠르나요? 글쎄, 우리는 모두 무슨 String.Format일을 하는지 볼 수 있습니다. 포맷 문자열을 처리하는 데 필요한 코드의 양에 놀랄 것입니다. 이 String.Format외에도 ( 메모리 소비에 대한 의견을 보았습니다) 내부적으로 StringBuilder를 사용합니다. 방법은 다음과 같습니다.

StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));

따라서 전달 된 모든 인수에 대해 8자를 예약합니다. 인수가 한 자리 값이면 너무 나쁘면 공간이 낭비됩니다. 인수가에서 긴 텍스트를 반환하는 사용자 지정 개체 ToString()인 경우 재 할당이 필요할 수도 있습니다 (물론 최악의 시나리오).

이에 비해 연결은 객체 배열의 공간 만 낭비합니다 (참조 배열을 고려하여 너무 많지는 않음). 형식 지정자에 대한 구문 분석 및 중개 StringBuilder가 없습니다. boxing / unboxing 오버 헤드는 두 방법 모두에 존재합니다.

내가 String.Format을 사용하는 유일한 이유는 지역화가 관련된 경우입니다. 리소스에 형식 문자열을 넣으면 코드를 엉망으로 만들지 않고도 다른 언어를 지원할 수 있습니다 (형식화 된 값이 언어에 따라 순서가 바뀌는 시나리오를 생각해보세요. 즉, “{0} 시간 후 {1} 분 후”는 일본어에서 상당히 다르게 보일 수 있습니다. ).


내 첫 번째 (그리고 꽤 긴) 게시물을 요약하면 :

  • 나에게 가장 좋은 방법 (성능 대 유지 관리 / 가독성 측면에서)은 ToString()호출 없이 문자열 연결을 사용하는 것입니다.
  • 성능을 좋아한다면 ToString()권투를 피하기 위해 직접 전화하십시오 (나는 가독성에 다소 편향되어 있습니다)-질문의 첫 번째 옵션과 동일
  • 사용자에게 현지화 된 문자열을 표시하는 경우 (여기에서는 해당되지 않음) String.Format()가장자리가 있습니다.

답변

첫 번째 옵션이 더 읽기 쉽고 이것이 주요 관심사라고 생각합니다.

xlsSheet.Write("C" + rowIndex.ToString(), null, title);

string.Format은 내부적으로 StringBuilder를 사용하므로 ( reflector로 확인 ) 상당한 양의 연결을 수행하지 않는 한 성능상의 이점이 없습니다. 시나리오에서는 속도가 느리지 만 현실은 이러한 마이크로 성능 최적화 결정이 대부분 부적절하며 루프에 있지 않는 한 코드의 가독성에 집중해야합니다.

어느 쪽이든, 먼저 가독성을 위해 작성한 다음 성능 문제가 있다고 생각되면 성능 프로파일 러 를 사용하여 핫스팟을 식별하십시오.


답변

간단한 단일 연결 인 간단한 경우에는 복잡 할 가치가 없다고 느낍니다 string.Format(테스트하지 않았지만 이와 같은 간단한 경우 에는 형식 문자열 구문 분석이 약간 느려질 string.Format 있습니다. 그리고 다). Jon Skeet처럼 명시 적으로 호출하지 않는 것을 선호합니다 .ToString(). 왜냐하면 string.Concat(string, object)오버로드에 의해 암시 적으로 수행 될 것이기 때문이며 코드가 더 깔끔해 보이고 코드 없이는 읽기가 더 쉽다고 생각합니다.

그러나 몇 개 이상의 연결 (주관적인 수)에 대해서는 확실히 string.Format. 어느 시점에서 나는 가독성과 성능이 모두 연결로 인해 불필요하게 저하된다고 생각합니다.

형식 문자열에 대한 매개 변수가 많은 경우 (다시 말하지만 “many”는 주관적 임) 대체 인수에 주석 처리 된 인덱스를 포함하는 것을 선호합니다. 어떤 값이 어떤 매개 변수로 이동하는지 추적하지 않도록합니다. 인위적인 예 :

Console.WriteLine(
    "Dear {0} {1},\n\n" +

    "Our records indicate that your {2}, \"{3}\", is due for {4} {5} shots.\n" +
    "Please call our office at 1-900-382-5633 to make an appointment.\n\n" +

    "Thank you,\n" +
    "Eastern Veterinary",

    /*0*/client.Title,
    /*1*/client.LastName,
    /*2*/client.Pet.Animal,
    /*3*/client.Pet.Name,
    /*4*/client.Pet.Gender == Gender.Male ? "his" : "her",
    /*5*/client.Pet.Schedule[0]
);

최신 정보

내가 제시 한 예제가 약간 혼란 스럽습니다. 왜냐하면 연결과 여기를 모두 사용한 것처럼 보이기 때문 string.Format입니다. 그리고 예, 논리적으로 그리고 어휘 적으로, 그것이 제가 한 일입니다. 그러나 연결 은 모두 문자열 리터럴이기 때문에 컴파일러 1에 의해 모두 최적화됩니다 . 따라서 런타임에는 단일 문자열이 있습니다. 그래서 나는 내가 런타임에 많은 연결을 피하는 것을 선호한다고 말해야한다고 생각한다 .

물론 C # 5 또는 이전 버전을 계속 사용하지 않는 한이 주제의 대부분은 현재 구식입니다. 이제 우리는 가독성을 위해 거의 모든 경우에 훨씬 더 우수한 보간 문자열 을 가지고 string.Format있습니다. 요즘에는 문자열 리터럴의 시작이나 끝에 값을 직접 연결하지 않는 한 거의 항상 문자열 보간을 사용합니다. 오늘 저는 이전 예제를 다음과 같이 작성합니다.

Console.WriteLine(
    $"Dear {client.Title} {client.LastName},\n\n" +

    $"Our records indicate that your {client.Pet.Animal}, \"{client.Pet.Name}\", " +
    $"is due for {(client.Pet.Gender == Gender.Male ? "his" : "her")} " +
    $"{client.Pet.Schedule[0]} shots.\n" +
    "Please call our office at 1-900-382-5633 to make an appointment.\n\n" +

    "Thank you,\n" +
    "Eastern Veterinary"
);

이런 식으로 컴파일 타임 연결이 손실됩니다. 보간 된 각 문자열은 string.Format컴파일러 에 의해 호출로 바뀌고 그 결과는 런타임에 연결됩니다. 이는 가독성을 위해 런타임 성능을 희생한다는 것을 의미합니다. 대부분의 경우 런타임 패널티가 무시할 수 있기 때문에 가치있는 희생입니다. 그러나 성능이 중요한 코드에서는 다른 솔루션을 프로파일 링해야 할 수 있습니다.


1 C # 사양
에서 확인할 수 있습니다 .

… 상수 표현식에서 다음 구문이 허용됩니다.

  • 사전 정의 된 + … 이항 연산자 …

약간의 코드로도 확인할 수 있습니다.

const string s =
    "This compiles successfully, " +
    "and you can see that it will " +
    "all be one string (named `s`) " +
    "at run time";


답변

많은 변수가 연결되어 문자열이 더 복잡하다면 string.Format ()을 선택합니다. 그러나 문자열과 귀하의 경우 연결된되는 변수의 수의 크기, 나는 그것이 더, 첫 번째 버전으로 갈 것 스파르타 .


답변

String.Format (Reflector 사용)을 살펴 보았고 실제로 StringBuilder를 만든 다음 AppendFormat을 호출합니다. 따라서 여러 교반을 위해 연결하는 것보다 빠릅니다. 가장 빠른 (내 생각에) StringBuilder를 만들고 Append를 수동으로 호출하는 것입니다. 물론 “다”의 숫자는 추측 할 수 있습니다. 나는 + (실제로는 VB 프로그래머이기 때문에)를 예제처럼 간단한 것을 사용합니다. 더 복잡해지면 String.Format을 사용합니다. 변수가 많으면 StringBuilder 및 Append로 이동합니다. 예를 들어 코드를 빌드하는 코드가 있고 생성 된 코드 한 줄을 출력하기 위해 실제 코드 한 줄을 사용합니다.

이러한 작업 각각에 대해 생성되는 문자열 수에 대한 추측이있는 것 같으므로 몇 가지 간단한 예를 들어 보겠습니다.

"C" + rowIndex.ToString();

“C”는 이미 문자열입니다.
rowIndex.ToString ()은 다른 문자열을 만듭니다. (@manohard-rowIndex의 박싱이 발생하지 않음)
그런 다음 최종 문자열을 얻습니다.
예를 들어 보면

String.Format("C(0)",rowIndex);

그런 다음 “C {0}”를 문자열로 사용합니다.
rowIndex가 함수에 전달되도록 boxed됩니다
. 새 stringbuilder가 생성됩니다.
AppendFormat이 문자열 작성기에서 호출됩니다. AppendFormat이 어떻게 작동하는지에 대한 자세한 내용은 알지 못합니다. 매우 효율적이지만, 여전히 boxed rowIndex를 문자열로 변환해야합니다.
그런 다음 stringbuilder를 새 문자열로 변환하십시오.
StringBuilders가 무의미한 메모리 복사가 발생하는 것을 방지하려고 시도하지만 String.Format은 일반 연결에 비해 추가 오버 헤드로 끝납니다.

이제 몇 개의 문자열이있는 예를 들어 보면

"a" + rowIndex.ToString() + "b" + colIndex.ToString() + "c" + zIndex.ToString();

모든 경우에 동일하게 시작할 6 개의 문자열이 있습니다.
연결을 사용하면 4 개의 중간 문자열과 최종 결과도 있습니다. String, Format (또는 StringBuilder)을 사용하여 제거되는 중간 결과입니다.
각 중간 문자열을 만들려면 이전 문자열을 새 메모리 위치에 복사해야하며 잠재적으로 느린 메모리 할당 만이 아닙니다.


답변

나는 String.Format을 좋아한다. 당신의 포맷 된 텍스트를 인라인 연결보다 훨씬 더 쉽게 따르고 읽을 수있게 만들고 매개 변수를 포맷 할 수있는 훨씬 더 유연하지만, 당신과 같은 짧은 용도의 경우 연결에 대한 문제가 없다고 본다.

루프 내부 또는 큰 문자열 연결의 경우 항상 StringBuilder 클래스를 사용해야합니다.