문자열 출력 : C #의 형식 또는 연결?


178

문자열을 출력하거나 연결한다고 가정 해 봅시다. 다음 중 선호하는 스타일은 무엇입니까?

  • var p = new { FirstName = "Bill", LastName = "Gates" };

  • Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

  • Console.WriteLine(p.FirstName + " " + p.LastName);

오히려 형식을 사용하거나 단순히 문자열을 연결합니까? 가장 좋아하는 것은 무엇입니까? 이것들 중 하나가 당신의 눈을 아프게합니까?

하나를 사용하는 것이 아니라 다른 것을 사용하는 합리적인 논쟁이 있습니까?

나는 두 번째로 갈 것입니다.

답변:


88

이 코드를 사용해보십시오.

약간 수정 된 코드 버전입니다.
1. Console.WriteLine은 측정하려고하는 것보다 몇 배나 느릴 수 있으므로 제거했습니다.
2. 루프 전에 스톱워치를 시작하고 바로 멈 춥니 다.이 방법으로 함수 실행에 26.4 틱이 걸리더라도 정밀도를 잃지 않습니다.
3. 결과를 일부 반복으로 나누는 방식이 잘못되었습니다. 1000 밀리 초와 100 밀리 초가 있으면 어떻게되는지 확인하십시오. 두 경우 모두 1000000으로 나눈 후 0ms가됩니다.

Stopwatch s = new Stopwatch();

var p = new { FirstName = "Bill", LastName = "Gates" };

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (p.FirstName + " " + p.LastName);
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();
s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", p.FirstName, p.LastName);
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();


