Czystszy sposób na sprawdzenie null W C#? [duplikat]

To pytanie ma już odpowiedź tutaj:

Załóżmy, że mam ten interfejs,

interface IContact
{
    IAddress address { get; set; }
}

interface IAddress
{
    string city { get; set; }
}

class Person : IPerson
{
    public IContact contact { get; set; }
}

class test
{
    private test()
    {
        var person = new Person();
        if (person.contact.address.city != null)
        {
            //this will never work if contact is itself null?
        }
    }
}

Person.Contact.Address.City != null (to działa, aby sprawdzić, czy Miasto jest null, czy nie.)

Jednak ta kontrola nie powiedzie się, jeśli adres, Kontakt lub sama osoba jest null.

Obecnie jedno rozwiązanie Mi się wydawało, że jest to:

if (Person != null && Person.Contact!=null && Person.Contact.Address!= null && Person.Contact.Address.City != null)

{ 
    // Do some stuff here..
}
Czy jest na to sposób?

Naprawdę nie podoba mi się, że null sprawdzanie jest wykonywane jako (something == null). Zamiast tego, czy jest inny miły sposób, aby zrobić coś takiego jak metoda something.IsNull()?

Author: Karthik AMR, 2013-07-16

19 answers

W sposób ogólny możesz użyć drzewa wyrażeń i sprawdzić za pomocą metody rozszerzenia:

if (!person.IsNull(p => p.contact.address.city))
{
    //Nothing is null
}

Pełny kod:

public class IsNullVisitor : ExpressionVisitor
{
    public bool IsNull { get; private set; }
    public object CurrentObject { get; set; }

    protected override Expression VisitMember(MemberExpression node)
    {
        base.VisitMember(node);
        if (CheckNull())
        {
            return node;
        }

        var member = (PropertyInfo)node.Member;
        CurrentObject = member.GetValue(CurrentObject,null);
        CheckNull();
        return node;
    }

    private bool CheckNull()
    {
        if (CurrentObject == null)
        {
            IsNull = true;
        }
        return IsNull;
    }
}

public static class Helper
{
    public static bool IsNull<T>(this T root,Expression<Func<T, object>> getter)
    {
        var visitor = new IsNullVisitor();
        visitor.CurrentObject = root;
        visitor.Visit(getter);
        return visitor.IsNull;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Person nullPerson = null;
        var isNull_0 = nullPerson.IsNull(p => p.contact.address.city);
        var isNull_1 = new Person().IsNull(p => p.contact.address.city);
        var isNull_2 = new Person { contact = new Contact() }.IsNull(p => p.contact.address.city);
        var isNull_3 =  new Person { contact = new Contact { address = new Address() } }.IsNull(p => p.contact.address.city);
        var notnull = new Person { contact = new Contact { address = new Address { city = "LONDON" } } }.IsNull(p => p.contact.address.city);
    }
}
 237
Author: Toto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-17 22:32:40

Twój kod może mieć większe problemy niż konieczność sprawdzania referencji null. W obecnej sytuacji, prawdopodobnie naruszasz prawo Demeter.

Prawo Demeter jest jedną z tych heurystyk, jak Don ' t Repeat Yourself, które pomagają w pisaniu łatwego do utrzymania kodu. Mówi programistom, aby nie mieli dostępu do niczego zbyt daleko od najbliższego zasięgu. Na przykład, załóżmy, że mam ten kod:

public interface BusinessData {
  public decimal Money { get; set; }
}

public class BusinessCalculator : ICalculator {
  public BusinessData CalculateMoney() {
    // snip
  }
}

public BusinessController : IController {
  public void DoAnAction() {
    var businessDA = new BusinessCalculator().CalculateMoney();
    Console.WriteLine(businessDA.Money * 100d);
  }
}

Metoda DoAnAction narusza prawo Demeter. W jednej funkcji, posiada dostęp do a BusinessCalcualtor, a BusinessData I a decimal. Oznacza to, że jeśli którakolwiek z poniższych zmian zostanie dokonana, linia będzie musiała zostać refakturowana:

  • Typ powrotu BusinessCalculator.CalculateMoney() Zmienia się.
  • typ BusinessData.Money zmienia się

