Func <T> 대신 Expression <Func <T >>를 사용하는 이유는 무엇입니까?


948

나는 람다와 이해 Func및 Action대의원. 그러나 표정은 저를 ump니다.

어떤 상황 Expression<Func<T>>에서 평범하지 않은 오래된 것을 사용 Func<T>하시겠습니까?


14
Func <>는 c # 컴파일러 레벨의 메소드로 변환됩니다. Express <Func <>>는 코드를 직접 컴파일 한 후 MSIL 레벨에서 실행됩니다. 이것이 바로 더 빠른 이유입니다.
— Waleed AK

1
답변에 추가하여 CSHARP 언어 사양 "4.6 식 트리 유형은"상호 참조하는 것이 도움이된다
— djeikyb

답변:


1133

람다 식을 식 트리로 취급하고 실행하지 말고 내부를 살펴보십시오. 예를 들어 LINQ to SQL은 표현식을 가져 와서 동등한 SQL 문으로 변환하여 람다를 실행하지 않고 서버에 제출합니다.

개념적으로, Expression<Func<T>>이다 완전히 다른 에서 Func<T>. Func<T>a delegate는 메서드에 대한 포인터와 거의 같으며 람다 식에 대한 트리 데이터 구조 를 Expression<Func<T>>나타냅니다 . 이 트리 구조 는 실제 작업을 수행하는 대신 람다 식의 기능을 설명합니다 . 기본적으로 표현식, 변수, 메소드 호출의 구성에 대한 데이터를 보유합니다 (예 :이 람다는 일부 상수 + 일부 매개 변수와 같은 정보를 보유 함). 이 설명을 사용하여 실제 메소드 (로 사용 ) 로 변환 하거나 LINQ to SQL 예제와 같은 다른 작업을 수행 할 수 있습니다. 람다를 익명의 메소드와 표현 트리로 취급하는 행위는 순전히 컴파일 시간입니다.Expression.Compile

Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }

아무것도 얻지 않고 10을 반환하는 IL 메소드로 효과적으로 컴파일됩니다.

Expression<Func<int>> myExpression = () => 10;

매개 변수를 얻지 않고 값 10을 리턴하는 표현식을 설명하는 데이터 구조로 변환됩니다.

표현 대 기능 큰 이미지

컴파일 타임에서 둘 다 동일하게 보이지만 컴파일러가 생성하는 것은 완전히 다릅니다 .


95
즉, Expression에 특정 대리인에 대한 메타 정보 가 포함되어 있습니다.
— bertl

39
@bertl 사실은 아닙니다. 대리인은 전혀 관여하지 않습니다. 델리게이트와 연관이있는 이유는 표현식 을 델리게이트로 컴파일 하거나보다 정확하게 표현 하려면 메소드로 컴파일하고 해당 메소드의 델리게이트를 리턴 값으로 가져 오기 때문입니다. 그러나 표현 트리 자체는 데이터 일뿐입니다. Expression<Func<...>>그냥 사용하는 대신 대리인이 존재하지 않습니다 Func<...>.
— Luaan

5
@Kyle Delaney (isAnExample) => { if(isAnExample) ok(); else expandAnswer(); }와 같은 표현식은 ExpressionTree이며, 분기는 If 문에 대해 작성됩니다.
— Matteo Marciano-MSCP

3
@bertl Delegate는 CPU가 보는 것 (한 아키텍처의 실행 가능한 코드), Expression은 컴파일러가 보는 것 (단지 다른 형식의 소스 코드이지만 여전히 소스 코드)입니다.
— codewarrior

5
@ bertl : 표현은 stringbuilder가 문자열에 대한 기능이라고 말하면 더 정확하게 요약 될 수 있습니다. 문자열 / 기능은 아니지만 요청시 데이터를 생성하는 데 필요한 데이터가 포함되어 있습니다.
— Flater

337

이 답변이 얼마나 간단한 지 깨달을 때까지 답이 내 머리 위로 보였으므로 답변에 대한 답변을 추가하고 있습니다. 때로는 머리를 감쌀 수 없게 만드는 것이 복잡하다는 기대가 있습니다.

