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


948

나는 람다와 이해 FuncAction대의원. 그러나 표정은 저를 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 FuncLINQ와의 사랑 에 대한 자세한 내용 -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이 포장과 함께 모든에서 각각의 필터를 통해 반복,을 한 후 메모리에 데이터를 모두 장전 한 번 FuncExpression힘을 실어 드라이버를 분석하여 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.