Biorąc pod uwagę sytuację w had, zmiany te są raczej prawdopodobne. Jeśli taki kod jest zapisywany w bazie kodowej, wprowadzanie tych zmian może stać się bardzo kosztowne. Poza tym oznacza to, że twoje BusinessController jest połączone z BusinessCalculator i typy BusinessData.

Jednym ze sposobów uniknięcia tej sytuacji jest przepisanie kodu w ten sposób:

public class BusinessCalculator : ICalculator {
  private BusinessData CalculateMoney() {
    // snip
  }
  public decimal CalculateCents() {
    return CalculateMoney().Money * 100d;
  }
}

public BusinessController : IController {
  public void DoAnAction() {
    Console.WriteLine(new BusinessCalculator().CalculateCents());
  }
}

Teraz, jeśli dokonasz jednej z powyższych zmian, musisz tylko refaktorować jeszcze jeden fragment kodu, metodę BusinessCalculator.CalculateCents(). Wyeliminowałeś również zależność BusinessController od BusinessData.


Twój kod cierpi na podobny problem:

interface IContact
{
    IAddress address { get; set; }
}

interface IAddress
{
    string city { get; set; }
}

class Person : IPerson
{
    public IContact contact { get; set; }
}

class Test {
  public void Main() {
    var contact = new Person().contact;
    var address = contact.address;
    var city = address.city;
    Console.WriteLine(city);
  }
}

Jeśli którakolwiek z poniższych zmian zostanie dokonana, będziesz musiał refaktorować główną metodę, którą napisałem lub sprawdzić null napisał:

  • rodzaj IPerson.contact zmienia się
  • typ IContact.address zmienia się
  • rodzaj IAddress.city zmienia się

Myślę, że powinieneś rozważyć głębszą refaktoryzację kodu niż zwykłe przepisanie sprawdzenia null.


To powiedziawszy, myślę, że są chwile, w których przestrzeganie prawa Demeter jest niewłaściwe. (Jest przecież heurystyczną, a nie twardą i szybką regułą, mimo że nazywa się ją " prawem.")

W szczególności, myślę, że if:

  1. masz kilka klas, które reprezentują rekordy przechowywane w warstwie trwałości Twojego programu i
  2. Jesteś bardzo pewny, że nie będziesz musiał refaktorować tych klas w przyszłości.]}

Ignorowanie prawa Demetera jest dopuszczalne, gdy mamy do czynienia z tymi klasami. Dzieje się tak dlatego, że reprezentują one dane, z którymi pracuje Twoja aplikacja, więc dotarcie z jednego obiektu danych do drugiego jest sposobem odkrywania informacji w Twojej aplikacji program. W moim powyższym przykładzie sprzężenie spowodowane naruszeniem prawa Demeter było znacznie poważniejsze: sięgałem aż od kontrolera znajdującego się w górnej części stosu, przez kalkulator logiki biznesowej znajdujący się pośrodku stosu, do klasy danych prawdopodobnie w warstwie trwałości.

Wprowadzam ten potencjalny wyjątek do prawa Demeter, ponieważ z nazwami takimi jak Person, Contact, i Address, Twoje klasy wyglądają, jakby były warstwami danych. Jeśli tak, a Ty jesteś niezmiernie pewny, że nigdy nie będziesz musiał ich refaktorować w przyszłości, być może będziesz w stanie uciec od ignorowania prawa Demeter w twojej konkretnej sytuacji.

 61
Author: Kevin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-03 17:37:21

W Twoim przypadku możesz stworzyć nieruchomość dla osoby

public bool HasCity
{
   get 
   { 
     return (this.Contact!=null && this.Contact.Address!= null && this.Contact.Address.City != null); 
   }     
}

Ale nadal musisz sprawdzić, czy osoba jest null

if (person != null && person.HasCity)
{

}

