대 입문이 왜 값을 반환합니까?


126

이것은 허용됩니다 :

int a, b, c;
a = b = c = 16;

string s = null;
while ((s = "Hello") != null) ;

나의 이해에, 할당 s = ”Hello”;만의 원인이해야 “Hello”할당 할 수 s있지만, 작업이 어떤 값을 반환하지 않아야합니다. 그것이 사실이라면, 아무것도 비교되지 ((s = "Hello") != null)않기 때문에 오류가 발생합니다 null.

대 입문이 값을 반환하도록 허용하는 이유는 무엇입니까?


44
참고로이 동작은 C # 사양에 정의되어 있습니다 . "단순 할당 식의 결과는 왼쪽 피연산자에 할당 된 값입니다. 결과는 왼쪽 피연산자와 동일한 유형을 가지며 항상 값으로 분류됩니다."
Michael Petrotta

1
@Skurmedel 나는 downvoter가 아니에요, 나는 당신에 동의합니다. 그러나 수사적인 질문으로 생각 되었기 때문에?
jsmith

파스칼 / 델피 할당에서 : =는 아무것도 반환하지 않습니다. 난 싫어
Andrey

20
C 지점의 언어에 대한 표준입니다. 과제는 표현식입니다.
한스 패넌트

"할당 ...은"Hello "만 s에 할당해야하지만 작업이 값을 반환하지 않아야합니다."– 왜? "지정 문이 값을 리턴하도록 허용하는 이유는 무엇입니까?" -자신의 예제에서 알 수 있듯이 유용하기 때문입니다. (두 번째 예는 어리석지 만 while ((s = GetWord()) != null) Process(s);그렇지 않습니다).
짐 발터 (Jim Balter)

답변:


160

내 이해에 따르면 과제 s = "Hello"; "Hello"만 s에 할당해야하지만 작업이 값을 반환하지 않아야합니다.

이해가 100 % 잘못되었습니다. 왜이 거짓을 믿는지 설명 할 수 있습니까?

대 입문이 값을 반환하도록 허용하는 이유는 무엇입니까?

우선 과제 은 값을 생성하지 않습니다. 대입 값을 생성합니다. 할당 표현은 법적 진술입니다. C #에는 합법적 인 표현 중 소수만 있습니다. 표현을 기다리고 있습니다. 인스턴스 생성, 증분, 감소, 호출 및 할당 표현식은 명령문이 필요한 곳에서 사용될 수 있습니다.

C #에는 일종의 값을 생성하지 않는 표현식, 즉 void를 반환하는 유형의 무언가 호출이 있습니다. 또는 결과 값이없는 작업을 대기하는 등의 다른 모든 유형의 표현식은 값 또는 변수 또는 참조 또는 속성 액세스 또는 이벤트 액세스 등을 생성합니다.

진술로 합법적 인 모든 표현은 부작용에 유용합니다. . 이것이 핵심 통찰력이며, 아마도 과제가 표현이 아니라 진술이어야한다는 직관의 원인 일 것입니다. 이상적으로는 명령문마다 정확히 하나의 부작용이 있고 표현식에는 부작용이 없습니다. 그것은 이다 사이드 행하는 코드가 전혀 발현 맥락에서 사용될 수있는 비트 홀수.

이 기능을 허용하는 이유는 (1) 자주 사용하고 (2) C와 같은 언어에서는 관용적이기 때문입니다.

이 질문이 왜 C와 같은 언어로 관용적입니까?

불행히도 Dennis Ritchie는 더 이상 요청할 수 없지만 할당은 거의 항상 레지스터에 할당 된 값 보다 거의 남습니다 . C는 매우 "기계와 가까운"종류의 언어입니다. C의 디자인에 따라 기본적으로 "방금 할당 한 값을 계속 사용합니다"라는 언어 기능이 있다는 것이 그럴듯 해 보입니다. 이 기능을위한 코드 생성기를 작성하는 것은 매우 쉽습니다. 할당 된 값을 저장 한 레지스터를 계속 사용하면됩니다.


1
값 반환 아무것도 (심지어 인스턴스 건설 표현식으로 간주됩니다) 알고 아니었다
user437291