Console.Clear();
Console.WriteLine(n.ToString()+" x result = string.Format(\"{0} {1}\", p.FirstName, p.LastName); took: " + (fElapsedMilliseconds) + "ms - " + (fElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x result = (p.FirstName + \" \" + p.LastName); took: " + (cElapsedMilliseconds) + "ms - " + (cElapsedTicks) + " ticks");
Thread.Sleep(4000);

그게 내 결과입니다.

1000000 x 결과 = string.Format ( "{0} {1}", p.FirstName, p.LastName); 소요 : 618ms-2213706 틱
1000000 x 결과 = (p.FirstName + ""+ p.LastName); 소요 : 166ms-595610 틱


1
매우 흥미로운. 나는 평균 224ms 대 48ms, x4.66 개선, x3.72보다 훨씬 낫습니다. IL을 다시 작성할 수있는 컴파일 후 도구가 string.Format합성 형식화 기능을 사용하지 않고 (즉, 단순함 {0}) 훨씬 빠른 문자열 연결로 대체 하는지 궁금합니다 . PostSharp와 같은 기존 IL 리 라이터를 통해 이러한 성과를 달성 할 수 있을지 궁금합니다.
Allon Guralnek

31
문자열은 변경할 수 없습니다. 이는 코드에서 동일한 작은 메모리 조각이 반복해서 사용됨을 의미합니다. 동일한 두 문자열을 함께 추가하고 동일한 새 문자열을 반복해서 생성해도 메모리에는 영향을 미치지 않습니다. .Net은 동일한 메모리 참조를 사용하기에 충분합니다. 따라서 코드는 두 concat 메소드의 차이점을 실제로 테스트하지 않습니다. 아래 답변에서 코드를 참조하십시오.
루 딩턴

1
솔직히, 난 항상 읽기 쉽고 더 빨리 와우하기 때문에 항상 연결 :)
puretppc

속도가 다른 것을 선택하는 유일한 이유입니까?
niico

158

많은 사람들이 즉시 가장 빠른 코드를 찾고 싶어한다는 사실에 놀랐습니다. 한 번의 반복으로 처리하는 데 1 초도 걸리지 않으면 최종 사용자에게 눈에 띄게 나타날까요? 별로.

조기 최적화 = 실패.

String.Format옵션은 아키텍처 관점에서 가장 의미가 있기 때문에 옵션을 사용합니다. 나는 그것이 문제가 될 때까지 성능에 관심이 없다.

고객이 나중에 표시 여부를 구성 할 수 있도록 "Firstname Lastname"또는 "Lastname, Firstname."형식 옵션을 사용하여 이를 변경하려는 경우이를 고려하십시오 . 형식 문자열을 바꾸면됩니다. concat을 사용하면 추가 코드가 필요합니다. 이 특정 예제에서는 큰 소리처럼 들리지 않지만 외삽됩니다.


47
"조기 최적화 == FAIL"측면에서 좋은 점입니다. 그러나 실행 풋 프린트 (클라우드 및 인프라 스트럭처 서비스, 누구?)에 대한 비용을 지불하거나 1 백만 명의 사용자를 지원하기 시작하면 요청에 대한 단일 사용자의 응답은 문제가되지 않습니다. 사용자에게 요청을 서비스하는 비용은 몇 천 건의 전화가 걸려 오는 경우의 규모 문제뿐만 아니라 최종 비용에 대한 비용입니다.
Aidanapword

23
이것은 완전히 잘못되었습니다. 웹 개발 환경에서는 종종 문자열 생성 코드가 모델, 뷰 및 컨트롤러 모두에서 깊어지고 페이지로드 당 수만 번 호출 될 수 있습니다. 문자열 생성 코드를 평가하는 데 소요되는 시간을 50 % 단축하는 것은 엄청난 승리 일 수 있습니다.
벤자민 서스

2
이와 같은 질문은 OP의 한 인스턴스에만 적용되는 것이 아닙니다. 이 답변은 사람들이 "어떻게 현을 조립해야합니까?"로 기억할 수있는 것입니다. 모든 코드 를 작성할 때
Phil Miller

6
@ Benjamin : ...이 경우 프로필을 작성하여 병목 현상이 발생합니다. 그래도 난 당신이 아무데도 그것을 당기고 돈을 내기 것입니다; 과거에 많은 웹 응용 프로그램을 작성하고 프로파일 링 한 결과 응답 시간 (서버 측) 에서 병목 현상 이 데이터베이스 쿼리라는 것을 거의 항상 발견했습니다 .
BlueRaja-대니 Pflughoeft

2
이것은 가장 조기 최적화가 아닙니다. 꽤 잘못된 것입니다. 많은 형식화 및 문자열 작성을 수행하는 경우 특히 .NET에서 문자열 성능이 UI를 완전히 정지시킬 수 있습니다. ubiquity.acm.org/article.cfm?id=1513451
user99999991

54

오, 친애하는-다른 답글 중 하나를 읽은 후 작업 순서를 반대로 바꾸어 보았습니다. 따라서 연결을 먼저 수행 한 다음 String.Format ...

Bill Gates
Console.WriteLine(p.FirstName + " " + p.LastName); took: 8ms - 30488 ticks
Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 0ms - 182 ticks

따라서 작업 순서가 큰 차이를 만들거나 첫 번째 작업은 항상 훨씬 느립니다.

다음은 작업이 두 번 이상 완료된 실행 결과입니다. 주문 변경을 시도했지만 첫 번째 결과가 무시되면 일반적으로 동일한 규칙을 따릅니다.

Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 5ms - 20335 ticks
Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 0ms - 156 ticks
Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 0ms - 122 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 181 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 122 ticks
Bill Gates
String.Concat(FirstName, " ", LastName); took: 0ms - 142 ticks
Bill Gates
String.Concat(FirstName, " ", LastName); took: 0ms - 117 ticks

보시다시피 동일한 메소드의 후속 실행 (코드를 3 개의 메소드로 리팩토링)이 점차 빨라졌습니다. 가장 빠른 것은 Console.WriteLine (String.Concat (...)) 메소드에 이어 정상 연결과 형식화 된 조작으로 나타납니다.

첫 번째 작업이 모든 시간을 다시 시작하기 전에 Console.Writeline ( "Start!")을 배치하면 시작시 지연이 콘솔 스트림이 초기화 될 수 있습니다.


2
그런 다음 테스트에서 Console.WriteLine을 완전히 제거하십시오. 결과가 왜곡됩니다!
CShark

이 정확한 이유에 대한 성능 테스트를 실행할 때 나는 항상 일회용 또는 "컨트롤"시나리오로 시작
drzaus

36

문자열은 변경할 수 없습니다. 이는 코드에서 동일한 작은 메모리 조각이 반복해서 사용됨을 의미합니다. 동일한 두 문자열을 함께 추가하고 동일한 새 문자열을 반복해서 생성해도 메모리에는 영향을 미치지 않습니다. .Net은 동일한 메모리 참조를 사용하기에 충분합니다. 따라서 코드는 두 concat 메소드의 차이점을 실제로 테스트하지 않습니다.

크기에 대해 이것을보십시오 :

Stopwatch s = new Stopwatch();

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0, sbElapsedMilliseconds = 0, sbElapsedTicks = 0;

Random random = new Random(DateTime.Now.Millisecond);

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (random.Next().ToString() + " " + random.Next().ToString());
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();

s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", random.Next().ToString(), random.Next().ToString());
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();

StringBuilder sb = new StringBuilder();
s.Start();
for(var i = 0; i < n; i++){
    sb.Clear();
    sb.Append(random.Next().ToString());
    sb.Append(" ");
    sb.Append(random.Next().ToString());
    result = sb.ToString();
}
s.Stop();
sbElapsedMilliseconds = s.ElapsedMilliseconds;
sbElapsedTicks = s.ElapsedTicks;
s.Reset();

Console.WriteLine(n.ToString() + " x result = string.Format(\"{0} {1}\", p.FirstName, p.LastName); took: " + (fElapsedMilliseconds) + "ms - " + (fElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x result = (p.FirstName + \" \" + p.LastName); took: " + (cElapsedMilliseconds) + "ms - " + (cElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(\" \"); sb.Append(random.Next().ToString()); result = sb.ToString(); took: " + (sbElapsedMilliseconds) + "ms - " + (sbElapsedTicks) + " ticks");
Console.WriteLine("****************");
Console.WriteLine("Press Enter to Quit");
Console.ReadLine();

샘플 출력 :

1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 513ms - 1499816 ticks
1000000 x result = (p.FirstName + " " + p.LastName); took: 393ms - 1150148 ticks
1000000 x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(" "); sb.Append(random.Next().ToString()); result = sb.ToString(); took: 405ms - 1185816 ticks

1
답변에 StringBuilder 및 샘플 출력 추가
mikeschuld

string.Format여기서 사용하는 것이 작은 성능의 가치가 있다는 것을 알았 습니다. 구조적으로 형식을 더 쉽게 변경할 수 있다는 점에서 더 좋습니다. 그러나 stringbuilder는 실제로 요점을 알지 못합니다. 여기에있는 다른 모든 스레드는 문자열을 연결하는 대신 Stringbuilder를 사용해야한다고 말합니다. 장점은 무엇입니까? 이 벤치 마크가 입증 하듯이 속도가 빠르지 않습니다.
roryok

22

불쌍한 번역가들을 동정하십시오

응용 프로그램이 영어로 유지되는 것을 알고 있다면 괜찮습니다. 시계 눈금을 저장하십시오. 그러나 많은 문화권에서는 일반적으로 주소와 같이 성 이름이 표시됩니다.

따라서 string.Format()특히 영어가 모국어가 아닌 곳으로 응용 프로그램을 사용하려는 경우을 사용하십시오.


2
어떻게 것이 string.Format()다른 문화에서 다른 행동? 여전히 이름과 성을 인쇄하지 않습니까? 두 상황에서 서로 다른 문화를 고려해야 할 것 같습니다. 여기에 뭔가 빠진 것 같습니다.
Broots Waymb

2
@DangerZone에 동의합니다. string.Format()주소에 이름을 사용하고 있다는 것을 어떻게 알 수 있습니까? 경우 string.Format()교환 {0} {1}문화를 기반으로, 나는 그것이 깨진 고려할 것입니다.
Alex McMillan

2
제레미가 다른 나라를 지원하는 시나리오에서 언어 문자열로 형식 문자열 자체를 추출하는 것이 적절할 것이라고 생각했습니다. 대부분의 국가에서 해당 문자열은 "{0} {1}"이지만 성을 먼저 사용하는 국가 (예 : 헝가리, 홍콩, 캄보디아, 중국, 일본, 한국, 마다가스카르, 대만, 베트남 및 인도의 일부) 해당 문자열은 대신 "{1} {0}"입니다.
Richard J Foster

과연. 또는 더 미묘하게 사람의 속성으로 형식 문자열을 추가하십시오. 예를 들어, 이름을 따서 성을 갖고 싶지만 동료 Beng은 그렇지 않습니다.
Jeremy McGee

14

100,000 회 이상의 반복 결과는 다음과 같습니다.

Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took (avg): 0ms - 689 ticks
Console.WriteLine(p.FirstName + " " + p.LastName); took (avg): 0ms - 683 ticks

벤치 코드는 다음과 같습니다.

Stopwatch s = new Stopwatch();

var p = new { FirstName = "Bill", LastName = "Gates" };

//First print to remove the initial cost
Console.WriteLine(p.FirstName + " " + p.LastName);
Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

int n = 100000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

for (var i = 0; i < n; i++)
{
    s.Start();
    Console.WriteLine(p.FirstName + " " + p.LastName);
    s.Stop();
    cElapsedMilliseconds += s.ElapsedMilliseconds;
    cElapsedTicks += s.ElapsedTicks;
    s.Reset();
    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    fElapsedMilliseconds += s.ElapsedMilliseconds;
    fElapsedTicks += s.ElapsedTicks;
    s.Reset();
}

Console.Clear();

Console.WriteLine("Console.WriteLine(\"{0} {1}\", p.FirstName, p.LastName); took (avg): " + (fElapsedMilliseconds / n) + "ms - " + (fElapsedTicks / n) + " ticks");
Console.WriteLine("Console.WriteLine(p.FirstName + \" \" + p.LastName); took (avg): " + (cElapsedMilliseconds / n) + "ms - " + (cElapsedTicks / n) + " ticks");

그래서 누구의 답을 답으로 표시할지 모르겠습니다. :)


이 답변의 배경이 왜 파란색입니까?
user88637 2016 년

@yossi 응답자가
asker

9

문자열을 연결하는 것은 간단한 시나리오에서 좋습니다. LastName, FirstName과 같이 복잡한 것보다 복잡합니다. 이 형식을 사용하면 코드를 읽을 때 문자열의 최종 구조가 무엇인지 알 수 있습니다. 연결하면 최종 결과를 즉시 식별하는 것이 거의 불가능합니다 (이와 같은 매우 간단한 예는 제외).

장기적으로 의미하는 것은 문자열 형식을 변경하기 위해 다시 돌아올 때 서식 문자열을 팝업하여 약간 조정하거나 눈썹에 주름을 잡고 움직일 수 있다는 것입니다. 텍스트와 혼합 된 속성 접근 자의 종류로 인해 문제가 발생할 가능성이 높습니다.

.NET 3.5를 사용하는 경우 다음 과 같은 확장 방법을 사용하여 다음 과 같이 커프 구문에서 쉽게 이동할 수 있습니다.

string str = "{0} {1} is my friend. {3}, {2} is my boss.".FormatWith(prop1,prop2,prop3,prop4);

마지막으로 응용 프로그램이 복잡 해짐에 따라 응용 프로그램에서 문자열을 제대로 유지 관리하기 위해 문자열을 리소스 파일로 이동하여 지역화하거나 단순히 정적 도우미로 옮기려고 할 수 있습니다. 일관되게 형식을 사용하면 훨씬 쉽게 달성 할 수 있으며 코드를 간단히 리팩터링하여 다음과 같은 것을 사용할 수 있습니다

string name = String.Format(ApplicationStrings.General.InformalUserNameFormat,this.FirstName,this.LastName);

7

매우 간단한 조작을 위해 연결을 사용하지만 일단 2 또는 3 요소를 초과하면 Format이 더 적합한 IMO가됩니다.

String.Format을 선호하는 또 다른 이유는 .NET 문자열을 변경할 수 없기 때문에 임시 / 중간 복사본이 줄어든다는 것입니다.


6

스타일 선호도를 완전히 이해하고 부분적으로 내 선호도에 따라 첫 번째 답변에 대한 연결을 선택했지만 내 결정의 일부는 연결이 더 빠를 것이라는 생각에 기초했습니다. 그래서 호기심으로, 나는 그것을 테스트했고 결과는 특히 작은 문자열에 대해 놀랍습니다.

다음 코드를 사용하십시오.

    System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch();

    var p = new { FirstName = "Bill", LastName = "Gates" };

    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    Console.WriteLine("Console.WriteLine(\"{0} {1}\", p.FirstName, p.LastName); took: " + s.ElapsedMilliseconds + "ms - " + s.ElapsedTicks + " ticks");

    s.Reset();
    s.Start();
    Console.WriteLine(p.FirstName + " " + p.LastName);
    s.Stop();

    Console.WriteLine("Console.WriteLine(p.FirstName + \" \" + p.LastName); took: " + s.ElapsedMilliseconds + "ms - " + s.ElapsedTicks + " ticks");

나는 다음과 같은 결과를 얻었다 :

Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 2ms - 7280 ticks
Bill Gates
Console.WriteLine(p.FirstName + " " + p.LastName); took: 0ms - 67 ticks

포맷 방법을 사용하면 100 배 이상 느립니다 !! 연결은 1ms로 등록되지 않았으므로 타이머 틱도 출력합니다.


2
물론 측정을 수행하려면 작업을 두 번 이상 수행해야합니다.
erikkallen

2
그리고 질문의 범위를 벗어나기 때문에 Console.Writeline ()에 대한 호출을 잃습니까?
Aidanapword

스트링 빌더로 테스트 했습니까? ;)
niico

6

C # 6.0부터 시작하여 보간 된 문자열 을 사용하여 형식을 훨씬 단순화 할 수 있습니다.

var name = "Bill";
var surname = "Gates";
MessageBox.Show($"Welcome to the show, {name} {surname}!");

보간 된 문자열 표현식은 표현식이 포함 된 템플릿 문자열처럼 보입니다. 보간 된 문자열 표현식은 포함 된 표현식을 표현식 결과의 ToString 표현으로 대체하여 문자열을 작성합니다.

보간 된 문자열은 String.Format과 성능이 비슷하지만 값과식이 인라인으로 삽입되므로 가독성과 구문이 향상되었습니다.

문자열 보간에 관한 이 dotnetperls 기사 를 참조하십시오 .

문자열 형식을 지정하는 기본 방법을 찾고 있다면 가독성과 성능 측면에서 의미가 있습니다 (마이크로 초가 특정 사용 사례에서 차이를 만드는 경우는 제외).


5

기본 문자열 연결의 경우 일반적으로 읽기 쉽고 더 간단한 두 번째 스타일을 사용합니다. 그러나 더 복잡한 문자열 조합을 수행하는 경우 일반적으로 String.Format을 선택합니다.

String.Format은 많은 따옴표와 더하기를 저장합니다 ...

Console.WriteLine("User {0} accessed {1} on {2}.", user.Name, fileName, timestamp);
vs
Console.WriteLine("User " + user.Name + " accessed " + fileName + " on " + timestamp + ".");

소수의 문자 만 저장되었지만이 예에서는 형식이 훨씬 깔끔하다고 생각합니다.


5

더 나은 테스트는 Perfmon 및 CLR 메모리 카운터를 사용하여 메모리를 감시하는 것입니다. 내 이해는 문자열을 연결하는 대신 String.Format을 사용하려는 전체 이유는 문자열을 변경할 수 없기 때문에 가비지 수집기에 다음 문자열에서 다시 사용해야 할 임시 문자열을 불필요하게 부담한다는 것입니다.

StringBuilder 및 String.Format은 잠재적으로 느리지 만 메모리 효율성이 높습니다.

문자열 연결에있어 나쁜 점은 무엇입니까?


나는 동의한다; 모든 문자열 작업은 문자열의 새 복사본을 만듭니다. 가비지 수집기에서 모든 메모리를 조만간 회수합니다. 따라서 많은 문자열을 할당하면 나중에 물릴 수 있습니다.
Marnix van Valen

5

일반적으로 나는 문자열이 길어지면 훨씬 쉽게 읽을 수 있기 때문에 전자를 선호합니다.

다른 장점은 마지막 문자열을 Console.Write 메서드에 전달하기 전에 실제로 2 개의 문자열 생성 문을 수행하기 때문에 성능 중 하나라고 생각합니다. String.Format은 내가 믿는 표지 아래에서 StringBuilder를 사용하므로 여러 연결을 피할 수 있습니다.

그러나 String.Format에 전달하는 매개 변수 (및 Console.Write와 같은 다른 메서드)가 값 형식이면 전달되기 전에 상자에 넣어 져서 자체 성능 저하를 일으킬 수 있습니다. 여기에 블로그 게시물이 있습니다 .


1
해당 블로그 게시물은 현재 jeffbarnes.net/blog/post/2006/08/08/…에 있습니다. 편집 할 담당자가 충분하지 않습니다.
Richard Slater

5

2015 년 8 월 19 일부터 1 주일이 지나면이 질문은 정확히 7 세가됩니다. 이제 더 좋은 방법이 있습니다. 연결 문자열과 비교하여 성능 테스트를 수행하지 않았으므로 유지 관리 측면에서 좋습니다 (그러나 요즘 문제는 몇 밀리 초 차이입니까?). C # 6.0 을 사용하는 새로운 방법 :

var p = new { FirstName = "Bill", LastName = "Gates" };
var fullname = $"{p.FirstName} {p.LastName}";

이 새로운 기능은 IMO 보다 우수 하며 실제로 는 일부 요소에 따라 값이 달라지는 쿼리 문자열을 작성하는 코드가 있으므로 실제로 더 좋습니다 . 6 개의 인수가있는 하나의 쿼리 문자열을 상상해보십시오. 예를 들어 다음을 수행하는 대신

var qs = string.Format("q1={0}&q2={1}&q3={2}&q4={3}&q5={4}&q6={5}", 
    someVar, anotherVarWithLongName, var3, var4, var5, var6)

다음과 같이 쓸 수 있으며 읽기가 더 쉽습니다.

var qs=$"q1={someVar}&q2={anotherVarWithLongName}&q3={var3}&q4={var4}&q5={var5}&q6={var6}";

실제로, C # 6.0의 새로운 방법은 적어도 가독성 관점에서 이전의 대안보다 낫습니다.
Philippe

맞습니다. 또한 객체를 원하는 곳에 직접 넣을 것이기 때문에 어떤 객체가 어떤 인덱스 (자리 표시 자)로 이동하는지에 대해 걱정할 필요가 없으므로 더욱 안전합니다.
폰 v.

BTW, 실제로는 적어도 Roslyn과 함께 Format을 호출합니다.
Philippe

BTW,이 포스터가 언급하는 것을 "문자열 보간"이라고하며이 스레드의 다른 곳에서 해결됩니다.
CShark

4
  1. 서식은 ".NET"방식입니다. 특정 리팩토링 도구 (하나의 리 팩터!)는 서식 스타일을 사용하기 위해 concat 스타일 코드를 리팩토링하도록 제안합니다.
  2. 포맷팅은 컴파일러에 최적화하기가 더 쉽습니다 (두 번째는 아마도 'Concat'방법을 사용하도록 리팩토링 될 것입니다).
  3. 형식은 일반적으로 읽기가 더 명확합니다 (특히 "팬시"형식).
  4. 서식은 모든 변수에서 '.ToString'에 대한 암시 적 호출을 의미하므로 가독성에 좋습니다.
  5. "Effective C #"에 따르면 .NET 'WriteLine'및 'Format'구현이 엉망이되어 모든 값 유형 (잘못된)을 자동 상자에 넣습니다. "효과적인 C #"은 IMHO가 가짜 인 '.ToString'호출을 명시 적으로 수행하도록 권장합니다 ( Jeff의 게시 참조 )
  6. 현재 컴파일러에서 형식 유형 힌트를 확인하지 않아 런타임 오류가 발생합니다. 그러나 이것은 향후 버전에서 수정 될 수 있습니다.

4

가독성을 기준으로 선택합니다. 변수 주위에 텍스트가있을 때 형식 옵션을 선호합니다. 이 예에서 :

Console.WriteLine("User {0} accessed {1} on {2}.", 
                   user.Name, fileName, timestamp);

변수 이름이 없어도 의미를 이해하지만 concat은 따옴표와 + 기호로 어수선하고 내 눈을 혼란스럽게합니다.

Console.WriteLine("User " + user.Name + " accessed " + fileName + 
                  " on " + timestamp + ".");

(나는 그것을 좋아하기 때문에 Mike의 예를 빌렸다)

형식 문자열이 변수 이름이없는 의미가 없다면 concat을 사용해야합니다.

   Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

형식 옵션을 사용하면 변수 이름을 읽고 해당 숫자에 매핑합니다. concat 옵션은 필요하지 않습니다. 나는 여전히 따옴표와 + 기호로 혼란 스럽지만 대안은 더 나쁩니다. 루비?

   Console.WriteLine(p.FirstName + " " + p.LastName);

성능면에서 format은 문자열을 구문 분석 해야하기 때문에 format 옵션이 concat보다 느릴 것으로 예상합니다 . 이런 종류의 명령을 최적화해야한다는 것을 기억하지는 않지만 그렇게하면 and과 string같은 방법을 살펴볼 것 입니다.Concat()Join()

형식의 다른 장점은 형식 문자열을 구성 파일에 넣을 수 있다는 것입니다. 오류 메시지와 UI 텍스트에 매우 편리합니다.


4

String.Format을 사용하지만 리소스 파일에 형식 문자열이 있으므로 다른 언어로 현지화 할 수 있습니다. 간단한 문자열 연결을 사용하면 그렇게 할 수 없습니다. 분명히 해당 문자열을 현지화 할 필요가 없다면 이것은 생각할 이유가 아닙니다. 실제로 문자열이 무엇인지에 달려 있습니다.

사용자에게 표시 될 경우 String.Format을 사용하여 필요한 경우 현지화 할 수 있습니다 .FxCop 은 다음 과 같은 경우에 대비하여 철자를 검사합니다. :)

숫자 또는 문자열이 아닌 다른 것들 (예 : 날짜)이 포함되어 있으면 String.Format을 사용 하여 형식을 보다 잘 제어 할 수 있습니다.

SQL과 같은 쿼리를 작성하는 경우 Linq를 사용 합니다.

루프 내에서 문자열을 연결하는 경우 성능 문제를 피하기 위해 StringBuilder 를 사용 합니다.

사용자가 볼 수없는 출력에 대해 성능에 영향을 미치지 않으면 String.Format을 사용합니다. 어쨌든 그것을 사용하는 습관이 있고 그냥 익숙합니다.)


3

쉽게 읽을 수 있어야하는 무언가를 다루는 경우 (그리고 이것은 대부분의 코드 임), 연산자 오버로드 버전을 고수합니다.

  • 코드는 수백만 번 실행되어야합니다
  • 당신은 많은 concats를하고 있습니다 (4 이상은 톤입니다)
  • 코드는 Compact Framework를 대상으로합니다.

이러한 상황 중 두 가지 이상에서는 StringBuilder를 대신 사용합니다.


3

결과를 지역화하려는 경우 다른 자연 언어의 데이터 순서가 동일하지 않을 수 있으므로 String.Format이 필수적입니다.


2

나는 이것이 출력이 얼마나 복잡한 지에 크게 달려 있다고 생각합니다. 당시에 가장 적합한 시나리오를 선택하는 경향이 있습니다.

작업에 따라 올바른 도구를 선택하십시오.


2

나는 두 번째도 선호하지만 현재로서는 그 입장을 뒷받침하는 합리적인 논쟁이 없습니다.


2

좋은 것!

방금 추가 한

        s.Start();
        for (var i = 0; i < n; i++)
            result = string.Concat(p.FirstName, " ", p.LastName);
        s.Stop();
        ceElapsedMilliseconds = s.ElapsedMilliseconds;
        ceElapsedTicks = s.ElapsedTicks;
        s.Reset();

그리고 그것은 더 빠릅니다 (문자열. 추측은 두 예제 모두에서 호출되지만 첫 번째 것은 일종의 번역이 필요합니다).

1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 249ms - 3571621 ticks
1000000 x result = (p.FirstName + " " + p.LastName); took: 65ms - 944948 ticks
1000000 x result = string.Concat(p.FirstName, " ", p.LastName); took: 54ms - 780524 ticks

2
연산자 기반 문자열 연결이 컴파일러에서 호출로 변환되기 때문에 정확히 같은 시간이 걸립니다 string.Concat(...). 컴파일 중에 수행되므로 런타임 성능에 영향을 미치지 않습니다. 테스트를 여러 번 실행하거나 더 큰 테스트 샘플에서 실행하면 테스트가 동일하다는 것을 알 수 있습니다.
Allon Guralnek

2

여기에있는 답변이 모든 것을 다룰 수는 없다고 생각하기 때문에 여기에 약간의 추가를하고 싶습니다.

Console.WriteLine(string format, params object[] pars)전화 string.Format. '+'는 문자열 연결을 의미합니다. 나는 이것이 항상 스타일과 관련이 있다고 생각하지 않습니다. 나는 상황에 따라 두 가지 스타일을 혼합하는 경향이 있습니다.

짧은 답변

당신이 직면하고있는 결정은 문자열 할당과 관련이 있습니다. 간단하게 해보도록하겠습니다.

당신이 가지고 있다고

string s = a + "foo" + b;

이를 실행하면 다음과 같이 평가됩니다.

string tmp1 = a;
string tmp2 = "foo" 
string tmp3 = concat(tmp1, tmp2);
string tmp4 = b;
string s = concat(tmp3, tmp4);

tmp여기서는 실제로 로컬 변수가 아니지만 JIT의 임시 변수입니다 (IL 스택에서 푸시 됨). ldstr리터럴의 경우 IL에서 와 같이 스택에서 문자열을 푸시하면 스택 의 문자열 포인터에 대한 참조를 넣습니다.

concat두 문자열을 모두 포함하는 문자열 참조가 없기 때문에이 참조 를 호출하는 순간 문제가됩니다. 이는 .NET이 새로운 메모리 블록을 할당 한 다음 두 문자열로 채워야한다는 것을 의미합니다. 이것이 문제인 이유는 할당이 상대적으로 비싸기 때문입니다.

질문을 다음과 같이 변경하는 방법 : concat작업 수를 어떻게 줄일 수 있습니까?

따라서 대략적인 대답은 다음과 같습니다. string.Format> 1 concats의 경우 '+'는 1 concat에 적합합니다. 그리고 마이크로 퍼포먼스 최적화에 신경 쓰지 않는다면 string.Format일반적인 경우에는 잘 작동합니다.

문화에 관한 메모

그리고 문화라는 것이 있습니다 ...

string.FormatCultureInfo서식 에 사용할 수 있습니다 . 간단한 연산자 '+'는 현재 문화권을 사용합니다.

파일 형식과 f.ex를 작성하는 경우 특히 중요합니다. double문자열에 '추가'하는 값. 다른 컴퓨터에서는 string.Formatexplicit와 함께 사용하지 않으면 다른 문자열로 끝날 수 있습니다 CultureInfo.

F.ex. '.'를 변경하면 어떻게되는지 고려하십시오. 네덜란드어로 쉼표로 구분 된 값 파일을 쓰는 동안 ','의 경우 소수점 구분 기호는 쉼표이므로 사용자는 '재미있는'놀라움을 얻을 수 있습니다.

더 자세한 답변

사전에 정확한 문자열 크기를 모르는 경우 사용하는 버퍼를 전체 화하기 위해 이와 같은 정책을 사용하는 것이 가장 좋습니다. 여유 공간이 먼저 채워진 후 데이터가 복사됩니다.

성장이란 새로운 메모리 블록을 할당하고 이전 데이터를 새로운 버퍼에 복사하는 것을 의미합니다. 그런 다음 이전 메모리 블록을 해제 할 수 있습니다. 이 시점에서 결론을 얻습니다. 성장은 비용이 많이 드는 작업입니다.

이를 수행하는 가장 실용적인 방법은 초과 할당 정책을 사용하는 것입니다. 가장 일반적인 정책은 2의 거듭 제곱으로 버퍼를 전체적으로 할당하는 것입니다. 물론 128 문자가 필요하다는 것을 이미 알고 있다면 1,2,4,8에서 성장하는 것이 이치에 맞지 않으므로 조금 더 똑똑해야합니다. ) 그러나 당신은 그림을 얻는다. 이 정책은 위에서 설명한 값 비싼 작업이 너무 많이 필요하지 않도록합니다.

