A sınıfı bir özellik, A sınıfı bir alt nesnenin özelliğine gönderme


9

Sadeleştirildiğinde şöyle görünen bu koda sahibiz:

public class Room
{
    public Client Client { get; set; }

    public long ClientId
    {
        get
        {
            return Client == null ? 0 : Client.Id;
        }
    }
}

public class Client 
{
    public long Id { get; set; }
}

Şimdi üç bakış açımız var.

1) Bu özellik iyi koddur, çünkü Clientözellik her zaman ayarlanmalıdır (yani null değil), bu yüzden Client == nullasla gerçekleşmez ve Kimlik değeri 0yine de yanlış bir kimliği gösterir (bu kodun yazarının görüşüdür ;-))

2) Bunu bilmen arayanın güvenemez 0yanlış değeridir Idve ne zaman Clientözelliği her zaman ayarlanmalıdır Bir atarsam exceptioniçinde getiken Clientözelliği null olur

3) ClientÖzelliğin her zaman ayarlanması gerektiğinde, yalnızca döndürür Client.Idve özellik boş NullRefolduğunda kodun bir istisna atmasına izin verirsiniz Client.

Bunlardan hangisi en doğru? Yoksa dördüncü bir ihtimal var mı?


5
Bir cevap değil, sadece bir not: Hiçbir zaman mülk sahiplerinden istisnalar atmayın.
Brandon

3
Brandon'ın noktasını detaylandırmak için: stackoverflow.com/a/1488488/569777
MetaFight

1
Elbette: Genel fikir ( msdn.microsoft.com/en-us/library/… , stackoverflow.com/questions/1488472/… , diğerleri arasında) özelliklerin mümkün olduğunca az yapılması, hafif olması ve temizliği temsil etmesi gerektiğidir. , nesnelerinizin halka açık durumu, dizinleyiciler küçük bir istisnadır. Hata ayıklama sırasında bir özellik için izleme penceresinde istisnaları görmek de beklenmeyen bir davranıştır.
Brandon

1
Bir mülk alıcısına atanan kodu yazmamalısınız. Bu, bir nesnenin desteklenmeyen bir duruma gelmesi nedeniyle oluşan istisnaları gizlemeniz ve yutmanız gerektiği anlamına gelmez.
Ben

4
Neden sadece Room.Client.Id yapamıyorsunuz? Bunu neden Room.ClientId'e sarmak istiyorsun? Eğer bir müşteri için kontrol etmek o zaman neden Room.HasClient gibi bir şey değil {get {return client == null; }} daha basit ve hala normal Room.Client.Id insanların gerçekten kimliğe ihtiyaç duyduklarında yapmasına izin veriyor?
James

Yanıtlar:


25

RoomSınıfınızın olabileceği durum sayısını sınırlamanız gerektiği gibi kokuyor .

ClientBoş olduğunda ne yapacağınızı sorduğunuz gerçeği, Roomdurum alanının çok büyük olduğu bir ipucudur .

İşleri basit tutmak için Clientherhangi bir Roomörneğin özelliğinin hiç boş kalmasına izin vermezdim . Bu, içindeki kodun asla boş olmadığını Roomkabul edebileceği anlamına gelir Client.

Gelecekte bir sebepten dolayı Clientolur nulldurumu desteklemek güdüsünden. Aksi takdirde bakım karmaşıklığınız artacaktır.

Bunun yerine, kodun hızlı ve başarısız olmasına izin verin. Sonuçta, bu desteklenen bir durum değil. Uygulama bu duruma girerse, dönüşü olmayan bir çizgiyi zaten geçtiniz. O zaman yapılacak tek şey uygulamayı kapatmaktır.

Bu, işlenmemiş bir boş başvuru istisnasının sonucu olarak (doğal olarak) olabilir.


2
Güzel. Ben düşünce Yani null olduğunda ne yapacağını gerçekten iyi soruya daha "Şimdiye kadar boş olduğu herhangi Odası örneğinin İstemci özelliğini izin vermeyeceğini şeyler basit tutmak için" gibi
Michel

@Michel, daha sonra bu gereksinimi değiştirmeye karar verirseniz, nulldeğeri daha sonra mevcut kodunuzla çalışacak ve gerekirse var olmayan bir istemci için davranış tanımlamanıza izin veren bir NullObject(veya daha doğrusu NullClient) ile değiştirebilirsiniz . Soru "müşteri yok" vakasının iş mantığının bir parçası olup olmadığıdır.
null

3
Tamamen doğru. Desteklenmeyen bir durumu desteklemek için tek bir kod karakteri yazmayın. Bir NullRef atmasına izin ver.
Ben

