Salt Okunur Nesneyi Döndüren En İyi Uygulamalar


11

C # 'da OOP hakkında "en iyi uygulamalar" soru var (ama tüm diller için geçerlidir).

Herkese açık olacak nesneyi içeren kütüphane sınıfına sahip olmayı düşünün, mülk erişimi aracılığıyla söyleyin, ancak halkın (bu kütüphane sınıfını kullanan kişiler) bunu değiştirmesini istemiyoruz.

class A
{
    // Note: List is just example, I am interested in objects in general.
    private List<string> _items = new List<string>() { "hello" }

    public List<string> Items
    {
        get
        {
            // Option A (not read-only), can be modified from outside:
            return _items;

            // Option B (sort-of read-only):
            return new List<string>( _items );

            // Option C, must change return type to ReadOnlyCollection<string>
            return new ReadOnlyCollection<string>( _items );
        }
    }
}

Açıkçası en iyi yaklaşım "seçenek C" dir, ancak çok az nesne ReadOnly değişkenine sahiptir (ve kesinlikle hiçbir kullanıcı tanımlı sınıf yoktur).

A sınıfının kullanıcısı olsaydınız,

List<string> someList = ( new A() ).Items;

orijinal (A) nesneye yayılır mı? Yoksa yorumlarda / belgelerde yazılması şartıyla bir klonu iade etmek uygun mudur? Bu klon yaklaşımının izlemesi oldukça zor hatalara yol açabileceğini düşünüyorum.

C ++ 'da const nesnelerini döndürebildiğimizi ve sadece const olarak işaretlenmiş yöntemleri çağırabileceğinizi hatırlıyorum. C # böyle bir özellik / desen yoktur sanırım? Değilse neden eklemiyorlar? Ben buna const doğruluğu denildiğine inanıyorum.

Ama sonra tekrar ana sorum “ne bekliyorsunuz” ya da Seçenek A ve B'nin nasıl ele alınacağıyla ilgili.


2
.NET 4.5 için yeni değiştirilemez koleksiyonların bu önizlemesi hakkında bu makaleye göz atın: blogs.msdn.com/b/bclteam/archive/2012/12/18/… Bunları zaten NuGet aracılığıyla alabilirsiniz.
Kristof Claes

Yanıtlar:


10

Koleksiyonlar için en iyi çözüm olan @ Jesse'nin cevabının yanı sıra: genel fikir, genel A arayüzünüzü sadece geri dönecek şekilde tasarlamaktır

  • değer türleri (int, double, struct gibi)

  • değişmez nesneler ("dize" gibi veya yalnızca A sınıfınız gibi salt okunur yöntemler sunan kullanıcı tanımlı sınıflar)

  • (yalnızca yapmanız gerekiyorsa): "Seçenek B" nin gösterdiği gibi nesne kopyaları

IEnumerable<immutable_type_here> tek başına değişmez, bu yüzden bu sadece yukarıdakilerin özel bir örneğidir.


19

Ayrıca, arabirimlere programlama yapmanız gerektiğini ve büyük ölçüde, bu özellikten ne dönmek istediğinizi bir IEnumerable<string>. A List<string>biraz hayır-hayır çünkü genel olarak değişkendir ve o kadar ileri gideceğim ki ReadOnlyCollection<string>, bir uygulayıcı olarak ICollection<string>Liskov İkame İlkesini ihlal ediyormuş gibi hissederim.


6
+1, anı kullanmak IEnumerable<string>tam olarak burada istediği şeydir.
Doc Brown

2
.Net 4.5'te de kullanabilirsiniz IReadOnlyList<T>.
svick

3
İlgili diğer taraflar için not. Özellik erişimcisi göz önüne alındığında: return _list (+ IEnumerable'a dolaylı döküm) yaparak List <string> yerine IEnumerable <string> döndürürseniz, bu liste <string> Listesine geri döndürülebilir ve değişiklikler değiştirilir ana nesneye yayılır. Dahili listeyi koruyacak yeni Liste <string> (_list) (+ IEnumerable'a ardışık örtülü yayın) döndürebilirsiniz.
Bununla

