Jak zrobić ToString dla potencjalnie null obiektu?

99

Czy istnieje prosty sposób na wykonanie następujących czynności:

String s = myObj == null ? "" : myObj.ToString();

Wiem, że mogę wykonać następujące czynności, ale naprawdę uważam to za hack:

String s = "" + myObj;

Byłoby wspaniale, gdyby Convert.ToString () miał do tego odpowiednie przeciążenie.

codymanix
źródło
3
Nie widzę nic złego w pierwszym. Jeśli uważasz, że druga strona to włamanie, najlepiej jest napisać funkcję narzędzia, która wykonuje sprawdzenie zerowe.
Nick Larsen
plz czy możesz bardziej precyzyjnie odpowiedzieć na swoje pytanie
FosterZ
3
string.Format ("{0}", myObj) akceptuje wartości null.
Holstebroe
3
W C # 6.0 możemy teraz używać operatorów warunkowych zerowych, takich jak theText? .ToString () lub theText? .Trim ()
Santosh
2
Na tej odpowiedzi Convert.ToString() robi dokładnie to pierwszą rzeczą, którą napisał pod spodem.
drzaus

Odpowiedzi:

179

C # 6.0 Edycja:

Dzięki C # 6.0 możemy teraz mieć zwięzłą, wolną od rzutowania wersję oryginalnej metody:

string s = myObj?.ToString() ?? "";

Lub nawet używając interpolacji:

string s = $"{myObj}";

Oryginalna odpowiedź:

string s = (myObj ?? String.Empty).ToString();

lub

string s = (myObjc ?? "").ToString()

by być jeszcze bardziej zwięzłym.

Niestety, jak już wskazano, często będziesz potrzebować rzutowania po obu stronach, aby działało to z typami innymi niż String lub Object:

string s = (myObjc ?? (Object)"").ToString()
string s = ((Object)myObjc ?? "").ToString()

Dlatego, chociaż może się wydawać elegancka, obsada jest prawie zawsze konieczna i nie jest tak zwięzła w praktyce.

Jak zasugerowano w innym miejscu, polecam użycie metody rozszerzenia, aby uczynić to czystszym:

public static string ToStringNullSafe(this object value)
{
    return (value ?? string.Empty).ToString();
}
Andrew Hanlon
źródło
Czy to się skompiluje? Czy operator koalescencji nie sprawdza typów?
Nick Larsen
1
Działa, ponieważ (object ?? string) zwraca obiekt, ponieważ coalesce używa najmniej powszechnego typu. Ale myślę, że to nie zadziała w przypadku interfejsów, ponieważ nie może zdecydować, który interfejs wybrać (ponieważ dopuszcza się wiele interfejsów na klasę).
codymanix
1
Nie do końca takie rozwiązanie, na jakie
liczyłem,
4
Głosowanie w dół jako obsada tego obiektu jest strasznie brzydkie i wiąże się z boksem.
steinar
1
pierwsza opcja c # 6 jest bardzo elegancka
Alexandre N.
40
string.Format("{0}", myObj);

string.Format sformatuje null jako pusty ciąg i wywoła ToString () na obiektach innych niż null. Jak rozumiem, właśnie tego szukałeś.

Holstebroe
źródło
3
Osobiście tego nie lubię. Kod może być nieznacznie krótszy niż String s = myObj == null? „”: myObj.ToString (), ale całkowicie ukrywasz uzasadnienie swojej metody.
AGuyCalledGerald
To prawda, że ​​jest to mikrooptymalizacja, ale zajmuje to około 5x dłużej niż tylko "" + myObj. Ale czytałem, że tworzy to dodatkowe struny. str.Concat(myObj)wydaje się działać dobrze i jest „jeszcze szybszy”.
drzaus
18

Byłoby wspaniale, gdyby Convert.ToString () miał do tego odpowiednie przeciążenie.

Od tamtej Convert.ToString(Object value)pory .Net 2.0 (około 5 lat przed zapytaniem o to pytanie) wydaje się robić dokładnie to, czego chcesz:

