Domanda

Ho una classe "Status" in C#, usata in questo modo:

Status MyFunction()
{
   if(...) // something bad
     return new Status(false, "Something went wrong")
   else
     return new Status(true, "OK");
}

Hai capito.Tutti i chiamanti di MyFunction Dovrebbe controlla lo stato restituito:

Status myStatus = MyFunction();
if ( ! myStatus.IsOK() )
   // handle it, show a message,...

I chiamanti pigri tuttavia possono ignorare lo stato.

MyFunction(); // call function and ignore returned Status

O

{
  Status myStatus = MyFunction(); 
} // lose all references to myStatus, without calling IsOK() on it

È possibile renderlo impossibile?per esempio.un'eccezione di lancio

Generalmente:è possibile scrivere una classe C# su cui tu Avere chiamare una determinata funzione?

Nella versione C++ della classe Status, posso scrivere un test su alcuni bool bIsChecked privati ​​nel file distruttore e suona qualche campanello quando qualcuno non controlla questa istanza.

Qual è l'opzione equivalente in C#?Ho letto da qualche parte che "Non vuoi un distruttore nella tua classe C#"

Il metodo Dispose dell'interfaccia IDisposable è un'opzione?

In questo caso non ci sono risorse non gestite da liberare.Inoltre, non è determinato Quando il GC eliminerà l'oggetto.Quando alla fine viene eliminato, è ancora possibile sapere dove e quando hai ignorato quella specifica istanza di Status?La parola chiave "using" aiuta, ma ancora una volta non lo è necessario per i chiamanti pigri.

È stato utile?

Soluzione

Sono abbastanza certo che non puoi ottenere l'effetto desiderato come valore di ritorno da un metodo.C# semplicemente non può fare alcune delle cose che C++ può fare.Tuttavia, un modo un po’ brutto per ottenere un effetto simile è il seguente:

using System;

public class Example
{
    public class Toy
    {
        private bool inCupboard = false;
        public void Play() { Console.WriteLine("Playing."); }
        public void PutAway() { inCupboard = true; }
        public bool IsInCupboard { get { return inCupboard; } }
    }

    public delegate void ToyUseCallback(Toy toy);

    public class Parent
    {
        public static void RequestToy(ToyUseCallback callback)
        {
            Toy toy = new Toy();
            callback(toy);
            if (!toy.IsInCupboard)
            {
                throw new Exception("You didn't put your toy in the cupboard!");
            }
        }
    }

    public class Child
    {
        public static void Play()
        {
            Parent.RequestToy(delegate(Toy toy)
            {
                toy.Play();
                // Oops! Forgot to put the toy away!
            });
        }
    }

    public static void Main()
    {
        Child.Play();
        Console.ReadLine();
    }
}

Nell'esempio molto semplice, ottieni un'istanza di Toy chiamando Parent.RequestToy, e passandolo a un delegato.Invece di restituire il giocattolo, il metodo chiama immediatamente il delegato con il giocattolo, che deve chiamare PutAway prima di restituire, altrimenti il ​​metodo RequestToy genererà un'eccezione.Non faccio affermazioni sulla saggezza dell'uso di questa tecnica - anzi in tutti gli esempi "qualcosa è andato storto" un'eccezione è quasi certamente una scommessa migliore - ma penso che si avvicini il più possibile alla tua richiesta originale.

Altri suggerimenti

So che questo non risponde direttamente alla tua domanda, ma se "qualcosa è andato storto" all'interno della tua funzione (circostanze impreviste) penso che dovresti lanciare un'eccezione anziché utilizzare i codici di ritorno dello stato.

Quindi lascia che sia il chiamante a catturare e gestire questa eccezione se può, o consentirne la propagazione se il chiamante non è in grado di gestire la situazione.

L'eccezione generata potrebbe essere di tipo personalizzato se appropriato.

Per previsto risultati alternativi, sono d'accordo con il suggerimento di @Jon Limjap.Mi piace il tipo di ritorno bool e anteporre al nome del metodo "Prova", come:

bool TryMyFunction(out Status status)
{
}

Se vuoi davvero richiedere all'utente di recuperare il risultato di MyFunction, potresti invece volerlo annullarlo e utilizzare una variabile out o ref, ad esempio,

void MyFunction(out Status status)
{
}

Potrebbe sembrare brutto ma almeno garantisce che una variabile venga passata alla funzione che raccoglierà il risultato che ti serve.

@Ian,

Il problema con le eccezioni è che se si tratta di qualcosa che accade un po' troppo spesso, potresti spendere troppe risorse di sistema per l'eccezione.Dovrebbe davvero essere utilizzata un'eccezione eccezionale errori, messaggi non del tutto attesi.

Anche System.Net.WebRequest genera un'eccezione quando il codice di stato HTTP restituito è un codice di errore.Il modo tipico di gestirlo è avvolgerlo attorno a un try/catch.Puoi comunque ignorare il codice di stato nel blocco catch.

Potresti, tuttavia, avere un parametro Action< Status> in modo che il chiamante sia costretto a passare una funzione di callback che accetta uno stato e quindi a controllare se lo ha chiamato.

