Neden Func <T> yerine <Func <T>> İfadesini kullanasınız?


949

Lambdaları anlıyorum ve Func ve Actiondelegeleri . Ama ifadeler beni çok üzüyor.

Hangi durumlarda Expression<Func<T>>sade bir eski yerine kullanmak isterdiniz Func<T>?


14
Func <>, c # derleyici düzeyinde bir yönteme dönüştürülecek, <Func <>> ifadesi, kodu doğrudan derledikten sonra MSIL düzeyinde yürütülecek, bu yüzden daha hızlı
Waleed AK

1
cevaplara ek olarak, csharp dil ​​belirtimi "4.6 ifade ağacı türleri" çapraz referans için
yararlıdır

Yanıtlar:


1133

Lambda ifadelerini ifade ağaçları olarak ele almak ve yürütmek yerine içine bakmak istediğinizde. Örneğin, LINQ to SQL ifadeyi alır ve eşdeğer SQL deyimine dönüştürür ve sunucuya gönderir (lambda yürütmek yerine).

Kavramsal olarak, Expression<Func<T>>bir bambaşka dan Func<T>. Func<T>"Bir delegate" terimi , bir yöntemin neredeyse bir göstergesidir ve lambda ifadesi için bir ağaç veri yapısınıExpression<Func<T>> belirtir . Bu ağaç yapısı , lambda ifadesinin gerçek şeyi yapmak yerine ne yaptığını açıklar . Temel olarak ifadelerin, değişkenlerin, yöntem çağrılarının, ... bileşimi hakkındaki verileri tutar (örneğin, bu lambda bazı sabit + bazı parametreler gibi bilgileri tutar). Bu açıklamayı gerçek bir yönteme dönüştürmek (ile ) veya başka şeyler (LINQ to SQL örneği gibi) yapmak için kullanabilirsiniz. Lambdaları anonim yöntemler ve ifade ağaçları olarak ele alma eylemi tamamen derleme zamanıdır.Expression.Compile

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

hiçbir şey almaz ve 10 döndüren bir IL yöntemine etkili bir şekilde derleyecektir.

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

parametre almayan ve 10 değerini döndüren bir ifadeyi tanımlayan bir veri yapısına dönüştürülür:

İfade vs Func büyük resim

Her ikisi de derleme zamanında aynı görünse de, derleyicinin ürettiği şey tamamen farklıdır .


96
Diğer bir deyişle, an Expression, belirli bir delege hakkındaki meta bilgileri içerir.
bertl

40
@bertl Aslında hayır. Delege hiç karışmıyor. Bir temsilci ile hiç bir ilişkisi yoktur nedeni ifadesini derlemek olmasıdır için ya da daha kesin konuşmak gerekirse, bir yönteme derlemek ve bir dönüş değeri olarak o yönteme temsilci olsun - Bir temsilci. Fakat ifade ağacının kendisi sadece veridir. Yalnızca Expression<Func<...>>yerine kullandığınızda temsilci yok Func<...>.
Luaan

5
@Kyle Delaney (isAnExample) => { if(isAnExample) ok(); else expandAnswer(); }böyle bir ifade bir ExpressionTree, If-ifadesi için dalları oluşturulur.
Matteo Marciano - MSCP

3
@bertl Temsilci CPU'nun gördüğü (bir mimarinin yürütülebilir kodu), İfade derleyicinin gördüğü (sadece kaynak kodun başka bir biçimi, ama yine de kaynak kodu).
kod savaşçısı

5
@bertl: Bir ifadenin, bir stringbuilder'ın bir string'e ne olduğunu ifade etmek için daha doğru bir şekilde özetlenebilir. Bir dize / fonk değildir, ancak istendiğinde bir tane oluşturmak için gerekli verileri içerir.
flater

337

Noobs için bir cevap ekliyorum, çünkü bu cevaplar ne kadar basit olduğunu anlayana kadar başımın üzerinde görünüyordu. Bazen 'kafanı etrafına saramazsın' karmaşık olması beklentinizdir.

LINQ-to-SQL genel olarak kullanmaya çalışırken gerçekten sinir bozucu bir 'hata' içine yürüdü kadar farkı anlamak zorunda değildi:

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

