?? Coalesce na pusty sznurek?

165

Coś, co robię coraz częściej, to sprawdzanie ciągu znaków pod kątem pustego (jak in ""lub null) i operatora warunkowego.

Aktualny przykład:

s.SiteNumber.IsNullOrEmpty() ? "No Number" : s.SiteNumber;

To jest tylko metoda rozszerzenia, jest odpowiednikiem:

string.IsNullOrEmpty(s.SiteNumber) ? "No Number" : s.SiteNumber;

Ponieważ jest pusty i nie jest pusty, ??nie załatwi sprawy. string.IsNullOrEmpty()Wersja ??byłoby idealnym rozwiązaniem. Myślę, że musi być na to czystszy sposób (mam nadzieję!), Ale nie mogłem go znaleźć.

Czy ktoś zna lepszy sposób na zrobienie tego, nawet jeśli jest to tylko w .Net 4.0?

Nick Craver
źródło
11
Aby cię trochę zmylić, możesz łatwo zdefiniować niestandardowe, binarne (i jednoargumentowe) operatory ad hoc w języku F #. Tutaj let (|?) x y = if String.IsNullOrEmpty(x) then y else xi używaj go jak s.SiteNumber |? "No Number".
Stephen Swensen

Odpowiedzi:

72

Nie ma na to wbudowanego sposobu. Możesz jednak sprawić, by metoda rozszerzenia zwracała ciąg lub wartość null, co pozwoliłoby na działanie operatora łączenia. Byłoby to jednak dziwne i osobiście wolę Twoje obecne podejście.

Ponieważ używasz już metody rozszerzającej, dlaczego nie utworzyć takiej, która zwraca wartość lub wartość domyślną:

string result = s.SiteNumber.ConvertNullOrEmptyTo("No Number");
Reed Copsey
źródło
7
Myślę, że masz rację i jest to najczystsze obecnie dostępne rozwiązanie, które jest nadal czytelne. Chciałbym jednak czegoś takiego jak ???operator w C # 5, kto wie.
Nick Craver
2
a co by to ??? operator zrobić? przyjmować wartości domyślne oprócz wartości null? brzmi w najlepszym
razie
1
Może z wyrażeniami lambda? Na przykład: załóżmy, że „pozycja” ma wartość null, więc ... item ?? x=> x.ToString() : null;
Isaac Llopis,
@IsaacLlopis, który kończy się wyglądaniem na messy-er niż oryginalny fragment OP's
maksymiuk
133

C # już pozwala nam podstawiać wartości nullz ??. Potrzebujemy więc tylko rozszerzenia, które konwertuje pusty ciąg na null, a następnie używamy go w następujący sposób:

s.SiteNumber.NullIfEmpty() ?? "No Number";

Klasa rozszerzenia:

public static class StringExtensions
{
    public static string NullIfEmpty(this string s)
    {
        return string.IsNullOrEmpty(s) ? null : s;
    }
    public static string NullIfWhiteSpace(this string s)
    {
        return string.IsNullOrWhiteSpace(s) ? null : s;
    }
}
RedFilter
źródło
44

Wiem, że to stare pytanie - ale szukałem odpowiedzi i żadne z powyższych nie pasowało do moich potrzeb, a także tego, z czego ostatecznie skorzystałem:

private static string Coalesce(params string[] strings)
{
    return strings.FirstOrDefault(s => !string.IsNullOrEmpty(s));
}

Stosowanie:

string result = Coalesce(s.SiteNumber, s.AltSiteNumber, "No Number");

EDYCJA: Jeszcze bardziej zwarty sposób zapisu tej funkcji to:

static string Coalesce(params string[] strings) => strings.FirstOrDefault(s => !string.IsNullOrEmpty(s));
sfsr
źródło
1
Podoba mi się, ale musiałem naprawić błąd kompilatora i uczynić go nieco bardziej kompaktowym.
gregmac
Dlaczego nazywasz to Coalesce, skoro nie łączy wartości, a jedynie wybiera tę, która nie jest pusta? To mylące imię, koleś.
Jimmyt1988,
8
Ponieważ Coalesce jest terminem używanym przez wiele baz danych do wykonywania tej samej operacji (znajdowania pierwszej wartości innej niż null). Łączenie łańcuchów razem jest konkatenacją.
Cameron Forward
Najlepsza odpowiedź, jeśli jesteśusing System.Linq
JoePC
To elegancka, niezła robota.
Mariano Luis Villa
16