void MyFunction(Action<Status> callback)
 { bool errorHappened = false;

   if (somethingBadHappend) errorHappened = true;

   Status status = (errorHappend)
                     ? new Status(false, "Something went wrong")
                     : new Status(true, "OK");
   callback(status)

   if (!status.isOkWasCalled) 
     throw new Exception("Please call IsOK() on Status"). 
 }

MyFunction(status => if (!status.IsOK()) onerror());

Se sei preoccupato che chiamino IsOK() senza fare nulla, usa invece Expression< Func< Status,bool>> e poi puoi analizzare lambda per vedere cosa fanno con lo stato:

void MyFunction(Expression<Func<Status,bool>> callback)
 { if (!visitCallbackExpressionTreeAndCheckForIsOKHandlingPattern(callback))
     throw new Exception
                ("Please handle any error statuses in your callback");


   bool errorHappened = false;

   if (somethingBadHappend) errorHappened = true;

   Status status = (errorHappend)
                     ? new Status(false, "Something went wrong")
                     : new Status(true, "OK");

   callback.Compile()(status);
 }

MyFunction(status => status.IsOK() ? true : onerror());

Oppure rinunciare del tutto alla classe di stato e farli passare in un delegato in caso di successo e in un altro in caso di errore:

void MyFunction(Action success, Action error)
 { if (somethingBadHappened) error(); else success();
 }

MyFunction(()=>;,()=>handleError()); 

Usare Status come valore di ritorno mi ricorda i "vecchi tempi" della programmazione C, quando restituivi un numero intero inferiore a 0 se qualcosa non funzionava.

Non sarebbe meglio se lanciassi un'eccezione quando (come dici tu) qualcosa è andato storto?Se qualche "codice pigro" non rileva la tua eccezione, lo saprai per certo.

Invece di costringere qualcuno a controllare lo stato, penso che dovresti presumere che il programmatore sia consapevole dei rischi derivanti dal non farlo e abbia un motivo per intraprendere tale linea di condotta.Non sai come verrà utilizzata la funzione in futuro e porre una limitazione del genere limita solo le possibilità.

Sarebbe sicuramente carino che il compilatore lo controllasse piuttosto che attraverso un'espressione.:/ Non vedere un modo per farlo però ...

Puoi lanciare un'eccezione:

throw MyException;


[global::System.Serializable]
        public class MyException : Exception
        {
        //
        // For guidelines regarding the creation of new exception types, see
        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp
        // and
        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
        //

        public MyException () { }
        public MyException ( string message ) : base( message ) { }
        public MyException ( string message, Exception inner ) : base( message, inner ) { }
        protected MyException (
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context )
            : base( info, context ) { }
    }

L'eccezione di cui sopra è completamente personalizzabile in base alle tue esigenze.

Una cosa che direi è questa, lascerei che sia il chiamante a controllare il codice di ritorno, è sua responsabilità fornire solo i mezzi e l'interfaccia.Inoltre, è molto più efficiente utilizzare i codici di ritorno e controllare lo stato con un'istruzione if anziché generare eccezioni.Se è davvero un Eccezionale circostanza, allora butta via a tutti i costi...ma diciamo che se non sei riuscito ad aprire un dispositivo, potrebbe essere più prudente attenersi al codice di ritorno.

@Paul potresti farlo in fase di compilazione C# estensibile.

Il GCC ha un warn_unused_result attributo ideale per questo genere di cose.Forse i compilatori Microsoft hanno qualcosa di simile.

Un modello che a volte può essere utile se l'oggetto a cui il codice invia le richieste verrà utilizzato solo da un singolo thread(*) è quello di fare in modo che l'oggetto mantenga uno stato di errore e di dire che se un'operazione fallisce l'oggetto sarà inutilizzabile fino al lo stato di errore viene ripristinato (le richieste future dovrebbero fallire immediatamente, preferibilmente lanciando un'eccezione immediata che includa informazioni sia sull'errore precedente che sulla nuova richiesta).Nei casi in cui il codice chiamante anticipa un problema, ciò potrebbe consentire al codice chiamante di gestire il problema in modo più pulito rispetto a quando viene generata un'eccezione;i problemi che non vengono ignorati dal codice chiamante generalmente finiscono per attivare un'eccezione subito dopo essersi verificati.

(*) Se a una risorsa accederanno più thread, creare un oggetto wrapper per ciascun thread e fare in modo che le richieste di ciascun thread passino attraverso il proprio wrapper.

Questo modello è utilizzabile anche in contesti in cui non esistono eccezioni e talvolta può essere molto pratico in questi casi.In generale, tuttavia, qualche variazione del modello try/do è solitamente migliore.I metodi lanciano un'eccezione in caso di errore a meno che il chiamante non indichi esplicitamente (utilizzando a TryXX metodo) che si prevedono errori.Se i chiamanti dicono che si prevedono errori ma non li gestiscono, è un problema loro.Si potrebbe combinare il tentativo con un secondo livello di protezione utilizzando lo schema sopra, ma non sono sicuro che ne varrebbe la pena.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top