9
@ user437291 : 인스턴스 생성이 표현식이 아닌 경우 생성 된 객체를 사용하여 작업을 수행 할 수 없습니다. 객체를 할당 할 수없고 메소드에 전달할 수 없으며 메소드를 호출 할 수 없습니다. 그 위에. 꽤 쓸모 없을까요?
Timwi

1
변수를 변경하는 것은 실제로 할당 연산자의 부작용 일뿐입니다. 식을 만들면 기본 목적으로 값이 생성됩니다.
mk12

2
"당신의 이해가 100 % 잘못되었습니다." - 영어의 OP의 사용이 가장 큰 것은 아니지만 그 / 그녀는 "이해하는 것은"무엇에 관한 것입니다 해야 "100 % 잘못"할 수있는 것보다이 물건의 종류되지 않도록, 그 의견 만드는 경우가 . 일부 언어 설계자들은 OP의 "해야한다"에 동의하고, 과제물에 가치가 없거나 하위 표현을 금지합니다. 나는 그것이 =/ ==오타에 대한 과잉 반응이라고 생각합니다. 이 값 =은 괄호로 묶이지 않으면 값을 사용하지 못하게함으로써 쉽게 해결할 수 있습니다 . 예를 들어 if ((x = y))또는 if ((x = y) == true)허용되지만 허용 if (x = y)되지 않습니다.

"값을 반환하는 것은 표현식으로 간주된다는 것을 인식하지 못했습니다"-흠 ... 값을 반환하는 모든 것은 표현식으로 간주됩니다. 둘은 사실상 동의어입니다.
Jim Balter

44

답변을 제공하지 않았습니까? 그것은 당신이 언급 한 종류의 구성을 정확하게 가능하게하는 것입니다.

대입 연산자의이 속성이 사용되는 일반적인 경우는 파일에서 행을 읽는 것입니다.

string line;
while ((line = streamReader.ReadLine()) != null)
    // ...

1
+1 이것은이 구성의 가장 실용적인 용도 중 하나입니다
Mike Burton

11
난 정말 간단한 속성 이니셜 라이저를 위해 그것을 좋아하지만, 가독성을 위해 "간단한"낮은 줄에주의를 기울여야합니다 return _myBackingField ?? (_myBackingField = CalculateBackingField());.
Tom Mayfield

34

할당 표현식을 가장 좋아하는 방식은 느리게 초기화 된 속성입니다.

private string _name;
public string Name
{
    get { return _name ?? (_name = ExpensiveNameGeneratorMethod()); }
}

5
그래서 많은 사람들이 ??=구문 을 제안한 것 입니다.
구성자

27

예를 들어, 다음과 같이 과제를 연결할 수 있습니다.

a = b = c = 16;

다른 경우에는 단일 표현식으로 결과를 지정하고 확인할 수 있습니다.

while ((s = foo.getSomeString()) != null) { /* ... */ }

둘 다 모호한 이유 일 수 있지만 이러한 구성을 좋아하는 사람들이 분명히 있습니다.


5
+1 체이닝은 중요한 기능은 아니지만 많은 일이 일어나지 않을 때 "작동"하는 것이 좋습니다.
Mike Burton

연쇄 력도 +1; 나는 항상 "표현식 반환 값"의 자연스러운 부작용보다는 언어에 의해 처리 된 특별한 경우라고 생각했습니다.
Caleb Bell

2
또한 반환에 할당을 사용할 수 있습니다 :return (HttpContext.Current.Items["x"] = myvar);
Serge Shultz

14

이 기능이 필요한 명령문 을 올바르게 사용 하기 위해 이미 언급 한 이유 (할당 루프 내 설정 및 테스트 등) 외에는 using이 기능이 필요합니다.

using (Font font3 = new Font("Arial", 10.0f))
{
    // Use font3.
}

MSDN은 일회용 개체를 using 문 외부에 선언하지 않는 것이 좋습니다. 폐기 된 후에도 범위를 유지하기 때문입니다 (MSDN 기사 링크 참조).


4
이것이 실제로 할당 체인이라고 생각하지 않습니다.
kenny

1
@kenny : 어 ... 아뇨.하지만 한 번도 주장한 적이 없습니다. 내가 말했듯이, 할당 체인을 포함하여 이미 언급 한 이유를 제외 하고는 할당 연산자가 할당 연산의 결과를 반환한다는 사실이 아니라면 using 문을 사용하는 것이 훨씬 더 어려울 것입니다. 이것은 실제로 할당 체인과 관련이 없습니다.
Martin Törnwall