Do twojego innego pytania, dla łańcuchów możesz również sprawdzić, czy null lub empty w ten sposób:

string s = string.Empty;
if (!string.IsNullOrEmpty(s))
{
   // string is not null and not empty
}
if (!string.IsNullOrWhiteSpace(s))
{
   // string is not null, not empty and not contains only white spaces
}
 48
Author: Koryu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-16 09:25:01

Zupełnie inną opcją (która moim zdaniem jest nieużywana) jest null wzorzec obiektu . Trudno powiedzieć, czy ma to sens w konkretnej sytuacji, ale może warto spróbować. Krótko mówiąc, będziesz miał implementację NullContact, implementację NullAddress itd., której używasz zamiast null. W ten sposób możesz pozbyć się większości sprawdzeń null, oczywiście kosztem, który musisz włożyć w projektowanie tych implementacji.

Jak zauważył Adam w jego komentarzu można napisać

if (person.Contact.Address.City is NullCity)

W przypadkach, gdy jest to naprawdę konieczne. Oczywiście ma to sens tylko wtedy, gdy miasto jest naprawdę nietrywialnym obiektem...

Alternatywnie, obiekt null może być zaimplementowany jako singleton (np. spójrz tutaj , aby uzyskać praktyczne instrukcje dotyczące użycia wzorca obiektu null i tutaj , aby uzyskać instrukcje dotyczące singletonów w C#), co pozwala na użycie klasycznego porównania.

if (person.Contact.Address.City == NullCity.Instance)

Osobiście, Wolę takie podejście, ponieważ myślę, że jest łatwiejsze do odczytania dla osób nie zaznajomionych z wzorcem.

 37
Author: bigge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-16 10:04:02

Aktualizacja 28/04/2014: propagacja Null jest planowana dla C# vNext


Są większe problemy niż propagacja sprawdzania null. Celuj w czytelny kod, który może być zrozumiany przez innego programistę i chociaż jest wordy - twój przykład jest w porządku.

Jeśli jest to sprawdzanie wykonywane często, rozważ zamknięcie go wewnątrz klasy Person jako wywołanie Właściwości lub metody.


To powiedziane, bezinteresowne Func i leki generyczne!

Nigdy bym tego nie zrobił, ale oto inna alternatywa:

class NullHelper
{
    public static bool ChainNotNull<TFirst, TSecond, TThird, TFourth>(TFirst item1, Func<TFirst, TSecond> getItem2, Func<TSecond, TThird> getItem3, Func<TThird, TFourth> getItem4)
    {
        if (item1 == null)
            return false;

        var item2 = getItem2(item1);

        if (item2 == null)
            return false;

        var item3 = getItem3(item2);

        if (item3 == null)
            return false;

        var item4 = getItem4(item3);

        if (item4 == null)
            return false;

        return true;
    }
}

Wywołane:

    static void Main(string[] args)
    {
        Person person = new Person { Address = new Address { PostCode = new Postcode { Value = "" } } };

        if (NullHelper.ChainNotNull(person, p => p.Address, a => a.PostCode, p => p.Value))
        {
            Console.WriteLine("Not null");
        }
        else
        {
            Console.WriteLine("null");
        }

        Console.ReadLine();
    }
 26
Author: Adam Houldsworth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-28 08:07:13

W przedmiocie pytania drugiego]}

Naprawdę nie podoba mi się, że sprawdzanie null jest wykonywane jako (coś = = null). Zamiast tego, jest inny miły sposób, aby zrobić coś takiego jak coś.Metoda IsNull ()?

Można rozwiązać za pomocą metody rozszerzenia:

public static class Extensions
{
    public static bool IsNull<T>(this T source) where T : class
    {
        return source == null;
    }
}
 15
Author: MarcinJuraszek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-17 22:23:09

Jeśli z jakiegoś powodu nie masz nic przeciwko jednemu z bardziej "over the top" rozwiązań, możesz sprawdzić rozwiązanie opisane w moim blogu . Używa drzewa wyrażeń, aby sprawdzić, czy wartość jest null przed obliczeniem wyrażenia. Ale aby utrzymać wydajność akceptowalną, tworzy i buforuje kod IL.