@ Ben katılmıyorum; MS yönergeleri, mülk sahiplerinin istisnalar atmaması gerektiğini açıkça belirtir. Ve bunun yerine, daha anlamlı bir istisna atılabildiğinde null referansların atılmasına izin vermek sadece tembeldir.
Andy

1
@ Kılavuzun nedenini anlamanız ve ne zaman geçerli olmadığını bilmenizi sağlayacaktır.
Ben

21

Sadece birkaç nokta:

a) İstemci örneği için zaten herkese açık bir alıcı olduğunda neden ClientId için özel olarak bir alıcı var? ClientId'ın neden olduğu bilgilerin Room'un imzasında neden longtaşla oyulması gerektiğini anlamıyorum .

b) İkinci görüşle ilgili olarak bir sabit getirebilirsiniz Invalid_Client_Id.

c) Birinci ve üçüncü görüşe ilişkin olarak (ve benim asıl meselem olmak): Bir odanın daima müşterisi olmalı mı? Belki sadece anlambilimsel ama kulağa doğru gelmiyor. Belki de Room ve Client için tamamen ayrı sınıflara ve onları birbirine bağlayan başka bir sınıfa sahip olmak daha uygun olur. Belki Randevu, Rezervasyon, Doluluk? (Bu aslında ne yaptığınıza bağlıdır.) Ve bu sınıfta "bir oda olmalı" ve "bir müşteri olmalı" kısıtlamalarını uygulayabilirsiniz.


5
+1, örneğin, ClientId'ın uzun süredir neden olan bilgilerin neden Odanın imzasına taşınması gerektiğini anlamıyorum
Michel

İngilizcen iyi. ;) Sadece bu cevabı okurken, siz söylemeden anadilinizin İngilizce olmadığını bilemezdim.
jpmc26

B & C puanları iyidir; RE: a, bu bir kolaylık yöntemi ve odanın Müşteriye referans olması gerçeği sadece bir uygulama detayı olabilir (Müşterinin herkesin görebileceği söylenebilir)
Andy

17

Üç görüşe de katılmıyorum. Eğer Clientkutu asla null, o zaman bile mümkün olması için yapmazlarnull !

  1. Değerini ayarlayın Clientkurucusundaki
  2. Yapıcıya bir atın ArgumentNullException.

Yani kodunuz şöyle bir şey olurdu:

public class Room
{
    private Client theClient;

    public Room(Client client) {
        if(client == null) throw new ArgumentNullException();
        this.theClient = client;
    }

    public Client Client { 
        get { return theClient; }
        set 
        {
            if (value == null) 
                throw new ArgumentNullException();
            theClient = value;
        }
    }

    public long ClientId
    {
        get
        {
            return theClient.Id;
        }
    }
}
public class Client 
{
    public long Id { get; set; }
}

Bu kod ilgisi yoktur, ama muhtemelen daha iyi bir kullanma kapalı olacağını değişmez sürümünü Roomverme yoluyla theClient readonlyve sonra eğer yeni bir oda yapma istemci değişiklikler. Bu, yanıtımın diğer yönlerinin sıfır güvenliğine ek olarak kodunuzun iş parçacığı güvenliğini de artıracaktır. Mutable vs immutable hakkındaki bu tartışmaya bakın


1
Bunlar değil ArgumentNullExceptionmi?
Mathieu Guindon

4
Hayır. Program kodu asla a atmamalıdır NullReferenceException, .Net VM tarafından "boş bir nesne başvurusunun kaldırılması denendiğinde " atılır . Bir atma gerektiğini ArgumentNullExceptionatılan edildiği, "null başvuru (Visual Basic hiçbir şey) bir yönteme geçirildiğinde geçerli bir argüman olarak kabul etmez."
Pharap

2
@Pharap Ben C # kodu yazmaya çalışan bir Java programcısıyım, böyle hatalar yapacağımı düşündüm.
durron597

