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


120

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

차이점은 무엇입니까

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

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

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



답변:


115

C # 6 이전

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

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

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

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

C # 6 사용

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

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

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



내가 알지. 그것은 농담으로 만들어졌습니다 (좋은 읽기였던 링크 btw를 읽었습니다)
nawfal

존. 나는 항상 Mr. Richter의 팬이었고, 복싱 등에 대한지도를 종교적으로 따랐습니다. 그러나 귀하의 (오래된) 기사를 읽은 후 저는 이제 개종자입니다. 감사합니다
stevethethread 2014 년


4
이제 C # 6을 사용할 수 있으므로 더 쉽게 읽을 수있는 새로운 문자열 보간 구문을 사용할 수 있습니다.xlsSheet.Write($"C{rowIndex}", null, title);
HotN

158

나의 초기 환경 설정 (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()가장자리가 있습니다.

5
1) string.FormatReSharper를 사용할 때 "안전"합니다. 즉, [잘못된] 사용할 수있는 다른 코드만큼 안전합니다. 2) string.Format 않는다 "안전한"허용 null: string.Format("A{0}B", (string)null)"AB"의 결과. 3) 나는 거의 이러한 수준의 성능에 관심 없다 (그리고이를 위해,이다 드문 일 내가 꺼내 StringBuilder) ...

2)에 동의하면 게시물을 수정하겠습니다. 이것이 1.1에서 안전한지 확인할 수 없지만 최신 프레임 워크는 실제로 null-safe입니다.
댄 C.

피연산자 중 하나가 매개 변수 또는 변수가 아닌 반환 값이있는 메서드 호출 인 경우에도 string.Concat이 사용됩니까?
Richard Collette 2013 년

2
@RichardCollette 예, String.Concat은 메서드 호출의 반환 값을 연결하는 경우에도 사용됩니다. 예를 들어 릴리스 모드에서 호출로 string s = "This " + MyMethod(arg) + " is a test";컴파일 String.Concat()됩니다.
Dan C.

환상적인 대답; 아주 잘 작성되고 설명되었습니다.
Frank V

6

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

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

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

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



5

간단한 단일 연결 인 간단한 경우에는 복잡 할 가치가 없다고 느낍니다 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";

1
참고로 모든 연결 대신 @ "... multi line string"을 사용할 수 있습니다.
Aaron Palmer

예,하지만 문자열을 왼쪽 정렬해야합니다. @ 문자열에는 따옴표 사이의 모든 새 줄과 탭 문자가 포함됩니다.
P Daddy

나는 이것이 오래되었다는 것을 알고 있지만 이것은 resx 파일에 형식 문자열을 넣는 경우입니다.
Andy

2
와우, 모두가 문제의 핵심이 아닌 문자열 리터럴에 집중하고 있습니다.
P Daddy

헤헤 - 난 그냥 당신의 문자열 연결의 내부에 주목했다String.Format()
크리스토퍼

3

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


3

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)을 사용하여 제거되는 중간 결과입니다.
각 중간 문자열을 만들려면 이전 문자열을 새 메모리 위치에 복사해야하며 잠재적으로 느린 메모리 할당 만이 아닙니다.


4
Nitpick. "a"+ ... + "b"+ ... + "c"+ ...에는 실제로 4 개의 중간 문자열이 없습니다. 컴파일러는 String.Concat (params string [] values) 정적 메서드에 대한 호출을 생성하고 모두 한 번에 연결됩니다. 그래도 가독성을 위해 string.Format을 선호합니다.
P Daddy

2

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

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


2

그 예는 아마 너무 사소해서 차이를 알아 차리지 못할 것입니다. 사실, 대부분의 경우 컴파일러가 모든 차이를 최적화 할 수 있다고 생각합니다.

그러나 내가 추측해야한다면 string.Format()더 복잡한 시나리오에 유리할 것입니다. 그러나 실제 데이터를 기반으로하지 않고 여러 개의 불변 문자열을 생성하는 대신 버퍼를 사용하여 더 나은 작업을 수행 할 가능성이 있다는 직감에 가깝습니다.


1

위의 많은 점에 동의합니다. 제가 언급해야 할 또 다른 점은 코드 유지 관리입니다. string.Format을 사용하면 코드를 쉽게 변경할 수 있습니다.

즉, 메시지가 "The user is not authorized for location " + location있거나 "The User is not authorized for location {0}"

다음과 같이 메시지를 변경하려는 경우 : location + " does not allow this User Access"또는 "{0} does not allow this User Access"

string.Format으로 내가해야 할 일은 문자열을 변경하는 것입니다. 연결을 위해 해당 메시지를 수정해야합니다.

여러 장소에서 사용하면 시간을 절약 할 수 있습니다.


1

나는 string.format이 더 빠르다는 인상을 받았다.이 테스트에서 3 배 더 느린 것 같습니다.

string concat = "";
        System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch    ();
        sw1.Start();
        for (int i = 0; i < 10000000; i++)
        {
            concat = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}","1", "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" , "10" , i);
        }
        sw1.Stop();
        Response.Write("format: "  + sw1.ElapsedMilliseconds.ToString());
        System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch();
        sw2.Start();
        for (int i = 0; i < 10000000; i++)
        {
            concat = "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10" + i;
        }
        sw2.Stop();

string.format은 4.6 초가 걸렸고 '+'를 사용하면 1.6 초가 걸렸습니다.


7
컴파일러는 인식 "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10"라인을 효과적으로가되도록, 하나의 문자열 리터럴로 "12345678910" + i빠르게 이전 인string.Format(...)
wertzui

0

string.Format은 형식 템플릿 ( "C {0}")이 구성 파일 (예 : Web.config / App.config)에 저장 될 때 더 나은 선택 일 수 있습니다.


0

string.Format, StringBuilder 및 문자열 연결을 포함한 다양한 문자열 메서드에 대한 약간의 프로파일 링을 수행했습니다. 문자열 연결은 거의 항상 다른 문자열 작성 방법보다 성능이 뛰어났습니다. 따라서 성능이 핵심이라면 더 좋습니다. 그러나 성능이 중요하지 않은 경우 개인적으로 string.Format이 코드에서 더 쉽게 따라갈 수 있음을 찾습니다. (그러나 그것은 주관적인 이유입니다) 그러나 StringBuilder는 아마도 메모리 사용과 관련하여 가장 효율적일 것입니다.



-1

문자열 연결은 String.Format에 비해 더 많은 메모리를 사용합니다. 따라서 문자열을 연결하는 가장 좋은 방법은 String.Format 또는 System.Text.StringBuilder Object를 사용하는 것입니다.

첫 번째 경우를 살펴 보겠습니다. "C"+ rowIndex.ToString () rowIndex가 값 유형이므로 ToString () 메서드가 Box에서 값을 String으로 변환 한 다음 CLR이 두 값이 모두 포함 된 새 문자열에 대한 메모리를 생성한다고 가정 해 보겠습니다.

string.Format이 객체 매개 변수를 예상하고 rowIndex를 객체로 취하고 내부적으로 문자열로 변환하는 경우 Boxing이 있지만 내재적이며 첫 번째 경우만큼 많은 메모리를 차지하지 않습니다.

짧은 문자열의 경우 그다지 중요하지 않습니다 ...

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.