IEnumerable'ın olası çoklu numaralandırması için kullanım uyarısı


359

Benim kod IEnumerable<>birkaç kez kullanmak gerekir böylece "Olası çoklu numaralandırma" Resharper hata olsun IEnumerable.

Basit kod:

public List<object> Foo(IEnumerable<object> objects)
{
    if (objects == null || !objects.Any())
        throw new ArgumentException();

    var firstObject = objects.First();
    var list = DoSomeThing(firstObject);        
    var secondList = DoSomeThingElse(objects);
    list.AddRange(secondList);

    return list;
}
  • objectsParametreyi değiştirebilir Listve sonra olası çoklu numaralandırmadan kaçınabilirim, ancak işleyebileceğim en yüksek nesneyi alamıyorum.
  • Bunu yapabilirim başka bir şey dönüştürmek olduğunu IEnumerableiçin Listyöntemin başında:

 public List<object> Foo(IEnumerable<object> objects)
 {
    var objectList = objects.ToList();
    // ...
 }

Ama bu sadece garip .

Bu senaryoda ne yapardınız?

Yanıtlar:


471

IEnumerableParametre olarak alma problemi , arayanlara "Bunu numaralandırmak istiyorum" demesidir. Kaç kez numaralandırmak istediğinizi söylemez.

Nesneler parametresini Liste olarak değiştirebilir ve sonra olası çoklu numaralandırmadan kaçınabilirim, ancak işleyebileceğim en yüksek nesneyi alamıyorum .

En yüksek nesneyi almanın hedefi asildir, ancak çok fazla varsayım için yer bırakır. Birisinin bu metoda LINQ'dan SQL sorgusuna geçmesini gerçekten istiyor musunuz, sadece iki kez numaralandırmanız için (her seferinde potansiyel olarak farklı sonuçlar elde etmek için?)

Burada semantik eksik, belki de yöntemin ayrıntılarını okumak için zaman almayan bir arayanın sadece bir kez yinelediğini varsayabilir - böylece size pahalı bir nesne iletirler. Yöntem imzanız her iki şekilde de gösterilmez.

Yöntem imzasını IList/ olarak değiştirerek, ICollectionen azından arayan kişiye beklentilerinizin ne olduğunu daha net hale getireceksiniz ve masraflı hatalardan kaçınabilirler.

Aksi takdirde, yönteme bakan çoğu geliştirici yalnızca bir kez yinelediğinizi varsayabilir. A almak IEnumerableçok önemliyse, .ToList()yöntemin başlangıcında yapmayı düşünmelisiniz .

Bu utanç verici. NET IEnumerable + Count + Indexer, Ekle / Kaldır vb. Yöntemleri olmadan bir arabirim yok, hangi ben bu sorunu çözmek şüpheli.


32
ReadOnlyCollection <T> istediğiniz arayüz gereksinimlerini karşılıyor mu? msdn.microsoft.com/tr-tr/library/ms132474.aspx
Dan Firelight Tarafından Fiddling

74
@DanNeely IReadOnlyCollection(T)(.net 4.5 ile yeni) bir IEnumerable(T)kez daha sayılması amaçlanan bir fikir olduğunu iletmek için en iyi arayüz olarak öneririm . Bu cevabın belirttiği gibi, IEnumerable(T)kendisi o kadar geneldir ki, yan etkiler olmadan tekrar numaralandırılamayan yeniden boyutlandırılamayan içeriğe bile atıfta bulunabilir. Ancak IReadOnlyCollection(T)tekrarlanabilirlik anlamına gelir.
binki

1
.ToList()Yöntemin başlangıcında bunu yaparsanız, öncelikle parametrenin null olup olmadığını kontrol etmeniz gerekir. Bu, bir ArgumentException öğesi attığınız iki yer alacağınız anlamına gelir: parametre null ise ve herhangi bir öğesi yoksa.
comecme

Okuduğum bu yazıyı anlamlarını da IReadOnlyCollection<T>vs IEnumerable<T>ve daha sonra yayınlanan bir soru kullanmayla ilgili IReadOnlyCollection<T>bir parametre olarak ve hangi durumlarda içinde. Sonunda geldiğim sonucun takip edilmesi mantığı olduğunu düşünüyorum . Birden fazla numaralandırmanın olabileceği yer değil , numaralandırmanın beklendiği yerde kullanılması gerektiğidir.
Neo