1
Bir koleksiyonun dizin tarafından erişmesi gereken herhangi bir alıcının, verileri bunu kolaylaştıran bir türe kopyalamak için hazırlanmasını istemek daha iyi mi yoksa koleksiyonu döndüren kodun, onu bir formda tedarik etmesini istemek daha mı iyi? dizin tarafından erişilebilir mi? Tedarikçinin endekse erişimi kolaylaştırmayan bir forma sahip olması muhtemel değilse, alıcıları kendi yedek kopyasını alma ihtiyacından kurtarmanın değerinin, tedarikçiyi tedarik etmek zorunda kalmamaktan kurtardığını düşünüyorum. rasgele erişimli bir form (ör. salt okunur IList<T>)
supercat

2
IEnumerable hakkında sevmediğim bir şey, koroutin olarak çalışıp çalışmadığını veya sadece bir veri nesnesi olup olmadığını asla bilemezsiniz. Bir koroutin olarak çalışırsa, ince hatalara neden olabilir, bu yüzden bazen bilmek iyidir. Bu yüzden ReadOnlyCollection veya IReadOnlyList vs. tercih etme eğilimindeyim
Steve Vermeulen

3

Bir süre önce benzer bir sorunla karşılaştım ve aşağıda benim çözümüm vardı, ama gerçekten sadece kütüphaneyi de kontrol ederseniz çalışır:


Sınıfınızla başlayın A

public class A
{
    private int _n;
    public int N
    {
        get { return _n; }
        set { _n = value; }
    }
}

Daha sonra, özelliklerin yalnızca get kısmını (ve herhangi bir okuma yöntemini) içeren bir arayüz elde edin ve

public interface IReadOnlyA
{
    public int N { get; }
}
public class A : IReadOnlyA
{
    private int _n;
    public int N
    {
        get { return _n; }
        set { _n = value; }
    }
}

Tabii ki salt okunur sürümü kullanmak istediğinizde, basit bir yayın yeterlidir. C çözümünün tam olarak bu olduğunu, ancak elde ettiğim yöntemi sunacağımı düşündüm


2
Bununla ilgili sorun, onu değişebilir versiyona geri döndürmenin mümkün olmasıdır. Ve LSP'nin ihlali, temel sınıfın (bu durumda arayüzün) yöntemleri ile gözlemlenebilen bacause durumu alt sınıftan yeni yöntemlerle değiştirilebilir. Ama en azından, zorlamasa bile niyetle iletişim kurar.
user470365

1

Bu soruyu bulan ve hala cevapla ilgilenen herkes için - şimdi ortaya koyan başka bir seçenek var ImmutableArray<T>. Bunlar neden daha iyi olduğunu düşünüyorum birkaç nokta IEnumerable<T>veya ReadOnlyCollection<T>:

  • değişmez olduğunu açıkça ifade eder (etkileyici API)
  • koleksiyonun halihazırda oluşturulduğunu açıkça belirtiyor (başka bir deyişle tembel olarak başlatılmıyor ve birden fazla kez yinelemenin bir sakıncası yok)
  • öğelerin sayısı biliniyorsa gibi, söz konusu knowlegde gizlemez IEnumerable<T>yapar
  • müşteri neyin uygulanacağını bilmediğinden IEnumerableveya IReadOnlyCollecthiçbir zaman değişmediğini varsayamaz (başka bir deyişle, bu koleksiyona referans değişmeden koleksiyondaki öğelerin değiştirilmediğine dair bir garanti yoktur)

Ayrıca APi'nin koleksiyondaki değişiklikler hakkında istemciyi bilgilendirmesi gerekiyorsa, bir etkinliği ayrı bir üye olarak göstereceğim.

Bunun IEnumerable<T>kötü olduğunu söylemediğimi unutmayın . Koleksiyonu bir argüman olarak geçerken veya tembel olarak başlatılan koleksiyonu döndürürken hala kullanırım (bu özel durumda, henüz bilinmediği için öğe sayısını gizlemek mantıklıdır).

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.