@ durron597 'final' teriminin kullanımından (bu durumda karşılık gelen C # anahtar kelimesinin readonly) olduğunu tahmin etmeliydim . Ben sadece düzenlenmiş olurdu ama bir yorum neden (gelecekteki okuyucular için) daha iyi açıklamak için hizmet hissettim. Bu cevabı henüz vermemiş olsaydınız, aynı çözümü verirdim. Değişmezlik de iyi bir fikirdir, ancak her zaman umduğu kadar kolay değildir.
Pharap

2
Mülkler genellikle istisnalar atmamalıdır; ArgumentNullException öğesini atan bir ayarlayıcı yerine, bunun yerine bir SetClient yöntemi sağlayın. Birisi soruyla ilgili bir cevabın bağlantısını yayınladı.
Andy

5

İkinci ve üçüncü seçeneklerden kaçınılmalıdır - alıcı, üzerinde kontrolü olmayan bir istisna dışında arayanı şaplak atmamalıdır.

İstemcinin boş olup olmayacağına karar vermelisiniz. Öyleyse, bir arayanın erişmeden önce boş olup olmadığını kontrol etmesi için bir yol sağlamalısınız (örn. Bool ClientIsNull özelliği).

İstemcinin hiçbir zaman null olmayacağına karar verirseniz, bunu yapıcı için gerekli bir parametre yapın ve bir null iletilirse istisnayı oraya atın.

Son olarak, ilk seçenek ayrıca bir kod kokusu içerir. Müşterinin kendi kimlik özelliği ile ilgilenmesine izin vermelisiniz. Bir kapsayıcı sınıfındaki bir alıcıyı kodlamak, içerilen sınıftaki bir alıcıyı kodlamak için aşırı doldurulmuş gibi görünüyor. İstemciyi bir mülk olarak göstermeniz (aksi takdirde, bir Müşterinin zaten sunduğu her şeyi çoğaltmanız gerekir ).

long clientId = room.Client.Id;

Müşteri null olabilirse, en azından arayan kişiye sorumluluk verirsiniz:

if (room.Client != null){
    long clientId = room.Client.Id;
    /* other code follows... */
}

1
Bence "Zaten her şeyi teklifler bir istemci çoğaltarak bitireceğiz" kalın ya da en azından italik olmak ihtiyaçlarını.
Pharap

2

Bir null Client özelliği desteklenen bir durumsa , bir NullObject kullanmayı düşünün .

Ancak büyük olasılıkla bu istisnai bir durumdur, bu yüzden bir null ile sonuçlanmayı imkansız (veya çok uygun değil) yapmalısınız Client:

public Client Client { get; private set; }

public Room(Client client)
{
    Client = client;
}

public void SetClient(Client client)
{
    if (client == null) throw new ArgumentNullException();
    Client = client;
}

Ancak desteklenmiyorsa, yarı pişmiş çözeltilerde zaman kaybetmeyin. Bu durumda:

return Client == null ? 0 : Client.Id;

NullReferenceException'ı burada 'büyük bir maliyetle' çözdünüz! Bu, tüm arayanları Room.ClientId0'ı kontrol etmeye zorlar :

var aRoom = new Room(aClient);
var clientId = aRoom.ClientId;
if (clientId == 0) RestartRoombookingWizard();
else ContinueRoombooking(clientid);

Garip hatalar, diğer hatalar (veya kendiniz!) İle bir süre sonra "ErrorCode" dönüş değerini kontrol etmeyi unutabilir.
Güvenli başarısız hızlı tr. NullReferenceException özelliğinin atılmasına izin verin ve arayanın işleri daha da fazla karıştırmasına izin vermek yerine "yığınını" çağrı yığını üzerinde daha yüksek bir yere yakalayın ...

Farklı bir kayda göre, eğer anlatmakClient için istekli iseniz ve ayrıca TellDontAskClientId hakkında düşünün . Nesnelerden ne elde etmek istediğinizi söylemek yerine çok fazla şey sorarsanız, her şeyi bir araya getirerek sona erdirebilir ve daha sonra değişikliği zorlaştırabilirsiniz.


Bu NotSupportedExceptionyanlış kullanılıyor, ArgumentNullExceptionbunun yerine bir kullanıyor olmalısınız .
Pharap

1

Şimdi piyasaya sürülen c # 6.0'dan itibaren, bunu yapmalısınız,

public class Room
{
    public Room(Client client)
    {
        this.Client = client;
    }

    public Client Client { get; }
}

public class Client
{
    public Client(long id)
    {
        this.Id = id;
    }

    public long Id { get; }
}

İçin bir mülk ClientoluşturmakRoom , açık bir kapsülleme ve KURU ilkesi ihlalidir.

Eğer erişmek gerekirse Ida Clienta Roomyapabileceğiniz,

var clientId = room.Client?.Id;

Kullanımına dikkat Boş Koşullu Operatörü , clientIdbir olacak long?eğer Clientolduğunu null, clientIdolacak null, aksi takdirde, clientIddeğerini sahip olacaktır Client.Id.


ama türü clientidnull uzunluğunda mı?
Michel

@Michel iyi, bir odanın bir Müşteri olması gerekiyorsa, ctor'a boş bir kontrol koyun. O var clientId = room.Client.Idzaman hem güvenli hem de long.
Jodrell
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.