32

Verileriniz her zaman tekrarlanabilir olacaksa, belki de endişelenmeyin. Bununla birlikte, bunu da açabilirsiniz - bu, özellikle gelen veriler büyükse (örneğin, disk / ağdan okuma) faydalıdır:

if(objects == null) throw new ArgumentException();
using(var iter = objects.GetEnumerator()) {
    if(!iter.MoveNext()) throw new ArgumentException();

    var firstObject = iter.Current;
    var list = DoSomeThing(firstObject);  

    while(iter.MoveNext()) {
        list.Add(DoSomeThingElse(iter.Current));
    }
    return list;
}

Not DoSomethingElse'in semantiğini biraz değiştirdim, ancak bu esas olarak kaydedilmemiş kullanımı göstermek için. Örneğin yineleyiciyi yeniden sarabilirsiniz. Siz de güzel bir yineleyici blok yapabilirsiniz; o zaman hayır list- ve yield returniade edilecek bir listeye eklemek yerine, öğeleri aldığınız gibi yaparsınız.


4
+1 Peki bu çok güzel bir kod, ama kodun güzelliğini ve okunabilirliğini yitirdim.
gdoron Monica'yı

5
@ gdoron her şey hoş değil; p Ben yukarıdaki okunamaz olsa da. Özellikle bir iteratör bloğuna yapılırsa - oldukça sevimli, IMO.
Marc Gravell

Ben sadece şunu yazabilirim: "var objectList = objects.ToList ();" ve tüm Numaralandırıcı işlemlerini kaydedin.
gdoron Monica'yı

10
@gdoron açıkça bunu yapmak istemediğinizi söylediğiniz soruda; p Ayrıca, çok büyük (potansiyel olarak, sonsuz - dizilerin sonlu olması gerekmiyorsa, bir ToList () yaklaşımı uygun olmayabilir, ancak hala yinelenebilir). Sonuçta, sadece bir seçenek sunuyorum. Garanti kapsamında olup olmadığını görmek için tüm içeriği yalnızca siz bilirsiniz. Verileriniz tekrarlanabilir durumdaysa geçerli başka bir seçenek: hiçbir şeyi değiştirmeyin! Bunun yerine R #'ı yoksay ... hepsi bağlama bağlıdır.
Marc Gravell

1
Bence Marc'ın çözümü gayet güzel. 1. 'Liste'nin nasıl başlatıldığı çok açık, listenin nasıl yinelendiği çok açık ve listenin hangi üyelerinin üzerinde çalışıldığı çok açık.
Keith Hoffman

9

Kullanılması IReadOnlyCollection<T>veya IReadOnlyList<T>yerine yöntem imzası IEnumerable<T>, sen iterating önce sayımı kontrol etmek gerekebilir veya başka bir nedenden dolayı birden çok kez yinelemek için bu açık hale avantajına sahiptir.

Bununla birlikte, kodlarınızı arabirimleri kullanmak için yeniden düzenlemeye çalışırsanız, örneğin daha dinamik ve dinamik proxy'ye daha kolay hale getirmek için sorunlara neden olacak büyük bir dezavantajı vardır. Kilit nokta, diğer koleksiyonlardan ve bunların ilgili salt okunur arabirimlerinden IList<T>miras kalmamasıIReadOnlyList<T> ve benzer şekilde olmasıdır. (Kısacası, bunun nedeni .NET 4.5'in önceki sürümlerle ABI uyumluluğunu korumak istemesidir. Ancak .NET çekirdeğinde bunu değiştirme fırsatını bile almadılar. )

Bu IList<T>, programın bir bölümünden alırsanız ve programın bir programın beklediği başka bir bölüme iletilmesini IReadOnlyList<T>istiyorsanız, bunu yapamazsınız demektir! Ancak IList<T>birIEnumerable<T> .

Sonunda, IEnumerable<T>tüm toplama arabirimleri de dahil olmak üzere tüm .NET koleksiyonları tarafından desteklenen tek salt okunur arabirimdir. Kendinizi bazı mimari seçimlerden kilitlediğinizi fark ettiğinizde, başka herhangi bir alternatif sizi ısırmaya geri dönecektir. Bu nedenle, salt okunur bir koleksiyon istediğinizi ifade etmek için işlev imzalarında kullanmak için uygun tür olduğunu düşünüyorum.

