C # 'da, anonim bir yöntem neden bir getiri ifadesi içeremez?


88

Bunun gibi bir şey yapmanın güzel olacağını düşündüm (lambda getiri getirisi yaparak):

public IList<T> Find<T>(Expression<Func<T, bool>> expression) where T : class, new()
{
    IList<T> list = GetList<T>();
    var fun = expression.Compile();

    var items = () => {
        foreach (var item in list)
            if (fun.Invoke(item))
                yield return item; // This is not allowed by C#
    }

    return items.ToList();
}

Ancak anonim yöntemde verimi kullanamayacağımı öğrendim. Nedenini merak ediyorum. Verim dokümanlar sadece izin verilmez söylüyorlar.

İzin verilmediğinden, sadece Liste oluşturdum ve öğeleri ona ekledim.


Artık C # async5.0'da awaitiçeriye izin veren anonim lambdalara sahip olabildiğimize göre, neden hala yieldiçeride anonim yineleyiciler uygulamadıklarını bilmek isterim . Aşağı yukarı aynı durum makinesi üreteci.
noseratio

Yanıtlar:


114

Eric Lippert, geçtiğimiz günlerde bazı durumlarda verime neden izin verilmediğiyle ilgili bir dizi blog yazısı yazdı.

DÜZENLEME2:

  • Bölüm 7 (bu daha sonra yayınlanmıştır ve özellikle bu soruyu ele almaktadır)

Cevabı muhtemelen orada bulacaksınız ...


DÜZENLEME1: Bu, Eric'in Abhijeet Patel'in yorumuna cevabında, 5. Bölümün yorumlarında açıklanmıştır:

S:

Eric,

Anonim bir yöntemde veya lambda ifadesinde "verime" neden izin verilmediğine dair biraz fikir verebilir misiniz?

A:

İyi soru. Anonim yineleme bloklarına sahip olmayı çok isterim. Kendinize yerel değişkenleri kapatan yerinde küçük bir dizi üreteci inşa edebilmek harika olurdu. Neden olmasın: faydalar maliyetlerden ağır basmaz. Sıra üreteçlerini yerinde yapmanın müthişliği aslında şeylerin genel şemasında oldukça küçüktür ve nominal yöntemler çoğu senaryoda işi yeterince iyi yapar. Dolayısıyla faydalar o kadar zorlayıcı değil.

Maliyetler büyük. Yineleyici yeniden yazma, derleyicideki en karmaşık dönüşümdür ve anonim yöntem yeniden yazma en karmaşık ikinci yöntemdir. Anonim yöntemler diğer anonim yöntemlerin içinde olabilir ve anonim yöntemler yineleyici blokların içinde olabilir. Bu nedenle, yaptığımız şey, önce tüm anonim yöntemleri yeniden yazmaktır, böylece bunlar bir kapatma sınıfının yöntemi olurlar. Bu, bir yöntem için IL yaymadan önce derleyicinin yaptığı ikinci son şeydir. Bu adım tamamlandığında, yineleyici yeniden yazıcısı yineleyici bloğunda anonim yöntem olmadığını varsayabilir; hepsi zaten yeniden yazılmış. Bu nedenle, yineleyiciyi yeniden yazan kişi, orada gerçekleştirilmemiş anonim bir yöntem olabileceğinden endişe etmeden yineleyiciyi yeniden yazmaya odaklanabilir.

Ayrıca yineleyici bloklar, anonim yöntemlerin aksine asla "iç içe geçmez". Yineleyici yeniden yazıcısı, tüm yineleyici bloklarının "en üst düzey" olduğunu varsayabilir.

Anonim yöntemlerin yineleyici blokları içermesine izin verilirse, bu iki varsayım da pencereden dışarı çıkar. Anonim bir yöntem içeren bir yineleyici bloğu içeren anonim bir yöntemi içeren anonim bir yöntemi içeren bir yineleyici bloğunuz olabilir ve ... yuck. Şimdi, en karmaşık iki algoritmamızı çok daha karmaşık bir algoritmada birleştirerek, iç içe geçmiş yineleyici bloklarını ve yuvalanmış anonim yöntemleri aynı anda işleyebilen bir yeniden yazma geçişi yazmalıyız. Tasarlamak, uygulamak ve test etmek gerçekten zor olurdu. Bunu yapacak kadar zekiyiz, eminim. Burada akıllı bir ekibimiz var. Ancak "olması güzel ama gerekli olmayan" bir özellik için bu kadar büyük bir yük almak istemiyoruz. - Eric