Mam kilka rozszerzeń narzędzi, których lubię używać:

public static string OrDefault(this string str, string @default = default(string))
{
    return string.IsNullOrEmpty(str) ? @default : str;
}

public static object OrDefault(this string str, object @default)
{
    return string.IsNullOrEmpty(str) ? @default : str;
}

Edycja: Zainspirowany odpowiedzią sfsr , od teraz będę dodawać ten wariant do mojego zestawu narzędzi:

public static string Coalesce(this string str, params string[] strings)
{
    return (new[] {str})
        .Concat(strings)
        .FirstOrDefault(s => !string.IsNullOrEmpty(s));
}
Justin Morgan
źródło
1
Zdecydowanie używam terminu „Coalesce”, ponieważ bardziej przypomina on intencję operatora koalescencji zerowej (??), chociaż zmieniłem go na „CoalesceTo”.
llaughlin
Co robi @prefiks @defaultparametru? Nigdy wcześniej tego nie widziałem.
Drew Chapin,
3
@druciferre - to po prostu umożliwia użycie defaultjako nazwy zmiennej, mimo że jest to zastrzeżone słowo kluczowe w C #.
Justin Morgan
1
@ Jimmyt1988 - Ponieważ przybliża standardową funkcję T-SQL COALESCE .
Justin Morgan,
1
@ Jimmyt1988 - Również dlatego, że specjalnie wybiera pierwszą niepustą funkcję z listy o dowolnej długości. To subtelny szczegół, ale funkcja T-SQL działa w ten sam sposób. Nazwa sprawia, że ​​jest intuicyjna dla każdego, kto zna tę funkcję, z dokumentacją lub bez.
Justin Morgan,
6

Być może nieco szybsza metoda rozszerzenia niż proponowana wcześniej:

public static string Fallback(this string @this, string @default = "")
{
    return (@this == null || @this.Trim().Length == 0) ? @default : @this;
}
Chilidog
źródło
11
Dlaczego nie skorzystać z IsNullOrWhitespace zamiast przycinać i wydłużać .
weston
1
codepubliczny ciąg statyczny Coalesce (ten ciąg @this, string @default = "") {return (@this == null || String.IsNullOrWhiteSpace (@this))? @default: @this; }
paul-2011
6

Jedną z zalet operatora koalescencji zerowej jest to, że powoduje zwarcia. Gdy pierwsza część nie jest pusta, druga nie jest oceniana. Może to być przydatne, gdy powrót wymaga kosztownej operacji.

Skończyło się na:

public static string Coalesce(this string s, Func<string> func)
{
    return String.IsNullOrEmpty(s) ? func() : s;
}

Stosowanie:

string navigationTitle = model?.NavigationTitle.
    Coalesce(() => RemoteTitleLookup(model?.ID)). // Expensive!
    Coalesce(() => model?.DisplayName);
wensveen
źródło
6

Po prostu używam metody rozszerzenia NullIfEmpty, która zawsze zwraca wartość null, jeśli ciąg jest pusty, co pozwala? (Operator Null Coalescing) do normalnego użycia.

public static string NullIfEmpty(this string s)
{
    return s.IsNullOrEmpty() ? null : s;
}

To pozwala? do normalnego użytku i sprawia, że ​​łańcuchy są łatwe do odczytania.

string string1 = string2.NullIfEmpty() ?? string3.NullIfEmpty() ?? string4;
jjr2000
źródło
3

co powiesz na metodę rozszerzenia ciągu ValueOrDefault ()

public static string ValueOrDefault(this string s, string sDefault)
{
    if (string.IsNullOrEmpty(s))
        return sDefault;
    return s;
}

or return null jeśli string jest Empty:

public static string Value(this string s)
{
    if (string.IsNullOrEmpty(s))
        return null;
    return s;
}

