Domanda

modifica 2015 Questa domanda e le sue risposte non sono più rilevanti. E 'stato chiesto prima dell'avvento di C # 6, che ha l'opertor propagazione nullo (?.), Che elimina i hacky-soluzioni discusse in questa domanda e le risposte successive. A partire dal 2015, in C # si dovrebbe ora utilizzare Form.ActiveForm? .ActiveControl? .Name.


Ho riflettuto sul problema di propagazione nulla in .NET, che spesso porta a brutto, il codice ripetuto in questo modo:

Tentativo # 1 codice di solito:

string activeControlName = null;
var activeForm = Form.ActiveForm;
if (activeForm != null)
{
    var activeControl = activeForm.ActiveControl;
    if(activeControl != null)
    {
        activeControlname = activeControl.Name;
    }
}

Ci sono state alcune discussioni su StackOverflow, circa una Forse monade, o utilizzando una sorta di "se non nullo" metodo di estensione:

Tentativo # 2, metodo di estensione:

// Usage:
var activeControlName = Form.ActiveForm
                          .IfNotNull(form => form.ActiveControl)
                          .IfNotNull(control => control.Name);

// Definition:
public static TReturn IfNotNull<TReturn, T>(T instance, Func<T, TReturn> getter)
    where T : class
{
    if (instance != null ) return getter(instance);
    return null;
}

Credo che questo sia il migliore, però, c'è un po 'di sintattico disordinato-ness con le ripetute "IfNotNull" e il lambda. Ora sto considerando questo progetto:

Tentativo # 3, forse con metodo di estensione

// Usage:
var activeControlName = (from window in Form.ActiveForm.Maybe()
                         from control in window.ActiveControl.Maybe()
                         select control.Name).FirstOrDefault();

