Üye koleksiyonlarını sergilemek için ReadOnlyCollection veya IEnumerable?


124

Çağıran kod yalnızca koleksiyon üzerinde yineleniyorsa, dahili bir koleksiyonu bir IEnumerable yerine bir ReadOnlyCollection olarak göstermek için herhangi bir neden var mı?

class Bar
{
    private ICollection<Foo> foos;

    // Which one is to be preferred?
    public IEnumerable<Foo> Foos { ... }
    public ReadOnlyCollection<Foo> Foos { ... }
}


// Calling code:

foreach (var f in bar.Foos)
    DoSomething(f);

Gördüğüm gibi IEnumerable, ReadOnlyCollection arabiriminin bir alt kümesidir ve kullanıcının koleksiyonu değiştirmesine izin vermez. Yani IEnumberable arayüzü yeterliyse, o zaman kullanılacak olan budur. Bu, bunun hakkında akıl yürütmenin uygun bir yolu mu yoksa bir şeyi mi kaçırıyorum?

Teşekkürler / Erik


6
.NET 4.5 kullanıyorsanız, yeni salt okunur koleksiyon arabirimlerini denemek isteyebilirsiniz . Hala ReadOnlyCollectionparanoyak iseniz , geri dönen koleksiyonu bir paketlemek isteyeceksiniz , ancak şimdi belirli bir uygulamaya bağlı değilsiniz.
StriplingWarrior

Yanıtlar:


96

Daha modern çözüm

Dahili koleksiyonun değiştirilebilir olması gerekmedikçe, System.Collections.Immutablepaketi kullanabilir, alan türünüzü değişmez bir koleksiyon olacak şekilde değiştirebilir ve sonra bunu doğrudan ifşa edebilirsiniz - Footabii ki kendi kendine değişmez olduğunu varsayarak .

Soruyu daha doğrudan ele almak için yanıt güncellendi

Çağıran kod yalnızca koleksiyon üzerinde yineleniyorsa, dahili bir koleksiyonu bir IEnumerable yerine bir ReadOnlyCollection olarak göstermek için herhangi bir neden var mı?

Arama koduna ne kadar güvendiğinize bağlıdır. Bu üyeyi arayacak her şey üzerinde tam kontrole sahipseniz ve hiçbir kodun kullanılmayacağını garanti ediyorsanız :

ICollection<Foo> evil = (ICollection<Foo>) bar.Foos;
evil.Add(...);

o zaman elbette, koleksiyonu doğrudan iade etmenizin bir zararı olmayacaktır. Genelde bundan biraz daha paranoyak olmaya çalışırım.

Aynı şekilde, dediğiniz gibi: eğer sadece ihtiyacınız varsa IEnumerable<T>, o zaman neden kendinizi daha güçlü bir şeye bağlayasınız?

Orijinal cevap

.NET 3.5 kullanıyorsanız, basit bir Atla çağrısı yaparak kopya oluşturmaktan ve basit atamadan kaçınabilirsiniz:

public IEnumerable<Foo> Foos {
    get { return foos.Skip(0); }
}

(Önemsiz bir şekilde kaydırmak için pek çok başka seçenek var - Skipfazla Seç / Nerede ile ilgili güzel olan şey , her yineleme için anlamsızca yürütecek bir temsilcinin olmamasıdır.)

.NET 3.5 kullanmıyorsanız, aynı şeyi yapmak için çok basit bir sarmalayıcı yazabilirsiniz:

public static IEnumerable<T> Wrapper<T>(IEnumerable<T> source)
{
    foreach (T element in source)
    {
        yield return element;
    }
}

15
Not bunun için bir performans cezası olduğu: AFAIK Enumerable.Count yöntemi IEnumerables içine dökülmüştür Koleksiyonu için optimize edilir, ancak geçiş tarafından üretilen bir aralık (0)
shojtsy

6
-1 Sadece ben miyim yoksa bu farklı bir sorunun cevabı mı? Bu "çözümü" bilmek yararlıdır, ancak operasyon bir ReadOnlyCollection ve IEnumerable döndürme arasında bir karşılaştırma istedi. Bu yanıt, bu kararı desteklemek için herhangi bir gerekçe olmaksızın IEnumerable'ı döndürmek istediğinizi varsayar.
Zaid Mesud

1
Cevap gösterileri döküm ReadOnlyCollectionbir etmek ICollectionve daha sonra çalışan Addbaşarıyla. Bildiğim kadarıyla bu mümkün olmamalı. evil.Add(...)Bir hata oluşturması gerekir. dotnetfiddle.net/GII2jW
Shaun Luttin

1
@ShaunLuttin: Evet, kesinlikle - bu fırlatır. Ama cevabımda, bir ReadOnlyCollection- IEnumerable<T>aslında salt okunur olmayan bir döküm yapıyorum ... bu bir ifşa etmek yerineReadOnlyCollection
Jon Skeet

1
Aha. Paranoyanız , kötü oyuncuya karşı korunmak için sizi bir ReadOnlyCollectionyerine a kullanmaya yönlendirir IEnumerable.
Shaun Luttin

43

Yalnızca koleksiyonu yinelemeniz gerekiyorsa:

foreach (Foo f in bar.Foos)

sonra IEnumerable'ı döndürmek yeterlidir.

Öğelere rastgele erişime ihtiyacınız varsa:

Foo f = bar.Foos[17];

ardından ReadOnlyCollection'a sarın .