Nie próbowałem jednak tych rozwiązań.

devio
źródło
2
Podoba mi się opcja nr 1, chociaż nazwałbym ją czymś bardziej semantycznym, jak Or (), więc mógłbym napisać "string s = s.SiteNumber.Or (" Default ");"
jvenema
2
Wywołanie czegoś ...OrDefault()byłoby mylące, gdyby nie zachowywało się jak reszta ...OrDefault()metod frameworka . Czy nam się to podoba, czy nie, MS nadało określone znaczenie temu nazewnictwie i odstępowanie od tego zachowania w metodach niestandardowych jest niepotrzebnie mylące dla użytkowników twojego API.
mattmc3
3

Używam własnej metody rozszerzenia łańcucha Coalesce. Ponieważ ci tutaj używają LINQ i absolutnie marnują zasoby na czasochłonne operacje (używam go w ciasnych pętlach), udostępnię moje:

public static class StringCoalesceExtension
{
    public static string Coalesce(this string s1, string s2)
    {
        return string.IsNullOrWhiteSpace(s1) ? s2 : s1;
    }
}

Myślę, że jest to dość proste i nie musisz nawet przejmować się pustymi wartościami łańcuchowymi. Użyj tego w ten sposób:

string s1 = null;
string s2 = "";
string s3 = "loudenvier";
string s = s1.Coalesce(s2.Coalesce(s3));
Assert.AreEqual("loudenvier", s);

Często go używam. Jedna z tych "narzędziowych" funkcji, bez której nie możesz żyć po pierwszym użyciu :-)

Loudenvier
źródło
Myślę, że nie rozumiesz, dlaczego używają LINQ, a ponieważ parametry są oceniane przed wywołaniami, Twój s2.Coalesce(s3)jest uruchamiany nawet wtedy, gdy nie jest potrzebny. Lepiej jest użyć NullIfEmpty()rozszerzenia i ??.
NetMage
@NetMage Mogę zagwarantować, że wersje LINQ są dużo mniej wydajne niż ta, którą przedstawiłem. Jeśli chcesz, możesz utworzyć prosty test porównawczy, aby to przetestować. Sugeruję użycie github.com/dotnet/BenchmarkDotNet, aby zapobiec typowym pułapkom podczas pisania kodu do testów porównawczych.
Loudenvier
0

Podoba mi się zwięzłość poniższej metody rozszerzenia QQQ, chociaż oczywiście operator lubi? byłoby lepiej. Ale możemy to zwiększyć, pozwalając na porównanie nie tylko dwóch, ale trzech łańcuchowych wartości opcji, z którymi od czasu do czasu trzeba sobie poradzić (patrz druga funkcja poniżej).

#region QQ

[DebuggerStepThrough]
public static string QQQ(this string str, string value2)
{
    return (str != null && str.Length > 0)
        ? str
        : value2;
}

[DebuggerStepThrough]
public static string QQQ(this string str, string value2, string value3)
{
    return (str != null && str.Length > 0)
        ? str
        : (value2 != null && value2.Length > 0)
            ? value2
            : value3;
}


// Following is only two QQ, just checks null, but allows more than 1 string unlike ?? can do:

[DebuggerStepThrough]
public static string QQ(this string str, string value2, string value3)
{
    return (str != null)
        ? str
        : (value2 != null)
            ? value2
            : value3;
}

#endregion
Nicholas Petersen
źródło
Jeśli lubisz krótkie nazwy, możesz to nazwać Or, a ja użyłbym paramssłowa kluczowego, podobnie jak innych odpowiedzi, co pozwala uniknąć powielania definicji dla wielu parametrów.
Chiel ten Brinke
Dzięki za pomysł. Już dawno zastąpiłem tę nazwę „FirstNotNull” w moim własnym użyciu. W paramsprzypadku włączenia byłoby lepiej nie robić tego w przypadku domyślnego scenariusza lub dwóch, ponieważ paramspowoduje to niepotrzebne przydzielanie tablicy, gdy masz tylko jedno lub dwa domyślne dane wejściowe. Po tym ma sens.
Nicholas Petersen,