LINQ-to-SQL을 일반적으로 사용하려고하는 정말 성가신 '버그'에 들어가기 전까지는 차이점을 이해할 필요가 없었습니다.

public IEnumerable<T> Get(Func<T, bool> conditionLambda){
  using(var db = new DbContext()){
    return db.Set<T>.Where(conditionLambda);
  }
}

이것은 더 큰 데이터 세트에서 OutofMemoryExceptions를 받기 ​​시작할 때까지 훌륭하게 작동했습니다. 람다 내부에 중단 점을 설정하면 테이블의 각 행을 하나씩 반복하면서 람다 조건과 일치하는 것을 찾고 있음을 알았습니다. LINQ-to-SQL을 수행하는 대신 내 데이터 테이블을 거대한 IEnumerable로 취급하는 이유는 무엇입니까? 또한 내 LINQ-to-MongoDb 상대방과 똑같은 일을하고있었습니다.

수정 프로그램을 설정하는 간단했다 Func<T, bool>으로 Expression<Func<T, bool>>, 그래서 그것이 필요한 이유 검색 좀 Expression대신을 Func여기서 종료.

식은 단순히 대리자를 자체에 대한 데이터로 바꿉니다. 그래서 a => a + 1같은이된다 "는 거기 왼쪽에서을 int a. 오른쪽에 당신이 1을 추가 할 수 있습니다." 그게 다야. 이제 집에 갈 수 있습니다. 그것은 분명히 그것보다 더 구조적이지만, 그것은 본질적으로 모든 표현 트리입니다. 머리를 감쌀 필요는 없습니다.

LINQ --SQL에가 필요한 이유가 이해가 명확하게 Expression하고,이 Func적합하지 않습니다. FuncSQL / MongoDb / 기타 쿼리로 변환하는 방법의 핵심을 이해하기 위해 자체적으로 들어가는 방법을 가지고 있지 않습니다. 더하기 또는 곱하기 또는 빼기를 수행하는지 확인할 수 없습니다. 당신이 할 수있는 것은 그것을 실행하는 것입니다. Expression반면, 델리게이트 내부를보고 원하는 모든 것을 볼 수 있습니다. 이렇게하면 대리자를 SQL 쿼리와 같이 원하는대로 변환 할 수 있습니다. Func내 DbContext가 람다 식의 내용에 대해 눈이 멀었 기 때문에 작동하지 않았습니다. 이 때문에 람다 식을 SQL로 바꿀 수 없었습니다. 그러나 다음으로 최선을 다했으며 내 테이블의 각 행을 통해 조건을 반복했습니다.

편집 : John Peter의 요청에 따라 마지막 문장에 대해 설명합니다.

IQueryable은 IEnumerable을 확장하므로 IEnumerable의 메서드는 Where()을 허용하는 오버로드를 얻습니다 Expression. 에 전달하면 Expression결과로 IQueryable을 유지하지만을 전달 Func하면 기본 IEnumerable로 돌아가서 IEnumerable을 얻게됩니다. 다시 말해, 주목할 필요없이 데이터 세트를 쿼리 할 대상이 아닌 반복 할 목록으로 전환했습니다. 서명을 실제로 볼 때까지 차이를 느끼기가 어렵습니다.


2
차드; 이 주석을 조금 더 설명해주세요. "DbContext가 실제로 람다 식에있는 것을 눈에 띄게하여 SQL로 변환했기 때문에 Func가 작동하지 않았기 때문에 다음으로 최선을 다했고 내 테이블의 각 행을 통해 조건을 반복했습니다. "
— John Peters

2
>> 펑크 .. 당신이 할 수있는 전부는 그것을 실행하는 것입니다. 사실은 아니지만 그것이 강조되어야 할 점이라고 생각합니다. 기능 / 동작을 실행하고, 식을 분석해야합니다 (실행하기 전 또는 실행하는 대신).
— Konstantin

@Chad 여기에 문제가 있었습니까? : db.Set <T>는 모든 데이터베이스 테이블을 쿼리 한 후 .Where (conditionLambda)는 메모리의 전체 테이블에 열거 된 Where (IEnumerable) 확장 메서드를 사용했기 때문에 발생했습니다. . 이 코드는 전체 테이블을 메모리에로드하려고 시도했지만 객체를 생성했기 때문에 OutOfMemoryException이 발생한다고 생각합니다. 내가 맞아? 감사합니다 :)
— Bence Végert