StringBuilder기본적으로 기본 버퍼를 2의 거듭 제곱으로 분류하는 클래스입니다. 후드 아래에서 string.Format사용 StringBuilder합니다.

이것은 당신의 결정을 전체적인 할당과 추가 (복수) (배양없이) 또는 단지 할당과 추가 사이의 기본적인 절충점으로 만듭니다.


1

개인적으로 두 번째는 사용하는 모든 것이 직접 순서대로 출력됩니다. 첫 번째로는 {0} 및 {1}을 적절한 var와 일치시켜야하므로 쉽게 엉망이됩니다.

적어도 C ++ sprintf만큼 나쁘지는 않습니다. 여기서 변수 유형이 잘못되면 모든 것이 폭발 할 것입니다.

또한 두 번째는 모두 인라인이며 모든 {0} 항목을 검색하고 바꿀 필요가 없으므로 후자는 더 빠를 것입니다.하지만 확실하지 않습니다.


1

텍스트와 섞인 많은 변수가있을 때 나에게 읽기 쉬운 것처럼 보이기 때문에 실제로 첫 번째 것을 좋아합니다. 또한 string.Format ()을 사용할 때 따옴표를 다루는 것이 더 쉽습니다. 다음은 문자열 연결에 대한 적절한 분석 입니다.