( IReadOnlyList<T> ToReadOnly<T>(this IList<T> list)Altta yatan tür her iki arabirimi de destekliyorsa , her zaman basit olan bir uzantı yöntemi yazabileceğinizi , ancak yeniden düzenleme yaparken IEnumerable<T>her zaman uyumlu olduğu her yerde manuel olarak eklemeniz gerektiğini unutmayın .)

Her zaman olduğu gibi, bu mutlak değildir, yanlışlıkla çoklu numaralandırmanın felaket olacağı veritabanı ağır kod yazıyorsanız, farklı bir takas tercih edebilirsiniz.


2
+1 Eski bir gönderiye gelmek ve .NET platformunun sağladığı yeni özelliklerle (yani IReadOnlyCollection <T>) bu sorunu çözmenin yeni yollarıyla ilgili belgeler eklemek için
Kevin Avignon

4

Eğer amaç Marc Gravell'in cevabı okuyacak olandan daha fazla numaralandırmayı önlemekse, aynı semantiği koruyarak artıklığı Anyve Firstçağrıları basitçe kaldırabilir ve devam edebilirsiniz:

public List<object> Foo(IEnumerable<object> objects)
{
    if (objects == null)
        throw new ArgumentNullException("objects");

    var first = objects.FirstOrDefault();

    if (first == null)
        throw new ArgumentException(
            "Empty enumerable not supported.", 
            "objects");

    var list = DoSomeThing(first);  

    var secondList = DoSomeThingElse(objects);

    list.AddRange(secondList);

    return list;
}

Bunun IEnumerablegenel olmadığınızı veya en azından bir referans türü olarak kısıtlandığını varsaydığını unutmayın .


2
objectshala bir liste döndüren DoSomethingElse'ye aktarılıyor, bu yüzden hala iki kez numaralandırma olasılığınız hala var. Her iki durumda da koleksiyon bir LINQ to SQL sorgusu ise, DB iki kez vurmak.
Paul Stovell

1
@PaulStovell, Bunu okuyun: "Eğer amaç gerçekten Marc
Gravell'in cevabından

Boş listeler için istisnalar atma konusunda bazı diğer stackoverflow yayınlarında önerildi ve burada önereceğim: neden boş bir listeye bir istisna atmalıyım? Boş bir liste alın, boş bir liste verin. Bir paradigma olarak, bu oldukça basittir. Arayan kişi boş bir listeden geçtiğini bilmiyorsa, sorun var demektir.
Keith Hoffman

@Keith Hoffman, örnek kod OP'nin yayınladığı kodla aynı davranışı sürdürür ve burada kullanımı Firstboş bir liste için bir istisna atar. Boş listeye asla istisna atma ya da boş listeye daima istisna atma demenin mümkün olduğunu düşünmüyorum. Bu bağlıdır ...
João Angelo

Yeterince adil Joao. Düşünceniz için daha fazla yemek, yayınınızın eleştirisinden daha fazla.
Keith Hoffman

3

Bu durumda genellikle yöntemimi IEnumerable ve IList ile aşırı yüklerim.

public static IEnumerable<T> Method<T>( this IList<T> source ){... }

public static IEnumerable<T> Method<T>( this IEnumerable<T> source )
{
    /*input checks on source parameter here*/
    return Method( source.ToList() );
}

IEnumerable'ı çağırmanın bir .ToList () gerçekleştireceği yöntemlerin özet yorumlarında açıklamaya dikkat ediyorum.

Birden fazla işlem birleştiriliyorsa, programcı daha yüksek bir seviyede .ToList () yöntemini seçebilir ve ardından IList aşırı yüklenmesini çağırabilir veya IEnumerable aşırı yükümün bununla ilgilenmesine izin verebilir.


20
Bu çok korkunç.
Mike Chamberlain

1
@MikeChamberlain Evet, aynı zamanda gerçekten korkunç ve tamamen temiz. Ne yazık ki bazı ağır senaryoların bu yaklaşıma ihtiyacı var.
Mauro Sampietro