2
Özellikle şu anda yerel işlevler olduğu için ilginç.
Mafii

4
Bu cevabın güncel olup olmadığını merak ediyorum çünkü yerel bir fonksiyonda getiri getirisi alacak.
Joshua

2
@Joshua, ancak yerel bir işlev anonim bir yöntemle aynı değildir ... anonim yöntemlerde getiri getirisine hala izin verilmemektedir.
Thomas Levesque

21

Eric Lippert, yineleyici bloklar üzerindeki sınırlamalar (ve bu seçimleri etkileyen tasarım kararları) üzerine mükemmel bir makale dizisi yazmıştır.

Özellikle yineleyici bloklar, bazı karmaşık derleyici kod dönüşümleri tarafından uygulanır. Bu dönüşümler, anonim işlevler veya lambdalar içinde meydana gelen dönüşümleri etkileyecek, öyle ki belirli durumlarda her ikisi de kodu diğeriyle uyumsuz olan başka bir yapıya 'dönüştürmeye' çalışacaklardı.

Sonuç olarak, etkileşimden men edilirler.

Yineleme bloklarının başlık altında nasıl çalıştığı burada iyi ele alınmaktadır .

Uyumsuzluğun basit bir örneği olarak:

public IList<T> GreaterThan<T>(T t)
{
    IList<T> list = GetList<T>();
    var items = () => {
        foreach (var item in list)
            if (fun.Invoke(item))
                yield return item; // This is not allowed by C#
    }

    return items.ToList();
}

Derleyici aynı anda bunu aşağıdaki gibi bir şeye dönüştürmek istiyor:

// inner class
private class Magic
{
    private T t;
    private IList<T> list;
    private Magic(List<T> list, T t) { this.list = list; this.t = t;}

    public IEnumerable<T> DoIt()
    {
        var items = () => {
            foreach (var item in list)
                if (fun.Invoke(item))
                    yield return item;
        }
    }
}

public IList<T> GreaterThan<T>(T t)
{
    var magic = new Magic(GetList<T>(), t)
    var items = magic.DoIt();
    return items.ToList();
}

ve aynı zamanda yineleyici yönü, küçük bir durum makinesi yapmak için işini yapmaya çalışıyor. Bazı basit örnekler, makul miktarda akıl sağlığı kontrolü ile işe yarayabilir (ilk önce (muhtemelen keyfi olarak) iç içe geçmiş kapanışlarla ilgilenir), sonra ortaya çıkan en alt düzey sınıfların yineleyici durum makinelerine dönüştürülüp dönüştürülemeyeceğini görmek.

Ancak bu

  1. Oldukça fazla iş.
  2. En azından yineleyici blok yönü, kapanış yönünün verimlilik için belirli dönüşümleri uygulamasını engelleyebilmesi (tam teşekküllü bir kapanış sınıfı yerine yerel değişkenleri örnek değişkenlerine yükseltmek gibi) olmadan tüm durumlarda muhtemelen çalışamaz.
    • Uygulanmasının imkansız olduğu veya yeterince zor olduğu yerlerde küçük bir çakışma şansı bile olsaydı, ortaya çıkan destek sorunlarının sayısı büyük olasılıkla yüksek olurdu çünkü ince kırılma değişikliği birçok kullanıcı için kaybedilirdi.
  3. Etrafında çok kolay bir şekilde çalışılabilir.

Örneğinizde şöyle:

public IList<T> Find<T>(Expression<Func<T, bool>> expression) 
    where T : class, new()
{
    return FindInner(expression).ToList();
}