1

나는 항상 string.Format () 경로를 갔다. Nathan의 예제와 같은 변수에 형식을 저장할 수 있다는 것이 큰 장점입니다. 어떤 경우에는 변수를 추가 할 수 있지만 하나 이상의 변수가 연결되어 있으면 서식을 사용하도록 리팩터링합니다.


1

아, 그리고 완전성을 위해 다음은 정상적인 연결보다 몇 가지 틱입니다.

Console.WriteLine(String.Concat(p.FirstName," ",p.LastName));

1

첫 번째 (형식)가 나에게 더 좋습니다. 더 읽기 쉽고 임시 문자열 객체를 추가로 만들지 않습니다.


1

StringBuilder가 이러한 테스트로 어디에 서 있는지 궁금했습니다. 아래 결과 ...

class Program {
   static void Main(string[] args) {

      var p = new { FirstName = "Bill", LastName = "Gates" };

      var tests = new[] {
         new { Name = "Concat", Action = new Action(delegate() { string x = p.FirstName + " " + p.LastName; }) },
         new { Name = "Format", Action = new Action(delegate() { string x = string.Format("{0} {1}", p.FirstName, p.LastName); }) },
         new { Name = "StringBuilder", Action = new Action(delegate() {
            StringBuilder sb = new StringBuilder();
            sb.Append(p.FirstName);
            sb.Append(" ");
            sb.Append(p.LastName);
            string x = sb.ToString();
         }) }
      };

      var Watch = new Stopwatch();
      foreach (var t in tests) {
         for (int i = 0; i < 5; i++) {
            Watch.Reset();
            long Elapsed = ElapsedTicks(t.Action, Watch, 10000);
            Console.WriteLine(string.Format("{0}: {1} ticks", t.Name, Elapsed.ToString()));
         }
      }
   }

   public static long ElapsedTicks(Action ActionDelg, Stopwatch Watch, int Iterations) {
      Watch.Start();
      for (int i = 0; i < Iterations; i++) {
         ActionDelg();
      }
      Watch.Stop();
      return Watch.ElapsedTicks / Iterations;
   }
}

결과 :

Concat : 406 틱
Concat : 356 틱
Concat : 411 틱
Concat : 299 틱
연결 : 266 틱
체재 : 5269 진드기
체재 : 954 진드기
체재 : 1004 진드기
체재 : 984 진드기
체재 : 974 틱
StringBuilder : 629 틱
StringBuilder : 484 틱
StringBuilder : 482 틱
StringBuilder : 508 틱
StringBuilder : 504 틱

1

MCSD 준비 자료에 따르면 마이크로 소프트는 매우 적은 수의 연결 (아마도 2 ~ 4)을 다룰 때 + 연산자를 사용하는 것이 좋습니다. 나는 왜 그런지 잘 모르겠지만 고려해야 할 사항입니다.

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