it-swarm.it

Convalida dei parametri del costruttore in C # - Best practice

Qual è la migliore pratica per la validazione dei parametri del costruttore?

Supponiamo un po 'di C #:

public class MyClass
{
    public MyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            throw new ArgumentException("Text cannot be empty");

        // continue with normal construction
    }
}

Sarebbe accettabile lanciare un'eccezione?

L'alternativa che ho incontrato è stata la pre-validazione, prima di creare un'istanza:

public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
        {
            MessageBox.Show("Text cannot be empty");
            return null;
        }
        else
        {
            return new MyClass(text);
        }
    }
}
35
MPelletier

Tendo a eseguire tutte le mie convalide nel costruttore. Questo è un must perché creo quasi sempre oggetti immutabili. Per il tuo caso specifico penso che questo sia accettabile.

if (string.IsNullOrEmpty(text))
    throw new ArgumentException("message", "text");

Se stai usando .NET 4 puoi farlo. Naturalmente questo dipende dal fatto che consideri non valida una stringa che contiene solo spazi bianchi.

if (string.IsNullOrWhiteSpace(text))
    throw new ArgumentException("message", "text");
27
ChaosPandion

Molte persone affermano che i costruttori non dovrebbero lanciare eccezioni. KyleG su questa pagina , ad esempio, fa proprio questo. Onestamente, non riesco a pensare a un motivo per cui no.

In C++, lanciare un'eccezione da un costruttore è una cattiva idea, perché ti lascia con memoria allocata contenente un oggetto non inizializzato a cui non hai riferimenti (cioè è una classica perdita di memoria). Questo è probabilmente il motivo per cui deriva lo stigma: un gruppo di sviluppatori C++ di vecchia scuola ha messo a metà il loro apprendimento in C # e ha semplicemente applicato ciò che sapevano dal C++ ad esso. Al contrario, in Objective-C Apple ha separato la fase di allocazione dalla fase di inizializzazione, quindi i costruttori in quella lingua possono generare eccezioni.

C # non può perdere la memoria da una chiamata non riuscita a un costruttore. Anche alcune classi nel framework .NET genereranno eccezioni nei loro costruttori.

23
Ant

Gettare un'eccezione IFF che la classe non può essere messa in uno stato coerente per quanto riguarda il suo uso semantico. Altrimenti no. Non consentire MAI a un oggetto di esistere in uno stato incoerente. Ciò include non fornire costruttori completi (come avere un costruttore vuoto + inizializza () prima che l'oggetto sia effettivamente completamente costruito) ... SOLO DI NO!

In un pizzico, lo fanno tutti. L'ho fatto l'altro giorno per un oggetto molto usato in un ambito ristretto. Un giorno lungo la strada, io o qualcun altro probabilmente pagherò il prezzo per quella polizza in pratica.

Dovrei notare che per "costruttore" intendo la cosa che il client chiama per costruire l'oggetto. Potrebbe facilmente essere qualcosa di diverso da un vero e proprio costrutto chiamato "Costruttore". Ad esempio, qualcosa del genere in C++ non violerebbe il principio IMNSHO:

struct funky_object
{
  ...
private:
  funky_object();
  bool initialize(std::string);

  friend boost::optional<funky_object> build_funky(std::string);
};
boost::optional<funky_object> build_funky(std::string str)
{
  funky_object fo;
  if (fo.initialize(str)) return fo;
  return boost::optional<funky_object>();
}

Poiché l'unico modo per creare un funky_object sta chiamando build_funky il principio di non consentire mai l'esistenza di un oggetto non valido rimane intatto anche se il vero "Costruttore" non termina il lavoro.

Questo è un sacco di lavoro extra anche se per guadagno discutibile (forse anche perdita). Preferirei comunque il percorso di eccezione.

13
Edward Strange

In questo caso, utilizzerei il metodo factory. Fondamentalmente, impostare la tua classe ha solo costruttori privati ​​e un metodo factory che restituisce un'istanza del tuo oggetto. Se i parametri iniziali non sono validi, è sufficiente restituire null e fare decidere al codice chiamante cosa fare.

public class MyClass
{
    private MyClass(string text)
    {
        //normal construction
    }

    public static MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            return null;
        else
            return new MyClass(text);
    }
}
public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        var cls = MyClass.MakeMyClass(text);
        if(cls == null)
             //show messagebox or throw exception
        return cls;
    }
}

Non gettare mai eccezioni a meno che le condizioni non siano eccezionali. Sto pensando che in questo caso, un valore vuoto può essere passato facilmente. In tal caso, l'utilizzo di questo modello eviterebbe le eccezioni e le penali di prestazione che incorrono mantenendo lo stato MyClass valido.

9
Donn Relacion
  • Un costruttore non dovrebbe avere effetti collaterali.
    • Qualsiasi cosa oltre all'inizializzazione del campo privato dovrebbe essere vista come un effetto collaterale.
    • Un costruttore con effetti collaterali infrange il principio di responsabilità singola (SRP) e va contro lo spirito della programmazione orientata agli oggetti (OOP).
  • Un costruttore dovrebbe essere leggero e non dovrebbe mai fallire.
    • Ad esempio, rabbrividisco sempre quando vedo un blocco try-catch all'interno di un costruttore. Un costruttore non dovrebbe generare eccezioni o errori di registro.

Si potrebbe ragionevolmente mettere in discussione queste linee guida e dire: "Ma io non seguo queste regole e il mio codice funziona bene!" A quello, risponderei, "Beh, potrebbe essere vero, fino a quando non lo è."

  • Eccezioni ed errori all'interno di un costruttore sono molto inaspettati. A meno che non gli venga detto di farlo, i futuri programmatori non saranno inclini a circondare queste chiamate del costruttore con un codice difensivo.
  • Se qualcosa non riesce nella produzione, la traccia dello stack generata potrebbe essere difficile da analizzare. La parte superiore della traccia dello stack può puntare alla chiamata del costruttore, ma molte cose accadono nel costruttore e potrebbe non puntare al LOC effettivo non riuscito.
    • Ho analizzato molte tracce dello stack .NET in cui questo era il caso.
2
Jim G.

La mia preferenza è quella di impostare un valore predefinito, ma so che Java ha la libreria "Apache Commons" che fa qualcosa del genere, e penso che sia anche una buona idea. un problema con il lancio di un'eccezione se il valore non valido mettesse l'oggetto in uno stato inutilizzabile; una stringa non è un buon esempio ma cosa succederebbe se fosse per il DI di un uomo povero? Non potresti operare se un valore di null è stato sostituito al posto di, diciamo, un'interfaccia ICustomerRepository In una situazione come questa, lanciare un'eccezione è il modo corretto di gestire le cose, direi.

0
Wayne Molina

Dipende da cosa sta facendo MyClass. Se MyClass fosse in realtà una classe di repository di dati e il testo dei parametri fosse una stringa di connessione, la migliore pratica sarebbe quella di lanciare ArgumentException. Tuttavia, se MyClass è la classe StringBuilder (ad esempio), è possibile lasciarlo vuoto.

Quindi dipende da quanto sia essenziale il testo del parametro per il metodo: l'oggetto ha senso con un valore nullo o vuoto?

0
Steven Striga