1
그러나 Eric이 위에서 언급 한 것처럼 "할당 진술은 값을 반환하지 않습니다". 이것은 실제로 using 문의 언어 구문 일 뿐이며 using 명령문에서 "할당 식"을 사용할 수 없다는 것을 증명합니다.
kenny

@ kenny : 사과, 내 용어는 분명히 잘못되었습니다. 언어를 사용하여 값을 반환하는 할당 식을 일반적으로 지원하지 않고도 using 문을 구현할 수 있다는 것을 알고 있습니다. 그것은 내 눈에 아주 일관되지 않을 것입니다.
Martin Törnwall

2
@kenny : 실제로, 정의 및 할당 문 또는 의 표현식을 사용할 수 using있습니다. 이 모든 법적 있습니다 : using (X x = new X()), using (x = new X()), using (x). 그러나이 예제에서 using 문의 내용은 값을 반환하는 할당에 전혀 의존하지 않는 특수 구문입니다 Font font3 = new Font("Arial", 10.0f).식이 아니므로식이 필요한 곳에서는 유효하지 않습니다.
구성자

10

Eric Lippert가 자신의 답변에서 한 특정 요점에 대해 자세히 설명하고 다른 사람이 전혀 다루지 않은 특정 상황에 주목하고 싶습니다. 에릭은 말했다 :

[...] 할당은 거의 항상 레지스터에 할당 된 값 뒤에 남습니다.

과제는 항상 왼쪽 피연산자에 할당하려고 시도한 값을 남겨두고 싶습니다. "거의 항상"만이 아닙니다. 그러나이 문제를 문서에서 언급하지 않았기 때문에 모르겠습니다. 이론적으로 "뒤로"남겨두고 왼쪽 피연산자를 다시 평가하지 않는 매우 효과적인 구현 절차 일 수 있지만 효율적입니까?

이 스레드의 답변으로 지금까지 구성된 모든 예제에 대해 '효율적'입니다. 그러나 get 및 set 접근자를 사용하는 속성 및 인덱서의 경우 효율적입니까? 전혀. 이 코드를 고려하십시오.

class Test
{
    public bool MyProperty { get { return true; } set { ; } }
}

여기에 개인 변수의 래퍼가 아닌 속성이 있습니다. 부름을받을 때마다, 그는 자신의 가치를 설정하려고 할 때마다 아무 것도하지 않을 것입니다. 그러므로이 재산이 평가 될 때마다 그는 진실 할 것이다. 어떻게되는지 보자 :

Test test = new Test();

if ((test.MyProperty = false) == true)
    Console.WriteLine("Please print this text.");

else
    Console.WriteLine("Unexpected!!");

그것이 무엇을 인쇄하는지 맞춰보세요? 인쇄합니다 Unexpected!!. 결과적으로, set 접근자는 실제로 호출되지만 아무것도하지 않습니다. 그러나 그 후에 get 접근자는 전혀 호출되지 않습니다. 배정은 단순히 false우리가 재산에 배정하려고 한 가치 를 남깁니다 . 그리고이 false값은 if 문이 평가하는 것입니다.

이 문제를 연구 할 수 있는 실제 사례로 마무리 하겠습니다. List<string>내 클래스가 개인 변수로 가지고 있는 컬렉션 ( ) 의 편리한 래퍼 인덱서를 만들었습니다 .

인덱서로 전송 된 매개 변수는 문자열이며 내 콜렉션의 값으로 처리되었습니다. get 접근자는 해당 값이 목록에 존재하는지 여부 만 true 또는 false를 반환합니다. 따라서 get 접근자는 List<T>.Contains메소드 를 사용하는 또 다른 방법이었습니다 .

인덱서의 set 접근자가 문자열을 인수로 사용하여 호출되었고 오른쪽 피연산자가 bool 인 true경우 해당 매개 변수를 목록에 추가합니다. 그러나 동일한 매개 변수가 접근 자에게 전송되고 오른쪽 피연산자가 bool 인 false경우 대신 목록에서 요소를 삭제합니다. 따라서 세트 접근자는 List<T>.Add및 의 편리한 대안으로 사용되었습니다 List<T>.Remove.

