Uso intenzionalmente scorretto delle funzionalità del linguaggio, in particolare "ref" in C #, come suggerimento per i colleghi

7

Recentemente ho trovato un pezzo di codice simile a questo (all'incirca C #):

public bool ValidateStuff(ref ArrayList listOfErrors, Stuff thingsToValidate)
{
    if (!thingsToValidate.isValid() )
    {
        errors.add("New error!");
    }
}

ArrayList errors = [];
bool valid = ValidateStuff(ref errors, stuffToValidate);

La cosa fondamentale è la parola chiave "ref" - non c'è bisogno di usare quella parola chiave data la funzionalità qui, e quando ho chiesto perché era lì mi è stato detto che era stata aggiunta solo come una sorta di avvertimento che il Il parametro errori è manipolato dal metodo, non è puramente utilizzato come input.

Questo mi è sembrato subito sbagliato ( ref ha uno scopo, e qui non viene usato secondo il suo scopo), ma l'idea di forzare (dato che hai per usare ref quando si chiama il metodo) un promemoria di utilizzo leggermente controintuitivo sembrava ragionevole, e non potevo immediatamente pensare a un modo migliore, a parte l'uso liberale dei commenti. Cosa ne pensi?

Credito extra: come potrebbe essere migliore questo metodo? Forse semplicemente restituendo un nuovo Elenco di errori che viene aggiunto alla Lista principale nel codice chiamante? Fornendo due metodi, uno per il test e uno per gli errori? O va bene così com'è?

    
posta frumious 05.09.2014 - 22:16
fonte

3 risposte

8

Comprendi che la parola chiave ref indica l'intenzione di riassegnare il puntatore.

Se abiliti l'analisi del codice di Microsoft, riceverai un un avvertimento che ti dice che usare param param è probabilmente un cattivo design .

Il passaggio a ref non è solo un cambio di zucchero visivo o sintattico. In realtà consente al metodo di riassegnare il riferimento a un nuovo oggetto, che influenza il codice chiamante in modi che non intendevano.

Ad esempio, senza usare ref, se il metodo tenta di riassegnare la variabile del parametro a un nuovo oggetto, NON avrà alcun impatto sulla copia della variabile del codice chiamante.

public bool ValidateStuff(ArrayList listOfErrors, Stuff thingsToValidate)
{
    if (!thingsToValidate.isValid() )
    {
        listOfErrors = new ArrayList(){"error"};
    }
}

ArrayList errors = ["existing error1", "existing error2"];
bool valid = ValidateStuff(errors, stuffToValidate);
Console.WriteLine(errors);  //output is still "existing error1", "existing error2"

Ma la stessa cosa usando ref, in realtà cambia il riferimento al di fuori del metodo:

public bool ValidateStuff(ref ArrayList listOfErrors, Stuff thingsToValidate)
{
    if (!thingsToValidate.isValid() )
    {
        listOfErrors = new ArrayList(){"error"};
    }
}

ArrayList errors = ["existing error1", "existing error2"];
bool valid = ValidateStuff(ref errors, stuffToValidate);
Console.WriteLine(errors);  //output is now "error"

Ora, in entrambi questi esempi di pseudo codice si è verificato un errore nel codice. La differenza importante è che la prima versione potrebbe causare un errore prevedibile. Il secondo esempio potrebbe causare risultati molto imprevedibili in determinate situazioni.

Che cosa invece fare

Ora che hai (si spera) accettato il fatto che i progettisti linguistici intendessero utilizzare solo quando il metodo intende riassegnare il puntatore, hai solo alcune opzioni:

  1. Ridisegna il tuo metodo per restituire l'elenco degli errori invece di essere un vuoto.
  2. Aggiungi una nuova classe contenente tutto ciò che deve essere restituito e restituisci una nuova istanza di quella classe.
  3. Passa più parametri di tipo riferimento (senza utilizzare la parola chiave ref) e lascia che il metodo li modifichi secondo necessità.

Oltre a ciò, non puoi fare nient'altro. Il linguaggio è stato progettato per essere così. Non impazzire per il fatto che C # non consente più valori di ritorno. Ho sentito che Go supporta più valori di ritorno se non riesci ad accettarlo: D

    
risposta data 05.09.2014 - 23:50
fonte
4

Ugh. Sempre impressionante quando l'umanità mostra quanto può essere creativamente terribile ...

Sì, l'approccio corretto qui è di restituire un IEnumerable di qualunque sia il tuo tipo di errore (a meno che tu non stia usando solo eccezioni, nel qual caso esiste AggregateException ). Se stai facendo qualcosa di estraneo agli errori, il passaggio successivo è un oggetto speciale che ha una proprietà pass / fail ed è o espone un IEnumerable del tipo di errore.

    
risposta data 05.09.2014 - 22:35
fonte
3

Nei primi giorni di .NET e dei miei sforzi in .NET land ho usato lo stesso meccanismo, ovvero per segnalare ai chiamanti che l'oggetto raccolta / contenitore passato al metodo è stato modificato all'interno del metodo. Ma dopo un po 'ho capito cosa c'è di meglio nella risposta di Dan Ling sopra, cioè che questo è in realtà sbagliato: ref ha un significato diverso, e in realtà illustra qualcos'altro, cioè che la collezione passata può essere completamente diversa oggetto quando il metodo restituisce.

È meglio documentare usando tag di documenti XML (questo è quello che servono!) quale sia il comportamento del metodo rispetto a segnalarlo attraverso l'uso di "ref".

    
risposta data 06.09.2014 - 14:11
fonte

Leggi altre domande sui tag