Daha büyük veri kümelerinde OutofMemoryExceptions almaya başlayana kadar bu harika çalıştı. Lambda içindeki kesme noktalarını ayarlamak, masamdaki her satırdan lambda durumumla eşleşme arayan teker teker yinelendiğini fark etmemi sağladı. Bu beni bir süre boğdu, çünkü neden veri tabloma olması gerektiği gibi LINQ-to-SQL yapmak yerine dev bir IEnumerable olarak davranıyor? Aynı şeyi LINQ-MongoDb muadilimde de aynıydı.

Düzeltme sadece Func<T, bool>dönüşmekti Expression<Func<T, bool>>, bu yüzden bunun Expressionyerine niye bunun yerine ihtiyaç duyduğunu googled Func.

Bir ifade, bir temsilciyi kendisi hakkındaki verilere dönüştürür. Yani a => a + 1"Sol tarafta bir tane var int a. Sağ tarafta ona 1 eklersiniz." Bu kadar. Şimdi eve gidebilirsin. Açıkçası bundan daha yapılandırılmış, ama aslında tüm bu bir ifade ağacı aslında - başınızı saran bir şey yok.

Bunu anlayarak Expression, neden LINQ-to-SQL'e ihtiyaç duyulduğu ve bir Funcyeterli olmadığı anlaşılmaktadır . Funcbir SQL / MongoDb / diğer sorguya nasıl dönüştürüleceğinin nitritini görmek için kendi içine girmenin bir yolunu taşımaz. Toplama, çarpma veya çıkarma yapıp yapmadığını göremezsiniz. Yapabileceğiniz tek şey onu çalıştırmak. ExpressionÖte yandan, temsilcinin içine bakmanıza ve yapmak istediği her şeyi görmenize olanak tanır. Bu, temsilci bir SQL sorgusu gibi istediğiniz herhangi bir dile çevirmenizi sağlar.Funcbenim DbContext lambda ifade içeriğini kör olduğu için işe yaramadı. Bu nedenle lambda ifadesini SQL'e çeviremedi; ancak, bir sonraki en iyi şeyi yaptı ve bu koşullu benim tablodaki her satır üzerinden yineledi.

Düzenleme: John Peter'ın isteğindeki son cümlemle ilgili açıklama:

IQueryable, IEnumerable'ı genişletir, bu nedenle IEnumerable'ın Where()kabul eden aşırı yükleri alma gibi yöntemleri Expression. Buna bir Expressiongeçtiğinizde, sonuç olarak bir IQueryable tutarsınız, ancak bir geçtiğinizde FuncIEnumerable üssüne geri düşersiniz ve sonuç olarak bir IEnumerable alırsınız. Diğer bir deyişle, fark etmeden veri kümenizi sorgulanacak bir şeyin aksine yinelenecek bir listeye dönüştürdünüz. İmzalara gerçekten bakana kadar bir fark fark etmek zor.


2
Çad; Lütfen bu yorumu biraz daha açıklayın: "Func işe ​​yaramadı çünkü DbContext'im aslında lambda ifadesinde SQL'e dönüştürmek için olanlara kördü, bu yüzden bir sonraki en iyi şeyi yaptı ve bu koşullu tablomdaki her satır boyunca yineledi ."
John Peters

2
>> Func ... Yapabileceğiniz tek şey onu çalıştırmak. Tam olarak doğru değil, ama bence vurgulanması gereken nokta bu. İşlevler / İşlemler çalıştırılacak, İfadeler analiz edilecektir (çalıştırmadan önce ve hatta çalıştırmak yerine).
Konstantin

@ Sorun burada mıydı ?: db.Set <T> tüm veritabanı tablosunu sorguladı ve sonra, çünkü .Where (conditionLambda) bellekteki tüm tabloda numaralandırılan Where (IEnumerable) uzantı yöntemini kullandı . OutOfMemoryException olsun düşünüyorum, çünkü bu kod tüm tabloyu belleğe yüklemeye çalıştı (ve tabii ki nesneleri yarattı). Haklı mıyım? Teşekkürler :)
Bence Végert

104