http://msdn.microsoft.com/en-us/library/astxcyeh(v=vs.80).aspx

Czy brakuje mi / źle interpretuję coś naprawdę oczywistego?

Rob Gilliam
źródło
1
Ty nie, oni to robią. Po co proste, skoro może być skomplikowane :)
alex.peter
13

Za pomocą metody rozszerzenia można to osiągnąć:

public static class Extension
{
    public static string ToStringOrEmpty(this Object value)
    {
        return value == null ? "" : value.ToString();
    }
}

Poniższe nie zapisałyby nic na ekranie i nie zgłosiłyby wyjątku:

        string value = null;

        Console.WriteLine(value.ToStringOrEmpty());
Pieter van Ginkel
źródło
Czy wywołania metod rozszerzających pomijają zwykłe sprawdzanie wartości null ...?
Matti Virkkunen
2
Dodaje, że nie musisz wpisywać „{0}” i że możesz wybrać nazwę metody opisującą to, co robisz. Jest to szczególnie przydatne, gdy robisz to wiele razy. Możesz nawet nazwać metodę ToStringOrEmptyWhenNull.
Pieter van Ginkel
2
Jeśli OP uważa, że string s = "" + myObj;to hacking, wywołanie funkcji na obiekcie zerowym powinno trafić do tej samej łodzi. Głosowałbym negatywnie, ale rozwiązuje to problem, po prostu nie zgadzam się z użyciem. Obiekty puste powinny być rzucane NullReferenceException, nawet w metodach rozszerzających.
Nick Larsen
2
@NickLarsen: W większości przypadków zgadzam się z tobą, ale czasami chcesz po prostu wygodę prostego wywołania metody. Nazwa metody powinna wskazywać, że odwołania zerowe są w porządku, tak jak w przypadku propozycji for ToStringOrEmptyWhenNull.
Jordão
3
Metody rozszerzające nie zgłaszają, gdy „pierwszy” parametr ( thisparametr), ponieważ są one tylko cukrem składniowym. To jest x.SomeExtensionMethod()cukier syntaktyczny dla SomeStaticClass.SomeExtensionMethod(x);Tak więc, kiedy xnie nullpróbujemy wywołać metody na nullobiekcie, ale raczej przekazujemy nullobiekt do metody statycznej. Jeśli i tylko wtedy, gdy ta metoda sprawdza nullparametr i zgłasza w przypadku napotkania takiego, metoda rozszerzająca zostanie „wywołana” w przypadku zgłoszenia nullobiektu.
jason
7

Nie zgadzam się z tym:

String s = myObj == null ? "" : myObj.ToString();

jest w jakikolwiek sposób hackerem. Myślę, że to dobry przykład przejrzystego kodu. Jest absolutnie oczywiste, co chcesz osiągnąć i że oczekujesz zerowej wartości.

AKTUALIZACJA:

Teraz widzę, że nie powiedziałeś, że to był hack. Ale z pytania wynika, że ​​myślisz, że ten sposób nie jest właściwy. Moim zdaniem to zdecydowanie najwyraźniejsze rozwiązanie.

steinar
źródło
Nie powiedział, że ta metoda to hack. W rzeczywistości nie powiedział, co jest w tym złego, chyba że sugerował, że nie jest to proste. Nie sądzę, że jest coś złego w tej metodzie. +1, bo zrobiłbym to w ten sposób.
Mark Byers
Zgadzam się z OP ... w C # jest już operator koalescencji zerowej - zobacz mój post poniżej.
Andrew Hanlon
Myślę, że operator koalescencji zerowej jest w tym przypadku trochę mniej jasny, jednak byłoby mi dobrze, gdybyśmy go użyli.
steinar
@ Pieter Fair wystarczająco. Ale tak jest. Pytanie brzmi: „Czy istnieje prosty sposób wykonania następujących czynności:…”. Dokładnie ten sposób jest prosty!
steinar
Nigdy nie powiedziałem, że ta metoda to hack. Przeczytaj ponownie moje pytanie. Brakuje mi tylko małej funkcji w Framework, która ma taką funkcjonalność.
codymanix
4
string s = String.Concat(myObj);