104

LINQ to Entities와 같은 IQueryable 공급자는 Expression과 Func를 선택할 때 매우 중요하게 고려해야 할 사항은 Expression에서 전달한 내용을 '다이제스트'할 수 있지만 Func에서 전달한 내용은 무시한다는 것입니다. 주제에 대한 두 개의 블로그 게시물이 있습니다.

Entity Framework를 통한 Expression vs Func 및 LINQ와의 사랑 에 대한 자세한 내용 -7 부 : 표현식 및 기능 (마지막 섹션)


설명을 위해 + l. 그러나 'LINQ to Entities에서 LINQ 표현식 노드 유형'Invoke '는 지원되지 않습니다.' 결과를 가져온 후 ForEach를 사용해야했습니다.
— tymtam

77

나는 사이의 차이점에 대한 몇 가지 메모를 추가하고 싶습니다 Func<T>및 Expression<Func<T>>:

  • Func<T> 단지 일반적인 구식 MulticastDelegate입니다.
  • Expression<Func<T>> 는 표현 트리 형태의 람다 표현의 표현이고;
  • 표현 트리는 람다 표현 구문 또는 API 구문을 통해 구성 될 수 있습니다.
  • 표현식 트리는 델리게이트로 컴파일 할 수 있습니다 Func<T>.
  • 역변환은 이론적으로 가능하지만 일종의 디 컴파일이며, 직접적인 프로세스가 아니기 때문에 내장 기능이 없습니다.
  • 발현 트리는를 통해 관찰 / 번역 / 변형 될 수 있고 ExpressionVisitor;
  • IEnumerable의 확장 방법은 다음과 같이 작동합니다 Func<T>.
  • IQueryable의 확장 메소드는로 작동합니다 Expression<Func<T>>.

코드 샘플로 세부 사항을 설명하는 기사가 있습니다 :
LINQ : Func <T> vs. Expression <Func <T >> .

도움이 되길 바랍니다.


좋은 목록, 하나의 작은 메모는 역 변환이 가능하지만 정확한 역은 불가능하다는 언급입니다. 변환 과정에서 일부 메타 데이터가 손실됩니다. 그러나 다시 컴파일 할 때 동일한 결과를 생성하는 표현식 트리로 디 컴파일 할 수 있습니다.
— Aidiakapi

76

Krzysztof Cwalina의 저서 ( Framework Design Guidelines : Conventions, Idioms, and the Patterns for Reusable .NET Libraries ) 에서 더 철학적으로 설명 할 수 있습니다 .

리코 마리아니

이미지가 아닌 버전으로 편집 :

대부분의 경우 필요한 모든 코드를 실행하는 것이라면 Func 또는 Action 을 원할 것입니다. 코드를 실행하기 전에 코드를 분석, 직렬화 또는 최적화 해야하는 경우 Expression 이 필요 합니다. Expression 은 코드를 생각 하기위한 것이고 Func / Action 은 코드 를 실행하기위한 것입니다.


10
잘 넣어 즉. Func이 일종의 쿼리로 변환 될 것으로 예상 할 때 표현이 필요합니다. 즉. database.data.Where(i => i.Id > 0)로 실행 해야 합니다 SELECT FROM [data] WHERE [id] > 0. 당신은 단지 Func을 전달 경우에, 당신은 당신의 드라이버에 눈가리개를 넣었습니다 그것이 할 수있는 모든이다 SELECT *ID> 0이 포장과 함께 모든에서 각각의 필터를 통해 반복,을 한 후 메모리에 데이터를 모두 장전 한 번 Func에 Expression힘을 실어 드라이버를 분석하여 FuncSql / MongoDb / other 쿼리로 변환합니다.
— 채드 헤지 콕

내가 휴가를 계획하고있을 때, 나는 사용 Expression하려고하지만 휴가 중일 때는 Func/Action;)
— GoldBishop

1
@ChadHedgcock 이것이 마지막으로 필요한 부분이었습니다. 감사. 나는 이것을 잠시 동안보고 있었고, 여기에 귀하의 의견은 모든 연구 클릭을 만들었습니다.
— johnny