게이트웨이로 구현 된 자체 로직으로 목록을 래핑하는 깔끔하고 컴팩트 한 "API"가 있다고 생각했습니다. 인덱서 만 사용하면 몇 번의 키 입력으로 많은 작업을 수행 할 수 있습니다. 예를 들어, 목록에 값을 추가하고 그 값이 있는지 어떻게 확인할 수 있습니까? 나는 이것이 유일한 코드 줄이라고 생각했다.

if (myObject["stringValue"] = true)
    ; // Set operation succeeded..!

그러나 이전 예제에서 알 수 있듯이 값이 실제로 목록에 있는지 확인 해야하는 get 접근자는 호출되지 않았습니다. true값은 항상 뒤에 효과적으로 내 get 접근에서 구현 한 어떤 논리를 파괴 남아 있었다.


매우 흥미로운 답변입니다. 그리고 테스트 중심 개발에 대해 긍정적으로 생각하게되었습니다.
Elliot

1
이것은 흥미롭지 만 에릭의 답변에 대한 의견이며 OP의 질문에 대한 답변이 아닙니다.
짐 발터 (Jim Balter)

시도 된 할당 표현식이 먼저 대상 특성의 값을 얻을 것으로 기대하지 않습니다. 변수가 변경 가능하고 유형이 일치하면 왜 이전 값이 중요합니까? 새로운 값을 설정하십시오. 나는 IF 테스트에서 과제의 팬이 아닙니다. 할당이 성공했거나 true로 강제 된 양의 정수 또는 부울을 할당했기 때문에 값이 true입니까? 구현에 관계없이 논리는 모호합니다.
다 보스

6

할당이 값을 반환하지 않으면 해당 라인 a = b = c = 16도 작동하지 않습니다.

또한 같은 것을 쓸 while ((s = readLine()) != null)수 있으면 때로는 유용 할 수 있습니다.

따라서 할당이 할당 된 값을 반환하게하는 이유는 이러한 작업을 수행하기위한 것입니다.


아무도 언급 한 단점은-일반적인 할당 대 비교 버그 'if (something = 1) {}'이 항상 true를 반환하지 않고 컴파일되지 않도록하기 위해 할당이 null을 반환 할 수없는 이유가 궁금합니다. 이제 다른 문제가 발생할 수 있습니다.
Jon

1
@Jon 그 버그 =는 괄호로 묶이지 않은 표현식에서 발생하는 것을 허용하지 않거나 경고함으로써 쉽게 예방할 수 있습니다 (if / while 문의 자체를 계산하지 않음). gcc는 이러한 경고를 제공하므로 컴파일 된 C / C ++ 프로그램에서 이러한 종류의 버그를 제거했습니다. 다른 컴파일러 작성자가 gcc의 이것과 다른 좋은 아이디어에 거의 관심을 기울이지 않은 것은 부끄러운 일입니다.
짐 발터 (Jim Balter)

4

파서가 해당 구문을 해석하는 방법을 오해하고 있다고 생각합니다. 할당은 먼저 평가 되고 결과는 NULL과 비교됩니다. 즉, 명령문은 다음과 같습니다.

s = "Hello"; //s now contains the value "Hello"
(s != null) //returns true.

다른 사람들이 지적했듯이 할당 결과는 할당 된 값입니다. 갖는 장점을 상상하기 어렵다

((s = "Hello") != null)

s = "Hello";
s != null;

동등 하지 않습니다 ...


두 줄이 동등하다는 실질적인 의미에서 옳지 만 과제가 값을 반환하지 않는다는 진술은 기술적으로 사실이 아닙니다. 내 이해는 할당의 결과가 C # 사양에 따른 값이며, 그 의미에서 1 + 2 + 3과 같은 표현식에서 덧셈 연산자를 말하는 것과 다르지 않습니다.
Steve Ellinger

3

주된 이유는 C ++ 및 C와 (의도적) 유사성이라고 생각합니다. 어설 션 연산자 (및 다른 많은 언어 구성)가 C ++ 대응 방식처럼 동작하도록 만드는 것은 놀랍지 않은 원칙을 따르고 다른 곱슬 머리에서 온 프로그래머는 대괄호 언어는 많은 생각을 들이지 않고도 사용할 수 있습니다. C ++ 프로그래머가 쉽게 선택할 수있는 것은 C #의 주요 디자인 목표 중 하나였습니다.