byłby najkrótszą drogą, jak sądzę, i miałby również znikomy narzut wydajności. Pamiętaj jednak, że dla czytelnika kodu nie byłoby całkiem jasne, jaki jest zamiar.

herzmeister
źródło
2
To jest jawna forma "" + myObj, ale jeszcze gorzej mówiąc, co się tutaj dzieje. W każdym razie interesujące.
codymanix
@codymanix, nie sądzę, że to to samo - Concatfaktycznie robi zerowe sprawdzenie pod spodem i zwraca string.Emptylub arg0.ToString(), co wydaje się być nieco bardziej wydajne (mam na myśli, mówimy tutaj ms).
drzaus
2

właściwie nie rozumiałem, co chcesz robić. Jak rozumiem, możesz napisać ten kod w inny sposób. Pytasz o to czy nie? Czy możesz to bardziej wyjaśnić?

string s = string.Empty;
    if(!string.IsNullOrEmpty(myObj))
    {
    s = myObj.ToString();
    }
Serkan Hekimoglu
źródło
Jasne, że mogę to tak napisać, ale chcę proste wywołanie małej funkcji, zastanawiałem się, dlaczego nie ma czegoś takiego w .NET BCL
codymanix
Czy naprawdę potrzebujesz funkcji, skoro mamy metodę IsNullOrEmpty ()?
Serkan Hekimoglu,
W związku z tym zawsze chciałem, aby operator koalescencji zawiódł w następnej kolejności, gdy trafi w zerowe odniesienie w twojej płynnej wypowiedzi, ale całkowicie rozumiem, dlaczego jest to okropny pomysł. Nadpisanie, które pozwala ci to zrobić, też byłoby miłe.
Nick Larsen
1
string s = string.IsNullOrEmpty(myObj) ? string.Empty : myObj.ToString();
Nick Larsen
2

Mogę zostać pobity za moją odpowiedź, ale tak czy inaczej:

Po prostu napisałbym

string s = "" if (myObj! = null) {x = myObj.toString (); }

Czy korzystanie z operatora trójskładnikowego się opłaca? Nie wiem z góry mojej głowy.

I oczywiście, jak wspomniano powyżej, możesz umieścić to zachowanie w metodzie takiej jak safeString (myObj), która pozwala na ponowne użycie.

jaydel
źródło
OMG, jeśli obiekt jest null, to wywołaj na nim ToString ()?
codymanix
Chodź, to pomyłka. Zamieniłem pierwszy = na! żeby było poprawne. Ale naprawdę zamierzasz -1 mnie za literówkę?
jaydel
2

Miałem ten sam problem i rozwiązałem go po prostu rzucając obiekt na łańcuch. Działa to również w przypadku obiektów o wartości null, ponieważ ciągi znaków mogą mieć wartość null. O ile absolutnie nie chcesz mieć łańcucha zerowego, powinno to działać dobrze:

string myStr = (string)myObj; // string in a object disguise or a null
jahu
źródło
Najkrótsze i najlepsze rozwiązanie. (Obj1 ?? "") .ToString () właściwie najpierw rzuca Obj1 jako łańcuch :)
TPAKTOPA,
2

Niektóre (szybkie) testy wydajności podsumowujące różne opcje, ale nie to, że to naprawdę ma znaczenie # mikrooptymalizacja (przy użyciu rozszerzenia linqpad )

Opcje

void Main()
{
    object objValue = null;
    test(objValue);
    string strValue = null;
    test(strValue);
}