İfade vs Func seçiminde son derece önemli bir husus, LINQ to Entities gibi IQueryable sağlayıcılarının bir İfadede geçtiklerinizi 'sindirebilmeleri', ancak bir Func'ta geçtiklerinizi göz ardı edebilmeleridir. Konuyla ilgili iki blog yayınım var:

İfade ve Varlık Çerçevesi ile Func ve LINQ Aşık Olma - Bölüm 7: İfadeler ve İşlevler (son bölüm) hakkında daha fazla bilgi


Açıklama için + l. Ancak 'LINQ ifade düğümü türü' Invoke 'LINQ to Entities desteklenmiyor' alıyorum. ' ve sonuçları getirdikten sonra ForEach kullanmak zorundaydı.
Mart'ta timtam

77

Ben arasındaki farklar hakkında bazı notlar eklemek istiyorum Func<T>ve Expression<Func<T>>:

  • Func<T> sadece normal bir eski okul MulticastDelegate;
  • Expression<Func<T>> "Lambda" ifadesi, lambda ifadesinin ifade ağacı şeklinde bir temsilidir;
  • ifade ağacı lambda ifade sözdizimi veya API sözdizimi yoluyla oluşturulabilir;
  • ifade ağacı bir delege için derlenebilir Func<T> ;
  • ters dönüşüm teorik olarak mümkündür, ancak bu bir tür ayrıştırmadır, bunun için basit bir süreç olmadığı için yerleşik bir işlevsellik yoktur;
  • ifade ağacı gözlemlenebilir / çevrilebilir ExpressionVisitor ;
  • IEnumerable için uzatma yöntemleri Func<T> ;
  • IQueryable için uzatma yöntemleri ile çalışır Expression<Func<T>>.

Kod örnekleri ile ayrıntıları açıklayan bir makale vardır:
LINQ: Func <T> vs. İfade <Func <T>> .

Umarım yardımcı olacaktır.


Güzel bir liste, küçük bir not, ters dönüşümün mümkün olduğunu belirtiyorsunuz, ancak tam tersi değil. Dönüştürme işlemi sırasında bazı meta veriler kaybolur. Ancak, tekrar derlendiğinde aynı sonucu üreten bir İfade ağacına kodabilirsiniz.
Aidiakapi

76

Bu konuda Krzysztof Cwalina'nın kitabından daha felsefi bir açıklama var ( Çerçeve Tasarım Yönergeleri: Sözleşmeler, Deyimler ve Yeniden Kullanılabilir .NET Kütüphaneleri için Desenler );

Rico Mariani

Resim olmayan sürüm için düzenle:

Çoğu zaman Func veya Action isteyeceksiniz, eğer tek yapmanız gereken bir kod çalıştırmak. İhtiyacınız İfade çalıştırıldığı önce kodu analiz tefrika veya optimize gerektiğinde. İfade kodu düşünmek içindir, Func / Action kodu çalıştırmak içindir.


10
İyi koy. yani. Func'unuzun bir tür sorguya dönüştürülmesini beklerken ifadeye ihtiyacınız vardır. Yani. database.data.Where(i => i.Id > 0)olarak yürütülmesi gerekir SELECT FROM [data] WHERE [id] > 0. Sadece bir Func geçmek ederseniz, sürücü üzerinde blinders koyduk ve onu yapabileceği tek şey SELECT *id> 0'dır tamamlayan ile her şeyi dışarı her ve filtre aracılığıyla, iterate ve daha sonra belleğe, bu verilerin tümünü Dolu bir kez Funcde Expressiongüçlendirir sürücüyü analiz edip FuncSql / MongoDb / başka bir sorguya dönüştürür.
Chad Hedgcock

Bu yüzden bir tatil için planlarken kullanacağım Expressionama tatil Func/Action
içindeyken

1
@ChadHedgcock Bu ihtiyacım olan son parçaydı. Teşekkürler. Bir süredir buna bakıyorum ve buradaki yorumunuz tüm çalışma tıklamasını yaptı.
johnny

37

LINQ, kanonik bir örnektir (örneğin, bir veritabanıyla konuşmak), ancak gerçekte, aslında ne yapmaktan ziyade ne yapacağınızı ifade etmeyi önemsediğiniz her zaman . Örneğin, bu yaklaşımı protobuf-net'in RPC yığınında kullanıyorum (kod oluşturma vb. Önlemek - böylece ile bir yöntem çağırır:

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