private IEnumerable<T> FindInner<T>(Expression<Func<T, bool>> expression) 
    where T : class, new()
{
    IList<T> list = GetList<T>();
    var fun = expression.Compile();
    foreach (var item in list)
        if (fun.Invoke(item))
            yield return item;
}

2
Derleyicinin tüm kapanışları kaldırdıktan sonra olağan yineleyici dönüşümünü yapamamasının açık bir nedeni yoktur. Gerçekten biraz zorluk çıkaracak bir vaka biliyor musunuz? Btw, Magicsınıfınız olmalı Magic<T>.
Qwertie

4

Maalesef buna neden izin vermediklerini bilmiyorum, çünkü bunun nasıl çalışacağını tahmin etmek elbette tamamen mümkün.

Bununla birlikte, anonim yöntemler, yerel değişkenlerle ilgilenip ilgilenmediğine bağlı olarak, yöntemin mevcut sınıftaki bir yönteme veya hatta tamamen yeni bir sınıfa çıkarılması anlamında zaten "derleyici büyüsünün" bir parçasıdır.

Ek olarak, kullanarak yineleme yöntemleri yield de derleyici sihri kullanılarak gerçekleştirilir.

Tahminimce, bu ikisinden biri kodu diğer sihir parçası için tanımlanamaz hale getiriyor ve bu çalışmayı C # derleyicisinin mevcut sürümleri için yapmaya zaman harcamamaya karar verildi. Elbette bilinçli bir seçim olmayabilir ve işe yaramıyor çünkü kimse onu uygulamayı düşünmedi.

% 100 doğru bir soru için Microsoft Connect sitesini kullanmanızı ve bir soruyu bildirmenizi öneririm , karşılığında kullanılabilir bir şey alacağınızdan eminim.


1

Bunu yapardım:

IList<T> list = GetList<T>();
var fun = expression.Compile();

return list.Where(item => fun.Invoke(item)).ToList();

Elbette Linq yöntemi için .NET 3.5'ten başvurulan System.Core.dll'ye ihtiyacınız var. Ve şunları içerir:

using System.Linq;

Şerefe,

Sinsi


0

Belki bu sadece bir sözdizimi sınırlamasıdır. C # ile çok benzer olan Visual Basic .NET'te, yazmak zor olsa da mükemmel bir şekilde mümkündür.

Sub Main()
    Console.Write("x: ")
    Dim x = CInt(Console.ReadLine())
    For Each elem In Iterator Function()
                         Dim i = x
                         Do
                             Yield i
                             i += 1
                             x -= 1
                         Loop Until i = x + 20
                     End Function()
        Console.WriteLine($"{elem} to {x}")
    Next
    Console.ReadKey()
End Sub

Ayrıca parantezlere de dikkat edin ' here; lambda işlevi Iterator Function... End Function döndürür bir IEnumerable(Of Integer)ama değil böyle bir nesne kendisi. O nesneyi almak için çağrılması gerekir.

[1] tarafından dönüştürülen kod, C # 7.3'te (CS0149) hatalara neden oluyor:

static void Main()
{
    Console.Write("x: ");
    var x = System.Convert.ToInt32(Console.ReadLine());
    // ERROR: CS0149 - Method name expected 
    foreach (var elem in () =>
    {
        var i = x;
        do
        {
            yield return i;
            i += 1;
            x -= 1;
        }
        while (!i == x + 20);
    }())
        Console.WriteLine($"{elem} to {x}");
    Console.ReadKey();
}

Diğer cevaplarda verilen, derleyicinin üstesinden gelmenin zor olduğu nedenine kesinlikle katılmıyorum. Iterator Function()Eğer VB.NET örnekte gördüğünüz özellikle lambda adım adım elde oluşturulur.

VB'de, Iterator anahtar kelime vardır; C # karşılığı yoktur. IMHO, bunun bir C # özelliği olmaması için gerçek bir neden yok.

Yani gerçekten, gerçekten anonim yineleme işlevleri istiyorsanız, şu anda Visual Basic veya (kontrol etmedim) F # kullanın, @ Thomas Levesque'in cevabında Bölüm # 7'nin bir yorumunda belirtildiği gibi (F # için Ctrl + F yapın).

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.