// Define other methods and classes here
void test(string value) {
    new Perf<string> {
        { "coallesce", n => (value ?? string.Empty).ToString() },
        { "nullcheck", n => value == null ? string.Empty : value.ToString() },
        { "str.Format", n => string.Format("{0}", value) },
        { "str.Concat", n => string.Concat(value) },
        { "string +", n => "" + value },
        { "Convert", n => Convert.ToString(value) },
    }.Vs();
}

void test(object value) {
    new Perf<string> {
        { "coallesce", n => (value ?? string.Empty).ToString() },
        { "nullcheck", n => value == null ? string.Empty : value.ToString() },
        { "str.Format", n => string.Format("{0}", value) },
        { "str.Concat", n => string.Concat(value) },
        { "string +", n => "" + value },
        { "Convert", n => Convert.ToString(value) },
    }.Vs();
}

Prawdopodobnie ważne jest, aby wskazać, że Convert.ToString(...)zachowa ciąg pusty.

Wyniki

Obiekt

  • nullcheck 1,00x 1221 taktów minęło (0,1221 ms) [przy 10 tys. powtórzeń, 1,221E-05 ms na]
  • coallesce 1,14x 1387 taktów minęło (0,1387 ms) [przy 10 tys. powtórzeń, 1,387E-05 ms na]
  • ciąg + 1,16x 1415 taktów minęło (0,1415 ms) [przy 10 tys. powtórzeń, 1,415E-05 ms na]
  • str.Concat 1,16x 1420 taktów minęło (0,142 ms) [przy 10 tys. powtórzeń, 1,42E-05 ms na]
  • Konwersja 1,58x 1931 taktów, które upłynęły (0,1931 ms) [w 10 tys. Powtórzeń, 1,931E-05 ms na]
  • Str. format 5,45x 6655 taktów minęło (0,6655 ms) [przy 10 tys. powtórzeń, 6,655E-05 ms na]

Strunowy

  • nullcheck 1,00x 1190 taktów minęło (0,119 ms) [przy 10 tys. powtórzeń, 1,19E-05 ms na]
  • Konwertuj 1,01x 1200 taktów, które upłynęły (0,12 ms) [przy 10 tys. Powtórzeń, 1,2 E-05 ms na]
  • ciąg + 1,04x 1239 taktów minęło (0,1239 ms) [przy 10 tys. powtórzeń, 1,239E-05 ms na]
  • coallesce 1,20x 1423 taktów minęło (0,1423 ms) [przy 10 tys. powtórzeń, 1,423E-05 ms na]
  • str.Concat 4,57x 5444 taktów minęło (0,5444 ms) [przy 10 tys. powtórzeń, 5,444E-05 ms na]
  • Str. format 5,67x 6750 taktów minęło (0,675 ms) [przy 10 tys. powtórzeń, 6,75E-05 ms na]
drzaus
źródło
1

Komentarz Holstebroe byłby najlepszą odpowiedzią:

string s = string.Format("{0}", myObj);

Jeśli myObjjest null, Format umieszcza tam wartość pustego ciągu.

Spełnia również wymagania dotyczące jednej linii i jest łatwy do odczytania.

jp2code
źródło
tak, ale kod nie jest jasny. Ilu innych programistów będzie wiedziało, co próbujesz osiągnąć?
AGuyCalledGerald
0

Mimo że jest to stare pytanie, a OP zapytany o C #, chciałbym udostępnić rozwiązanie VB.Net dla tych, którzy pracują z VB.Net zamiast C #:

Dim myObj As Object = Nothing
Dim s As String = If(myObj, "").ToString()

myObj = 42
s = If(myObj, "").ToString()

Niestety VB.Net nie pozwala na? -Operator po zmiennej, więc myObj? .ToString nie jest poprawne (przynajmniej nie w .Net 4.5, którego użyłem do testowania rozwiązania). Zamiast tego używam If, aby zwrócić pusty ciąg w przypadku, gdy myObj ist Nothing. Zatem pierwszy Tostring-Call zwraca pusty ciąg, podczas gdy drugi (gdzie myObj nie jest Nothing) zwraca „42”.

Sascha
źródło