Bu Çözmek, deyim ağacı dekonstürüksiyon SomeMethod, (ve her bağımsız değişkenin değeri) RPC çağrısı yapar, herhangi günceller ref/out arg'yi ve uzak çağrıdan sonucu döndürür. Bu sadece ifade ağacı ile mümkündür. Bunu burada daha fazla kaplarım .

Başka bir örnek, genel operatörler koduyla yapıldığı gibi, ifade ağaçlarını bir lambda'ya derlemek amacıyla manuel olarak oluşturduğunuz zamandır .


20

İşlevinizi kod olarak değil veri olarak değerlendirmek istediğinizde bir ifade kullanırsınız. Kodu (veri olarak) değiştirmek istiyorsanız bunu yapabilirsiniz. Çoğu zaman ifadelere ihtiyaç duymazsanız muhtemelen bir tane kullanmanız gerekmez.


19

Birincil neden, kodu doğrudan çalıştırmak istemediğiniz, daha ziyade incelemek istemenizdir. Bu, çeşitli nedenlerden dolayı olabilir:

  • Kodu farklı bir ortama eşleme (ör. Entity Framework'te C # kodu SQL ile)
  • Kodun çalışma zamanında parçalarını değiştirme (dinamik programlama veya düz DRY teknikleri)
  • Kod doğrulama (komut dosyası taklit edilirken veya analiz yapılırken çok kullanışlıdır)
  • Serileştirme - ifadeler oldukça kolay ve güvenli bir şekilde serileştirilebilir, delegeler
  • Doğası gereği güçlü bir şekilde yazılmayan şeylerde güçlü tipte güvenlik ve çalışma zamanında dinamik çağrılar yapmanıza rağmen derleyici kontrollerinden yararlanma (Razor ile ASP.NET MVC 5 güzel bir örnektir)

5 hakkında biraz daha ayrıntılı olabilir misiniz
uowzd01

@ uowzd01 Sadece Razor'a bakın - bu yaklaşımı kapsamlı bir şekilde kullanır.
Luaan

@Luaan İfade serileştirmeleri arıyorum ancak sınırlı bir üçüncü taraf kullanımı olmadan hiçbir şey bulamıyorum. Net 4.5 ifade ağacı serileştirmeyi destekliyor mu?
vabii

@vabii Bildiğim kadarıyla değil - ve genel durum için gerçekten iyi bir fikir olmazdı. Demek istediğim, vaktinden önce tasarlanmış arayüzlere karşı, desteklemek istediğiniz belirli durumlar için oldukça basit bir serileştirme yazabilmenizle ilgiliydi - bunu birkaç kez yaptım. Genel durumda, bir Expressiondelege olarak serileştirmek imkansız olabilir, çünkü herhangi bir ifade keyfi bir delege / yöntem başvurusunun çağrılmasını içerebilir. "Kolay" elbette görecelidir.
Luaan

15

Performanstan bahseden henüz bir cevap göremiyorum. Or Func<>ye geçmek Where()ya Count()da kötüdür. Gerçek kötü. Eğer bir kullanırsanız Func<>o zaman çağırır IEnumerableyerine LINQ şeyler IQueryablebütün tablolar çekti ve almak demek olduğunu, daha sonra süzülür. Expression<Func<>>özellikle başka bir sunucuyu yaşayan bir veritabanını sorguluyorsanız, çok daha hızlıdır.


Bu bellek içi sorgu için de geçerli mi?
stt106

@ stt106 Muhtemelen hayır.
6:18

Bu sadece listeyi numaralandırırsanız geçerlidir. GetEnumerator veya foreach kullanıyorsanız, ienumerable'ı tam olarak belleğe yüklemezsiniz.
nelsontruran

1
@ stt106 List <> .Where () yantümcesine iletildiğinde, <Func <>> ifadesi .Compile () öğesini çağırır, bu nedenle Func <> neredeyse kesinlikle daha hızlıdır. Bkz referencesource.microsoft.com/#System.Core/System/Linq/...
NStuke
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.