37

LINQ는 정식 예제 (예 : 데이터베이스와 대화)이지만 실제로는 실제로 수행하는 것이 아니라 수행 할 작업을 표현 하는 데 더 관심이있는 경우 언제든지 가능 합니다. 예를 들어, 나는 코드 생성 등을 피하기 위해 protobuf-net 의 RPC 스택 에서이 접근법을 사용 하므로 다음과 같이 메소드를 호출하십시오.

string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));

이것은 해결하기 위해 식 트리 해체 SomeMethod(각 인수의 값), 수행 RPC 호출 모든 업데이트 ref/의 out인수를 원격 호출의 결과를 반환합니다. 이것은 표현식 트리를 통해서만 가능합니다. 나는 이것을 더 많이 여기에서 다룬다 .

또 다른 예는 일반 연산자 코드 에서와 같이 람다로 컴파일하기 위해 식 트리를 수동으로 작성하는 경우 입니다.


20

함수를 코드가 아닌 데이터로 취급하려는 경우 표현식을 사용합니다. 코드를 데이터로 조작하려는 경우이 작업을 수행 할 수 있습니다. 대부분의 경우 표현식이 필요하지 않으면 표현식을 사용할 필요가 없습니다.


19

주된 이유는 코드를 직접 실행하지 않고 검사하려는 경우입니다. 여러 가지 이유가있을 수 있습니다.

  • 코드를 다른 환경에 매핑 (예 : Entity Framework에서 C # 코드를 SQL에 매핑)
  • 런타임에서 코드 일부 교체 (동적 프로그래밍 또는 일반 DRY 기술)
  • 코드 유효성 검사 (스크립트를 에뮬레이션하거나 분석 할 때 매우 유용)
  • 직렬화-식을보다 쉽고 안전하게 직렬화 할 수 있습니다.
  • 기본적으로 강력하게 형식화되지 않은 것들에 대한 강력한 형식의 안전성과 런타임에서 동적 호출을 수행하더라도 컴파일러 검사를 악용 (Az.NET MVC 5 with Razor는 좋은 예입니다)

당신은 no.5에 조금 더 정교하게 할 수 있습니까
— uowzd01

@ uowzd01 Razor를 살펴보십시오.이 접근법은 광범위하게 사용됩니다.
— Luaan

@Luaan 식 직렬화를 찾고 있지만 제한된 타사 사용 없이는 아무것도 찾을 수 없습니다. .Net 4.5는 표현식 트리 직렬화를 지원합니까?
— vabii December

@vabii 내가 아는 바는 없으며 일반적인 경우에는 실제로 좋은 생각이 아닙니다. 제 요점은 미리 설계된 인터페이스에 대해 지원하려는 특정 사례에 대해 매우 간단한 직렬화를 작성할 수 있다는 것입니다. 몇 번만했습니다. 일반적으로 Expression식은 임의의 대리자 / 메서드 참조의 호출을 포함 할 수 있으므로 대리자처럼 직렬화하는 것이 불가능할 수 있습니다. "쉬운"은 물론 상대적입니다.
— Luaan

15

성능에 대한 답변이 아직 없습니다. 전달 Func<>에들 Where()또는 것은 Count()좋지 않습니다. 진짜 나빠 당신이 사용하는 경우 Func<>다음이 호출 IEnumerable대신 LINQ 물건 IQueryable전체 테이블에서 뽑아 얻을 것을 의미 한 후 여과한다. Expression<Func<>>특히 다른 서버에있는 데이터베이스를 쿼리하는 경우 훨씬 빠릅니다.


이것은 메모리 내 쿼리에도 적용됩니까?
— stt106

@ stt106 아마 아닐 것이다.
— mhenry1384

목록을 열거하는 경우에만 해당됩니다. GetEnumerator를 사용하거나 foreach를 사용하면 ienumerable을 메모리에 완전히로드하지 않습니다.
— nelsontruran

1
@ stt106 List <>의 .Where () 절에 전달되면 Expression <Func <>>가 .Compile ()을 호출하므로 Func <>이 훨씬 빠릅니다. 참조 referencesource.microsoft.com/#System.Core/System/Linq/...
— NStuke
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.