1
Ama sonra diziyi IEnumerable parameratized yöntemine her ilettiğinizde yeniden yaratabilirsiniz. @MikeChamberlain bu konuda katılıyorum, bu korkunç bir öneri.
Krythic

2
@Krythic Listenin yeniden oluşturulması gerekmiyorsa, başka bir yerde bir ToList gerçekleştirin ve IList aşırı yüklenmesini kullanın. Bu çözümün amacı budur.
Mauro Sampietro

0

Sadece ilk öğeyi kontrol etmeniz gerekiyorsa, tüm koleksiyonu tekrarlamadan göz atabilirsiniz:

public List<object> Foo(IEnumerable<object> objects)
{
    object firstObject;
    if (objects == null || !TryPeek(ref objects, out firstObject))
        throw new ArgumentException();

    var list = DoSomeThing(firstObject);
    var secondList = DoSomeThingElse(objects);
    list.AddRange(secondList);

    return list;
}

public static bool TryPeek<T>(ref IEnumerable<T> source, out T first)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));

    IEnumerator<T> enumerator = source.GetEnumerator();
    if (!enumerator.MoveNext())
    {
        first = default(T);
        source = Enumerable.Empty<T>();
        return false;
    }

    first = enumerator.Current;
    T firstElement = first;
    source = Iterate();
    return true;

    IEnumerable<T> Iterate()
    {
        yield return firstElement;
        using (enumerator)
        {
            while (enumerator.MoveNext())
            {
                yield return enumerator.Current;
            }
        }
    }
}

-3

Öncelikle, bu uyarı her zaman çok fazla bir şey ifade etmez. Genellikle performans şişe boynu olmadığından emin olduktan sonra devre dışı bıraktım. Bu sadece IEnumerableiki kez değerlendirildiği anlamına gelir, evaluationkendisi uzun sürmediği sürece genellikle sorun değildir . Uzun zaman alsa bile, bu durumda ilk kez tek bir eleman kullanıyorsunuz.

Bu senaryoda güçlü linq genişletme yöntemlerinden daha fazla yararlanabilirsiniz.

var firstObject = objects.First();
return DoSomeThing(firstObject).Concat(DoSomeThingElse(objects).ToList();

Bu durumda sadece bir IEnumerablekez biraz güçlükle değerlendirmek, ancak önce profili değerlendirmek ve gerçekten bir sorun olup olmadığını görmek mümkündür.


15
IQueryable ile çok çalışıyorum ve bu tür uyarıları kapatmak çok tehlikelidir.
gdoron Monica'yı

5
Kitaplarımda iki kez değerlendirme yapmak kötü bir şey. Yöntem IEnumerable alır, bir LINQ SQL sorgusu iletmek cazip olabilir, bu da birden çok DB çağrıları ile sonuçlanır. Yöntem imzası iki kez numaralanabileceğini göstermediğinden, imzayı değiştirmeli (IList / ICollection'ı kabul et) veya yalnızca bir kez yinelenmelidir
Paul Stovell

4
erken optimizasyon ve okunabilirliği bozma ve böylece daha sonra bir fark yaratan optimizasyon yapma imkanı kitabımda çok daha kötü.
vidstige

Bir veritabanı sorgusu haline geldiğinde bir kez, emin sadece değerlendirilen oluyor yapmada zaten bir fark yaratıyor. Bu sadece gelecekteki bazı teorik faydalar değil, aynı zamanda ölçülebilir bir gerçek fayda. Sadece bu değil, sadece bir kez değerlendirme performans için sadece yararlı olmakla kalmaz, aynı zamanda doğruluk için de gereklidir. Bir veritabanı sorgusunun iki kez yürütülmesi, biri iki sorgu değerlendirmesi arasında bir güncelleme komutu vermiş olabileceğinden farklı sonuçlar üretebilir.

1
@hvd Ben böyle bir şey tavsiye etmedi. Tabii ki IEnumerablesher yinelemede farklı sonuçlar veren veya yinelemesi gerçekten uzun süren numaralandırmamalısınız . Bkz. Marc Gravells cevabı - Her şeyden önce “belki endişelenmeyin”. Mesele şu ki - Bunu gördüğünüzde endişelenecek bir şeyiniz yok.
vidstige
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.