// Definition:
public struct Maybe<T> : IEnumerable<T>
      where T : class
{
    private readonly T instance;

    public Maybe(T instance)
    {
        this.instance = instance;
    }

    public T Value
    {
        get { return instance; }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return Enumerable.Repeat(instance, instance == null ? 0 : 1).GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

public static class MaybeExtensions
{
    public static Maybe<T> Maybe<T>(this T instance)
        where T : class
    {
        return new Maybe<T>(instance);
    }
}

La mia domanda è : è questo un abuso male metodi di estensione? E 'meglio dei vecchi soliti controlli nulli?

È stato utile?

Soluzione

E 'interessante il fatto che così tante persone prendere autonomamente il nome IfNotNull, per questo in C # - deve essere il nome più sensato possibile! :)

La prima quello che ho trovato su SO: possibili insidie di utilizzare questo (metodo di estensione based) stenografia

Il mio unico (nell'ignoranza di quanto sopra): avanti tubi in C #

Un altro esempio più recente: Come verificare la presenza di valori nulli in un'espressione lambda profondo?

Ci sono un paio di ragioni per cui il metodo di estensione IfNotNull può essere impopolare.

  1. Alcune persone sono fermamente convinto che un metodo di estensione dovrebbe un'eccezione se il suo parametro this è null. Non sono d'accordo, se il nome del metodo rende chiaro.

  2. Estensioni che si applicano in maniera troppo generica tenderanno a ingombrare il menu auto-completamento. Ciò può essere evitato con l'uso corretto dei namespace in modo da non infastidire la gente che non li vogliono, comunque.

Ho giocato in giro con l'approccio IEnumerable anche, proprio come un esperimento per vedere quante cose avrei potuto girare per adattare le parole chiave Linq, ma penso che il risultato finale è meno leggibile rispetto sia il concatenamento IfNotNull o l'imperativo grezzo codice.

Ho finito con una semplice classe Maybe indipendente con un metodo statico (non un metodo di estensione) e che funziona molto bene per me. Ma poi, io lavoro con una piccola squadra, e la mia prossima collega più anziano è interessato alla programmazione funzionale e lambda e così via, in modo che non è messo fuori da essa.

Altri suggerimenti

Per quanto io sono un fan di metodi di estensione, non credo che questo è davvero utile. Hai ancora la ripetizione delle espressioni (nella versione monadica), e significa solo che hai avuto modo di spiegare Maybe a tutti. La curva di apprendimento aggiunto non sembra avere abbastanza vantaggio in questo caso.

La versione IfNotNull almeno riesce ad evitare la ripetizione, ma credo che sia ancora un po 'troppo prolisso senza in realtà essere più chiara.

Forse un giorno avremo un operatore di dereferenziazione null-sicuro ...


Proprio come un a parte, il mio metodo di estensione semi-male preferito è:

public static void ThrowIfNull<T>(this T value, string name) where T : class
{
    if (value == null)
    {
        throw new ArgumentNullException(name);
    }
}

che permette di trasformare questo:

void Foo(string x, string y)
{
    if (x == null)
    {
        throw new ArgumentNullException(nameof(x));
    }
    if (y == null)
    {
        throw new ArgumentNullException(nameof(y));
    }
    ...
}

in:

void Foo(string x, string y)
{
    x.ThrowIfNull(nameof(x));
    y.ThrowIfNull(nameof(y));
    ...
}

C'è ancora la brutta ripetizione del nome del parametro, ma almeno è più ordinato. Naturalmente, in NET 4.0 userei Code Contracts, che è quello che sto intenzione di scrivere di questo momento ... Stack Overflow è grande evitamento lavoro;)

Se si desidera un metodo di estensione per ridurre il nidificato se è come si dispone, si potrebbe provare qualcosa di simile:

public static object GetProperty(this object o, Type t, string p)
{
    if (o != null)
    {
        PropertyInfo pi = t.GetProperty(p);
        if (pi != null)
        {
            return pi.GetValue(o, null);
        }
        return null;
    }
    return null;
}

così nel codice si era appena fatto:

string activeControlName = (Form.ActiveForm as object)
    .GetProperty(typeof(Form),"ActiveControl")
    .GetProperty(typeof(Control),"Name");

Non so se avrei voluto usarlo per causa spesso la lentezza della riflessione, e non ho davvero che questo molto meglio che l'alternativa, ma dovrebbe funzionare, indipendentemente dal fatto che si colpisce un nulla lungo la strada ...

(Nota: Ho potrebbe aver ottenuto quei tipi mescolati):)

Nel caso in cui hai a che fare con C # 6.0 / VS 2015 e al di sopra, che ora hanno una soluzione integrata per la propagazione nulla:

string ans = nullableString?.Length.ToString(); // null if nullableString == null, otherwise the number of characters as a string.

I lavori iniziali del campione ed è il più facile da leggere a colpo d'occhio. C'è davvero bisogno di migliorare su questo?

La soluzione IfNotNull è la migliore (fino a quando la squadra C # ci dà un operatore di dereferenziazione null-sicurezza, che è).

Non sono troppo pazza per entrambe le soluzioni. Cosa c'era di sbagliato con la versione ashorter dell'originale:

string activeControlName = null;
if (Form.ActiveForm != null)
    if (Form.ActiveForm.ActivControl != null) activeControlname = activeControl.Name;

Se non questo, quindi vorrei guardare a scrivere una NotNullChain o un oggetto FluentNotNull di catena può alcuni test non nulli di fila. Sono d'accordo che il metodo di estensione IfNotNull agisce su un nulla sembra un po 'strano -. Anche se i metodi di estensione sono lo zucchero sintattico

Credo che la risposta di Mark Synowiec potrebbe essere in grado di fatti generici.

secondo me, credo che la squadra # nucleo C dovrebbe guardare il questo "problema", anche se penso che ci sono cose più grandi da affrontare.

Certo, originale 2-nidificato IF è molto più leggibile rispetto ad altre scelte. Ma suggerendo si vuole risolvere il problema, più in generale, ecco un'altra soluzione:

try
{
    var activeForm = Form.ActiveForm; assumeIsNotNull(activeForm);
    var activeControl = activeForm.ActiveControl; assumeIsNotNull(activeControl);
    var activeControlname = activeControl.Name;
}
catch (AssumptionChainFailed)
{
}

dove

class AssumptionChainFailed : Exception { }
void assumeIsNotNull(object obj)
{
    if (obj == null) throw new AssumptionChainFailed();
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top