2

게시물에 포함하는 두 가지 이유 때문에
1) 그렇게 할 수 있습니다. a = b = c = 16
2) 과제가 성공했는지 테스트 할 수 있습니다. if ((s = openSomeHandle()) != null)


1

'a ++'또는 'printf ( "foo")'가 자체 포함 된 명령문으로 사용되거나 더 큰 표현식의 일부로 유용 할 수 있다는 사실은 C가 표현식 결과가 나타날 수도 있고 아닐 수도 있다는 가능성을 허용해야한다는 것을 의미합니다. 익숙한. 주어진 값을 유용하게 '반환'할 수있는 표현식이 그렇게 할 수 있다는 일반적인 개념이 있습니다. 할당 체인은 C에서 약간 "관심"이 될 수 있으며 문제의 모든 변수가 정확히 같은 유형을 갖지 않으면 C ++에서 더 흥미로울 수 있습니다. 이러한 사용법은 피하는 것이 가장 좋습니다.


1

또 다른 훌륭한 사용 사례는 항상 이것을 사용합니다.

var x = _myVariable ?? (_myVariable = GetVariable());
//for example: when used inside a loop, "GetVariable" will be called only once

0

여기에 답변에서 볼 수없는 추가 이점은 할당 구문이 산술을 기반으로한다는 것입니다.

이제 x = y = b = c = 2 + 3C 스타일 언어와 다른 산술을 의미합니다. 연산의 주장에, 우리는 상태 를 x, Y 등 같은지 및 C 스타일 언어는 그 명령의 브랜드는 이 실행 된 후에는, X, Y 등 동일.

이것은 산술과 코드 사이에 여전히 충분한 관계가 있으며, 그 이유가없는 한 산술의 자연스러운 것을 허용하지 않는 것이 합리적입니다. C 스타일 언어가 등호를 사용하여 가져온 또 다른 것은 등호 비교를 위해 ==를 사용하는 것입니다. 여기서 가장 오른쪽 ==이 값을 반환하기 때문에 이러한 종류의 체인은 불가능합니다.


@JimBalter 나는 5 년 안에 당신이 실제로 반론을 할 수 있을지 궁금하다.
Jon Hanna

@JimBalter 아니오, 두 번째 의견은 실제로 반론이며 건전한 의견입니다. 내 생각의 레토르트는 첫 번째 의견이 아니기 때문입니다. 산술 배울 때 아직도, 우리는 배울 a = b = c수단을 그 abc같은 일이다. C 형식의 언어를 배울 때 우리는 그 이후 내용 a = b = c, a그리고 b같은 것입니다 c. 내 대답 자체에서 알 수 있듯이 의미론에는 분명히 차이가 있지만 여전히 어린 시절에 나는 =과제에 사용 했지만 다른 언어로 프로그래밍하는 법을 배웠지 만 a = b = c이것이 나에게는 불합리한 것처럼 보였습니다 ...
Jon Hanna

그 언어는 =평등 비교에 사용되기 때문에 피할 수 없으므로 C 스타일 언어의 의미 a = b = c를 의미해야 a = b == c합니다. 나는 산술에 비유 할 수 있기 때문에 C에서 허용되는 체인이 훨씬 직관적이라는 것을 알았습니다.
Jon Hanna

0

많은 것들을 업데이트하고 변경 사항이 있는지 여부를 반환해야 할 때 할당 반환 값을 사용하는 것이 좋습니다.

bool hasChanged = false;

hasChanged |= thing.Property != (thing.Property = "Value");
hasChanged |= thing.AnotherProperty != (thing.AnotherProperty = 42);
hasChanged |= thing.OneMore != (thing.OneMore = "They get it");

return hasChanged;

그래도 조심하십시오. 이것을 짧게 할 수 있다고 생각할 수도 있습니다.

return thing.Property != (thing.Property = "Value") ||
    thing.AnotherProperty != (thing.AnotherProperty = 42) ||
    thing.OneMore != (thing.OneMore = "They get it");

그러나 이것은 실제로 첫 번째 사실을 찾은 후에 or 문 평가를 중단합니다. 이 경우 다른 첫 번째 값을 할당하면 후속 값 할당이 중지됩니다.

이 문제를 해결 하려면 https://dotnetfiddle.net/e05Rh8 을 참조 하십시오.

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