Rozwiązanie pozwala na zapisanie tego:

string city = person.NullSafeGet(n => n.Contact.Address.City);
 10
Author: Sandor Drieënhuizen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-16 12:09:17

Możesz napisać:

public static class Extensions
    {
        public static bool IsNull(this object obj)
        {
            return obj == null;
        }
    }

A następnie:

string s = null;
if(s.IsNull())
{

}
Czasami to ma sens. Ale osobiście unikałbym takich rzeczy... ponieważ nie jest jasne, dlaczego można wywołać metodę obiektu, która w rzeczywistości jest null.
 7
Author: Vladimir Gondarev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-16 09:21:54

Zrób to w oddzielnym method Jak:

private test()
{
    var person = new Person();
    if (!IsNull(person))
    {
        // Proceed
              ........

Gdzie Twój IsNull method is

public bool IsNull(Person person)
{
    if(Person != null && 
       Person.Contact != null && 
       Person.Contact.Address != null && 
       Person.Contact.Address.City != null)
          return false;
    return true;
}
 5
Author: Ashok Damani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-17 22:25:15

Potrzebujesz C#, czy chcesz tylko . NET ? Jeśli możesz zmieszać inny język. NET, spójrz na Oxygene . Jest to niesamowity, bardzo nowoczesny język OO, który jest skierowany do. NET (a także Java iCocoa . Tak. Wszystko natywnie, to naprawdę niesamowite toolchain.)

Oxygene ma Operator okrężnicy, który robi dokładnie to, o co prosisz. Cytuję z ich strony różne funkcje językowe :

Operator Dwukropka ( " :")

W Oxygene, jak w wielu Językach to był pod wpływem,, the "."operator służy do wywoływania członków na zajęciach lub obiekt, np.

var x := y.SomeProperty;

Owo "dereferencje" obiektu zawartego w "y", wywołuje (w tym przypadku) właściwość getter i zwraca jej wartość. Jeśli "y" nie jest przypisane (np. "nil"), zostanie wyrzucony wyjątek.

Operator ":" działa w podobny sposób, ale zamiast rzucać wyjątku na nieprzypisanym obiekcie, wynik będzie po prostu bądź zerowy. Dla programistów wywodzących się z Objective-C będzie to znane, gdyż tak też działa metoda Objective-C przy użyciu składni [].

... (snip)

Gdzie": "naprawdę świeci jest przy dostępie do właściwości w łańcuchu, gdzie każdy element może być zerowy. na przykład następujący kod:

var y := MyForm:OkButton:Caption:Length;

Będzie działać bez błędów, a zwraca nil jeśli którykolwiek z obiektów w łańcuchu jest nil-forma, przycisk lub jego podpis.

 4
Author: David M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-11 20:25:57
try
{
  // do some stuff here
}
catch (NullReferenceException e)
{
}

Nie rób tego. Sprawdź wartość null i dowiedz się, z jakim formatowaniem najlepiej żyć.

 3
Author: jwg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-16 09:59:51

Mam rozszerzenie, które może być do tego przydatne; ValueOrDefault (). Przyjmuje polecenie lambda i ocenia je, zwracając wartość ocenianą lub wartość domyślną, jeśli zostaną rzucone jakiekolwiek oczekiwane wyjątki (NRE lub IOE).

    /// <summary>
    /// Provides a null-safe member accessor that will return either the result of the lambda or the specified default value.
    /// </summary>
    /// <typeparam name="TIn">The type of the in.</typeparam>
    /// <typeparam name="TOut">The type of the out.</typeparam>
    /// <param name="input">The input.</param>
    /// <param name="projection">A lambda specifying the value to produce.</param>
    /// <param name="defaultValue">The default value to use if the projection or any parent is null.</param>
    /// <returns>the result of the lambda, or the specified default value if any reference in the lambda is null.</returns>
    public static TOut ValueOrDefault<TIn, TOut>(this TIn input, Func<TIn, TOut> projection, TOut defaultValue)
    {
        try
        {
            var result = projection(input);
            if (result == null) result = defaultValue;
            return result;
        }
        catch (NullReferenceException) //most reference types throw this on a null instance
        {
            return defaultValue;
        }
        catch (InvalidOperationException) //Nullable<T> throws this when accessing Value
        {
            return defaultValue;
        }
    }

    /// <summary>
    /// Provides a null-safe member accessor that will return either the result of the lambda or the default value for the type.
    /// </summary>
    /// <typeparam name="TIn">The type of the in.</typeparam>
    /// <typeparam name="TOut">The type of the out.</typeparam>
    /// <param name="input">The input.</param>
    /// <param name="projection">A lambda specifying the value to produce.</param>
    /// <returns>the result of the lambda, or default(TOut) if any reference in the lambda is null.</returns>
    public static TOut ValueOrDefault<TIn, TOut>(this TIn input, Func<TIn, TOut> projection)
    {
        return input.ValueOrDefault(projection, default(TOut));
    }

Przeciążenie, które nie przyjmuje określonej wartości domyślnej, zwróci null dla dowolnego typu odniesienia. To powinno zadziałać w Twoim scenariuszu:

class test
{
    private test()
    {
        var person = new Person();
        if (person.ValueOrDefault(p=>p.contact.address.city) != null)
        {
            //the above will return null without exception if any member in the chain is null
        }
    }
}
 3
Author: KeithS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-16 15:50:51

Taki łańcuch odniesienia może wystąpić na przykład, jeśli używasz narzędzia ORM i chcesz, aby Twoje klasy były jak najczystsze. W tym scenariuszu myślę, że nie można tego ładnie uniknąć.

Mam następującą metodę rozszerzenia "family", która sprawdza, czy obiekt, na którym jest wywoływany, jest null, a jeśli nie, zwraca jedną z jego żądanych Właściwości lub wykonuje z nią niektóre metody. Działa to oczywiście tylko dla typów referencyjnych, dlatego mam odpowiedni generic ograniczenie.

public static TRet NullOr<T, TRet>(this T obj, Func<T, TRet> getter) where T : class
{
    return obj != null ? getter(obj) : default(TRet);
}

public static void NullOrDo<T>(this T obj, Action<T> action) where T : class
{
    if (obj != null)
        action(obj);
}

Te metody nie dodają prawie żadnych kosztów w porównaniu z rozwiązaniami ręcznymi (brak refleksji, brak drzew wyrażeń), a dzięki nim można uzyskać ładniejszą składnię (IMO).

var city = person.NullOr(e => e.Contact).NullOr(e => e.Address).NullOr(e => e.City);
if (city != null)
    // do something...

Lub metodami:

person.NullOrDo(p => p.GoToWork());

Można jednak zdecydowanie argumentować, że długość kodu nie zmieniła się zbytnio.

 3
Author: Zoltán Tamási,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-29 10:07:13

Moim zdaniem, operator równości nie jest bezpieczniejszym i lepszym sposobem dla równości odniesienia.

Zawsze lepiej używać ReferenceEquals(obj, null). To zawsze zadziała. Z drugiej strony operator równości ( = = ) może być przeciążony i może sprawdzać, czy wartości są równe zamiast referencji, więc powiem, że ReferenceEquals() jest bezpieczniejszym i lepszym sposobem.
class MyClass {
   static void Main() {
      object o = null;
      object p = null;
      object q = new Object();

      Console.WriteLine(Object.ReferenceEquals(o, p));
      p = q;
      Console.WriteLine(Object.ReferenceEquals(p, q));
      Console.WriteLine(Object.ReferenceEquals(o, p));
   }
}

Odniesienie: artykuł MSDN obiekt.Metoda ReferenceEquals.

Ale także tutaj są moje myśli dla wartości null

  • Ogólnie rzecz biorąc, zwracanie wartości null jest najlepszym pomysłem, jeśli ktoś próbuje wskazać, że nie ma danych.

  • Jeśli obiekt nie jest null, lecz pusty, oznacza to, że dane zostały zwrócone, podczas gdy zwracanie null wyraźnie wskazuje, że nic nie zostało zwrócone.

  • Również IMO, jeśli zwrócisz null, spowoduje to wyjątek null, jeśli spróbujesz uzyskać dostęp do członków w obiekcie, co może być przydatne do podświetlania kod buggy.

W C# istnieją dwa różne rodzaje równości:

  • równość odniesienia i
  • równość wartości.

Gdy typ jest niezmienny, użyteczne może być przeciążenie operatora = = w celu porównania równości wartości zamiast równości odniesienia.

Operator nadrzędny = = w typach niezmiennych nie jest zalecany.

Zobacz artykuł MSDNwytyczne dotyczące przeciążenia Equals () i Operator = = (C# Poradnik programowania) Po Więcej Szczegółów.

 2
Author: Microtechie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-17 22:31:37

Mimo że kocham C#, Jest to jedna rzecz, która jest przyjemna w C++ podczas pracy bezpośrednio z instancjami obiektów; niektóre deklaracje po prostu nie mogą Być null, więc nie ma potrzeby sprawdzania null.

Najlepszym sposobem na uzyskanie kawałka tego ciasta w C# (co może być trochę zbytnim przeprojektowaniem z twojej strony - w takim przypadku wybierz inne odpowiedzi) jest użycie struct'S. podczas gdy możesz znaleźć się w sytuacji, w której struktura ma niezaprzeczalne" domyślne " wartości (ie, 0, 0.0, null string) nigdy nie ma potrzeby sprawdzania "if (myStruct == null)".

Nie przełączyłbym się na nie bez zrozumienia ich użycia, oczywiście. Zwykle są one używane do typów wartości, a nie do dużych bloków danych - za każdym razem, gdy przypisujesz strukturę z jednej zmiennej do drugiej, zazwyczaj kopiujesz dane, zasadniczo tworząc kopię każdej z wartości oryginału (możesz tego uniknąć za pomocą słowa kluczowego ref - przeczytaj na nim, a nie tylko po prostu przeczytaj korzystanie z niego). Mimo to może pasować do rzeczy takich jak StreetAddress - na pewno nie leniwie używałbym go na czymś, czego nie chciałem anulować.
 1
Author: Katana314,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-16 13:39:17

W zależności od celu użycia zmiennej "city", czystszym sposobem może być rozdzielenie sprawdzeń null na różne klasy. W ten sposób nie naruszyłbyś prawa Demeter. Więc zamiast:

if (person != null && person.contact != null && person.contact.address != null && person.contact.address.city != null)
{ 
    // do some stuff here..
}

Miałbyś:

class test
{
    private test()
    {
        var person = new Person();
        if (person != null)
        {
            person.doSomething();
        }
    }
}

...

/* Person class */
doSomething() 
{
    if (contact != null)
    {
        contact.doSomething();
    }
}

...

/* Contact class */
doSomething()
{
    if (address != null) 
    {
        address.doSomething();
    }
}

...

/* Address class */
doSomething()
{
    if (city != null)
    {
        // do something with city
    }
}

Ponownie, to zależy od celu programu.

 1
Author: Thomas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-16 20:43:12

W jakich okolicznościach te rzeczy mogą być zerowe? Jeśli null wskazywałoby błąd w kodzie, można by użyć kontraktów kodowych. Odbiorą go, jeśli podczas testowania otrzymasz null, a następnie odejdzie w wersji produkcyjnej. Coś takiego:

using System.Diagnostics.Contracts;

[ContractClass(typeof(IContactContract))]
interface IContact
{
    IAddress address { get; set; }
}

[ContractClassFor(typeof(IContact))]
internal abstract class IContactContract: IContact
{
    IAddress address
    {
        get
        {
            Contract.Ensures(Contract.Result<IAddress>() != null);
            return default(IAddress); // dummy return
        }
    }
}

[ContractClass(typeof(IAddressContract))]
interface IAddress
{
    string city { get; set; }
}

[ContractClassFor(typeof(IAddress))]
internal abstract class IAddressContract: IAddress
{
    string city
    {
        get
        {
            Contract.Ensures(Contract.Result<string>() != null);
            return default(string); // dummy return
        }
    }
}

class Person
{
    [ContractInvariantMethod]
    protected void ObjectInvariant()
    {
        Contract.Invariant(contact != null);
    }
    public IContact contact { get; set; }
}

class test
{
    private test()
    {
        var person = new Person();
        Contract.Assert(person != null);
        if (person.contact.address.city != null)
        {
            // If you get here, person cannot be null, person.contact cannot be null
            // person.contact.address cannot be null and person.contact.address.city     cannot be null. 
        }
    }
}

Oczywiście, jeśli możliwe nulle pochodzą z innego miejsca, musisz mieć już uwarunkowane dane. A jeśli któraś z wartości Null jest ważna, to nie powinieneś wprowadzać wartości NULL jako części umowy, musisz przetestować dla nich i odpowiednio je obsługiwać.

 1
Author: digitig,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-16 20:46:25

Jednym ze sposobów na usunięcie sprawdzeń null w metodach jest zamknięcie ich funkcji w innym miejscu. Jednym ze sposobów na to jest poprzez getters i setters. Na przykład zamiast tego:

class Person : IPerson
{
    public IContact contact { get; set; }
}

Zrób to:

class Person : IPerson
{
    public IContact contact 
    { 
        get
        {
            // This initializes the property if it is null. 
            // That way, anytime you access the property "contact" in your code, 
            // it will check to see if it is null and initialize if needed.
            if(_contact == null)
            {
                _contact = new Contact();
            }
            return _contact;
        } 
        set
        {
            _contact = value;
        } 
    }
    private IContact _contact;
}

Wtedy, gdy nazwiesz " osobę.contact", kod w metodzie "get" zostanie uruchomiony, inicjalizując w ten sposób wartość, jeśli jest null.

Możesz zastosować tę samą metodologię do wszystkich właściwości, które mogą być null we wszystkich Twoich typach. Korzyści dla takie podejście polega na tym, że 1) zapobiega konieczności wykonywania kontroli zerowych w linii i 2) sprawia, że kod jest bardziej czytelny i mniej podatny na błędy kopiowania i wklejania.

Należy jednak zauważyć, że jeśli znajdziesz się w sytuacji, w której musisz wykonać jakąś akcję, jeśli jedna z właściwości jest null (tzn. czy osoba z kontaktem null rzeczywiście coś znaczy w Twojej domenie?), wówczas takie podejście będzie raczej przeszkodą niż pomocą. Jeśli jednak właściwości w pytanie powinno nigdy nie być null, wtedy takie podejście da ci bardzo czysty sposób przedstawienia tego faktu.

--jtlovetteiii

 0
Author: jtlovetteiii,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-18 12:32:09

Możesz użyć reflection, aby uniknąć wymuszania implementacji interfejsów i dodatkowego kodu w każdej klasie. Po prostu Klasa Pomocnicza ze statycznymi metodami. To może nie być najskuteczniejszy sposób, bądź delikatny dla mnie, jestem dziewicą (Czytaj, noob)..

public class Helper
{
    public static bool IsNull(object o, params string[] prop)
    {
        if (o == null)
            return true;

        var v = o;
        foreach (string s in prop)
        {
            PropertyInfo pi = v.GetType().GetProperty(s); //Set flags if not only public props
            v = (pi != null)? pi.GetValue(v, null) : null;
            if (v == null)
                return true;                                
        }

        return false;
    }
}

    //In use
    isNull = Helper.IsNull(p, "ContactPerson", "TheCity");

Oczywiście jeśli masz literówkę w propnames, wynik będzie błędny (najprawdopodobniej)..

 0
Author: TDull,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-20 10:12:05