짧은 답변:
따옴표 연산자는 피연산자에 클로저 의미 를 유도 하는 연산자 입니다 . 상수는 값일뿐입니다.
따옴표와 상수는 의미 가 다르 므로 표현식 트리에서 다른 표현을 갖습니다 . 매우 다른 두 가지에 대해 동일한 표현을 갖는 것은 매우 혼란스럽고 버그가 발생하기 쉽습니다.
긴 대답 :
다음을 고려하세요:
(int s)=>(int t)=>s+t
외부 람다는 외부 람다의 매개 변수에 바인딩 된 덧셈기의 팩토리입니다.
이제 이것을 나중에 컴파일하고 실행할 표현식 트리로 표현하고 싶다고 가정합니다. 표현 트리의 본문은 무엇이어야합니까? 컴파일 된 상태에서 대리자 또는 식 트리를 반환할지 여부에 따라 다릅니다.
흥미롭지 않은 사건을 기각하는 것으로 시작합시다. 델리게이트를 반환하려면 Quote 또는 Constant를 사용할지 여부에 대한 질문은 논점입니다.
var ps = Expression.Parameter(typeof(int), "s");
var pt = Expression.Parameter(typeof(int), "t");
var ex1 = Expression.Lambda(
Expression.Lambda(
Expression.Add(ps, pt),
pt),
ps);
var f1a = (Func<int, Func<int, int>>) ex1.Compile();
var f1b = f1a(100);
Console.WriteLine(f1b(123));
람다는 중첩 된 람다를 가지고 있습니다. 컴파일러는 외부 람다에 대해 생성 된 함수의 상태에 대해 닫힌 함수에 대한 대리자로 내부 람다를 생성합니다. 우리는이 사건을 더 이상 고려할 필요가 없습니다.
컴파일 된 상태가 내부 의 표현식 트리 를 반환하기를 원한다고 가정 합니다. 이를 수행하는 방법에는 두 가지가 있습니다. 쉬운 방법과 어려운 방법입니다.
어려운 방법은 대신
(int s)=>(int t)=>s+t
우리가 정말로 의미하는 것은
(int s)=>Expression.Lambda(Expression.Add(...
그리고 다음의 식 트리 생성 이를 생산, 이 엉망 :
Expression.Lambda(
Expression.Call(typeof(Expression).GetMethod("Lambda", ...
blah blah blah, 람다를 만들기위한 수십 줄의 리플렉션 코드. 인용 연산자의 목적은 표현식 트리 생성 코드를 명시 적으로 생성하지 않고도 주어진 람다가 함수가 아닌 표현식 트리로 처리되기를 원한다는 것을 표현식 트리 컴파일러에 알리는 것 입니다.
쉬운 방법은 다음과 같습니다.
var ex2 = Expression.Lambda(
Expression.Quote(
Expression.Lambda(
Expression.Add(ps, pt),
pt)),
ps);
var f2a = (Func<int, Expression<Func<int, int>>>)ex2.Compile();
var f2b = f2a(200).Compile();
Console.WriteLine(f2b(123));
그리고 실제로이 코드를 컴파일하고 실행하면 올바른 답을 얻을 수 있습니다.
인용 연산자는 외부 람다의 형식 매개 변수 인 외부 변수를 사용하는 내부 람다에 대한 폐쇄 의미론을 유도하는 연산자입니다.
문제는 왜 Quote를 제거하고 동일한 작업을 수행하도록 만드는 것입니까?
var ex3 = Expression.Lambda(
Expression.Constant(
Expression.Lambda(
Expression.Add(ps, pt),
pt)),
ps);
var f3a = (Func<int, Expression<Func<int, int>>>)ex3.Compile();
var f3b = f3a(300).Compile();
Console.WriteLine(f3b(123));
상수는 클로저 의미론을 유도하지 않습니다. 왜 그래야합니까? 당신은 이것이 상수 라고 말했습니다 . 그것은 단지 가치 일뿐입니다. 컴파일러에게 전달 된 것처럼 완벽해야합니다. 컴파일러는 필요한 스택에 해당 값의 덤프를 생성 할 수 있어야합니다.
클로저가 유도되지 않았기 때문에 이렇게하면 호출시 'System.Int32'유형의 변수 's'가 정의되지 않았습니다. "예외가 발생합니다.
(참고 : 방금 인용 된 표현식 트리에서 델리게이트 생성을위한 코드 생성기를 검토했으며, 불행히도 2006 년에 코드에 넣은 주석이 아직 남아 있습니다. 참고로, 호이스트 된 외부 매개 변수는 인용 될 때 상수로 스냅 샷 됩니다. 표현 트리는 런타임 컴파일러에 의해 델리게이트로 수정됩니다.이 시점에서 기억하지 못하는 방식으로 코드를 작성한 이유가 있었지만 외부 매개 변수 값 에 대해 클로저를 도입하는 불쾌한 부작용이 있습니다. 변수에 대한 종결보다는. 분명히 그 코드를 물려받은 팀은 그 결함을 수정하지 않기로 결정 했으므로 컴파일 된 인용 된 내부 람다에서 관찰되는 닫힌 외부 매개 변수의 변이에 의존한다면 실망하게 될 것입니다. 그러나 (1) 형식 매개 변수를 변경하고 (2) 외부 변수의 변경에 의존하는 것은 매우 나쁜 프로그래밍 관행이므로,이 두 가지 나쁜 프로그래밍 관행을 사용하지 않도록 프로그램을 변경하는 것이 좋습니다. 다가오는 것 같지 않은 수정을 기다리고 있습니다. 오류에 대해 사과드립니다.)
따라서 질문을 반복하려면 :
C # 컴파일러는 중첩 된 람다 식을 Expression.Quote () 대신 Expression.Constant ()와 관련된 식 트리로 컴파일하고 식 트리를 다른 쿼리 언어 (예 : SQL)로 처리하려는 LINQ 쿼리 공급자를 만들 수 있습니다. )는 특수한 Quote 노드 유형이있는 UnaryExpression 대신 Expression 유형이있는 ConstantExpression을 찾을 수 있으며 다른 모든 것은 동일합니다.
당신이 올바른지. 우리는 수 수단에 의해 "이 값에 고정 된 의미를 유도"는 의미 정보 인코딩 플래그로 일정한 표현 형태를 사용하여 .
"상수"는 "이 상수 값을 사용합니다. 단 , 유형이 표현식 트리 유형 이고 값이 유효한 표현식 트리 인 경우가 아니면 대신 이 상수 값 을 사용합니다. 우리가 지금있을 수있는 외부 람다의 맥락에서 클로저 의미론을 유도하기 위해 주어진 표현 트리의 내부.
그런데 왜 것 우리는 미친 짓을? 인용 연산자는 엄청나게 복잡한 연산자 이며, 사용 하려는 경우 명시 적 으로 사용해야합니다. 당신은 이미 존재하는 수십 개의 팩토리 메소드와 노드 유형을 추가하지 않는 것에 대해 간결하게하기 위해 상수에 기괴한 코너 케이스를 추가하여 상수가 때때로 논리적으로 상수이고 때로는 다시 작성되도록 제안합니다. 클로저 의미론이있는 람다.
상수가 "이 값 사용"을 의미하지 않는다는 다소 이상한 효과도 있습니다. 위의 세 번째 경우에서 외부 변수에 대한 다시 작성되지 않은 참조가있는 식 트리를 전달하는 대리자로 식 트리를 컴파일하기 를 원하는 기괴한 이유가 있다고 가정 해 보겠습니다 . 왜? 아마도 컴파일러를 테스트 하고 있고 나중에 다른 분석을 수행 할 수 있도록 상수를 전달하기를 원하기 때문일 수 있습니다. 당신의 제안은 그것을 불가능하게 만들 것입니다. 표현식 트리 유형 인 상수는 상관없이 다시 작성됩니다. "상수"는 "이 값 사용"을 의미한다는 합리적인 기대를 가지고 있습니다. "Constant"는 "내 말대로"노드입니다. 상수 프로세서 ' 유형에 따라 말할 수 있습니다.
그리고 당신은 지금 이해의 부담을 가하고 있습니다 것을 물론 노트 (그 정수를 이해하는 것은 평균 한 경우에 "일정"과는 플래그에 따라 "폐쇄 의미 유도"한다는 의미 복잡했다되는 타입 시스템에 따라) 모든를 Microsoft 공급자뿐만 아니라 식 트리의 의미 분석을 수행하는 공급자입니다. 이러한 제 3 자 제공 업체 중 몇 개가 잘못 이해하고 있습니까?
"인용구"는 "안녕 친구, 여기를 봐, 나는 중첩 된 람다 표현식이고 외부 변수에 대해 닫히면 이상한 의미를 가지고 있습니다!"라는 큰 붉은 깃발을 흔들며 흔들고 있습니다. "Constant"는 "나는 가치에 지나지 않습니다. 적합하다고 생각되는대로 저를 사용하십시오." 어떤 것이 복잡하고 위험 할 때 우리는 사용자 가이 값이 특별한 것인지 아닌지를 알아 내기 위해 타입 시스템 을 파헤쳐 서 그 사실을 숨기지 않고 위험 신호를 흔드는 것을 원합니다 .
또한 중복을 피하는 것이 목표라는 생각은 잘못된 것입니다. 물론 불필요하고 혼란스러운 중복성을 피하는 것이 목표이지만 대부분의 중복성은 좋은 것입니다. 중복성은 명확성을 만듭니다. 새로운 팩토리 방법과 노드 종류는 저렴 합니다. 각 작업이 하나의 작업을 깨끗하게 나타내도록 필요한만큼 만들 수 있습니다. 우리는 "이 필드가이 항목으로 설정되지 않는 한 한 가지를 의미합니다.이 경우 다른 것을 의미합니다."와 같은 불쾌한 속임수에 의지 할 필요가 없습니다.