@Vojislav Stojkovic, +1. Bu tavsiye bugün hala geçerli mi?
w0051977

1
@ w0051977 Niyetinize bağlıdır. System.Collections.ImmutableJon Skeet'in önerdiği şekilde kullanmak , referans aldıktan sonra asla değişmeyecek bir şeyi ifşa ettiğinizde tamamen geçerlidir. Öte yandan, değiştirilebilir bir koleksiyonun salt okunur bir görünümünü ortaya koyuyorsanız, bu tavsiye hala geçerlidir, ancak muhtemelen onu IReadOnlyCollection(bunu yazdığımda yoktu) olarak ifşa edeceğim.
Vojislav Stojkovic

@VojislavStojkovic bar.Foos[17]ile kullanamazsınız IReadOnlyCollection. Tek fark ek Countmülktür. public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable
Konrad

@Konrad Doğru, eğer ihtiyacın olursa bar.Foos[17], onu ifşa etmelisin ReadOnlyCollection<Foo>. Boyutu bilmek ve onu yinelemek istiyorsanız, olarak gösterin IReadOnlyCollection<Foo>. Boyutu bilmeniz gerekmiyorsa kullanın IEnumerable<Foo>. Başka çözümler ve başka ayrıntılar da var, ancak bu belirli yanıtın tartışılmasının kapsamı dışındadır.
Vojislav Stojkovic

31

Bunu yaparsanız, arayanların IEnumerable'ı ICollection'a geri göndermesini ve ardından değiştirmesini engelleyen hiçbir şey yoktur. ReadOnlyCollection bu olasılığı ortadan kaldırır, ancak altta yatan yazılabilir koleksiyona yansıma yoluyla erişmek hala mümkündür. Koleksiyon küçükse, bu sorunu aşmanın güvenli ve kolay bir yolu bunun yerine bir kopyasını iade etmektir.


14
Bu, yürekten katılmadığım bir tür "paranoyak tasarım". Bir politikayı uygulamak için yetersiz anlambilim seçmek kötü bir uygulamadır.
Vojislav Stojkovic

24
Aynı fikirde olmaman onu yanlış yapmaz. Harici dağıtım için bir kitaplık tasarlıyorsam, API'yi kötüye kullanmaya çalışan kullanıcılar tarafından ortaya çıkan hatalarla uğraşmak yerine paranoyak bir arayüze sahip olmayı tercih ederim. Msdn.microsoft.com/en-us/library/k2604h5s(VS.71).aspx
Stu Mackellar

5
Evet, harici dağıtım için bir kitaplık tasarlamanın özel durumunda, açığa çıkardığınız her şey için paranoyak bir arayüze sahip olmak mantıklıdır. Yine de, Foos özelliğinin semantiği sıralı erişim ise, ReadOnlyCollection kullanın ve ardından IEnumerable döndürün. Yanlış anlamlara gerek yok;)
Vojislav Stojkovic

9
Senin de yapmana gerek yok. Kopyalamadan salt okunur bir yineleyiciyi iade edebilirsiniz ...
Jon Skeet

1
@shojtsy Kod satırınız çalışmıyor. olarak bir oluşturur null adlı . Oyuncu kadrosu bir istisna atar.
Nick Alexeev

3

ReadOnlyCollection'ı olabildiğince kullanmaktan kaçınırım, aslında normal Listeyi kullanmaktan çok daha yavaştır. Bu örneğe bakın:

List<int> intList = new List<int>();
        //Use a ReadOnlyCollection around the List
        System.Collections.ObjectModel.ReadOnlyCollection<int> mValue = new System.Collections.ObjectModel.ReadOnlyCollection<int>(intList);

        for (int i = 0; i < 100000000; i++)
        {
            intList.Add(i);
        }
        long result = 0;

        //Use normal foreach on the ReadOnlyCollection
        TimeSpan lStart = new TimeSpan(System.DateTime.Now.Ticks);
        foreach (int i in mValue)
            result += i;
        TimeSpan lEnd = new TimeSpan(System.DateTime.Now.Ticks);
        MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
        MessageBox.Show("Result: " + result.ToString());

        //use <list>.ForEach
        lStart = new TimeSpan(System.DateTime.Now.Ticks);
        result = 0;
        intList.ForEach(delegate(int i) { result += i; });
        lEnd = new TimeSpan(System.DateTime.Now.Ticks);
        MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
        MessageBox.Show("Result: " + result.ToString());

15
Erken optimizasyon. Ölçümlerime göre, bir ReadOnlyCollection üzerinde yineleme yapmak , for döngüsü içinde hiçbir şey yapmadığınızı varsayarak, normal bir listeden yaklaşık% 36 daha uzun sürer . Muhtemelen, bu farkı asla fark etmeyeceksiniz, ancak bu, kodunuzda bir sıcak nokta ise ve her son performansı gerektiriyorsa, onu sıkıştırabilirsiniz, bunun yerine neden bir Dizi kullanmıyorsunuz? Bu daha da hızlı olurdu.
StriplingWarrior

Karşılaştırma noktanız kusurlar içeriyor, şube tahminini tamamen görmezden geliyorsunuz.
Trojaner

0

Bazen, belki de birim testi sırasında koleksiyonla dalga geçmek istediğiniz için bir arayüz kullanmak isteyebilirsiniz. Bir adaptör kullanarak ReadonlyCollection'a kendi arayüzünüzü eklemek için lütfen blog girişime bakı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.