Frage

Ich habe eine „Status“-Klasse in C#, die wie folgt verwendet wird:

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

Du hast die Idee.Alle Aufrufer von MyFunction sollen Überprüfen Sie den zurückgegebenen Status:

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

Faule Anrufer können den Status jedoch ignorieren.

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

oder

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

Ist es möglich, dies unmöglich zu machen?z.B.eine Throw-Ausnahme

Allgemein:Ist es möglich, eine C#-Klasse zu schreiben, auf der Sie haben eine bestimmte Funktion aufrufen?

In der C++-Version der Status-Klasse kann ich einen Test für einen privaten Bool bIsChecked in schreiben Zerstörer und einige Alarmglocken klingeln lassen, wenn jemand diesen Fall nicht überprüft.

Was ist die entsprechende Option in C#?Ich habe irgendwo gelesen, dass „Sie keinen Destruktor in Ihrer C#-Klasse wollen“

Ist die Dispose-Methode der IDisposable-Schnittstelle eine Option?

In diesem Fall sind keine nicht verwalteten Ressourcen freizugeben.Darüber hinaus ist es nicht bestimmt Wann Der GC wird das Objekt entsorgen.Ist es nach der endgültigen Entsorgung immer noch möglich zu wissen, wo und wann Sie diese bestimmte Statusinstanz ignoriert haben?Das Schlüsselwort „using“ hilft zwar, ist es aber wiederum nicht erforderlich für faule Anrufer.

War es hilfreich?

Lösung

Ich bin mir ziemlich sicher, dass Sie als Rückgabewert einer Methode nicht den gewünschten Effekt erzielen können.C# kann einige der Dinge, die C++ kann, einfach nicht.Eine etwas hässliche Möglichkeit, einen ähnlichen Effekt zu erzielen, ist jedoch die folgende:

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();
    }
}

Im sehr einfachen Beispiel erhalten Sie eine Instanz von Toy, indem Sie Parent.RequestToy aufrufen. und es einem Delegierten übergeben.Anstatt das Spielzeug zurückzugeben, ruft die Methode sofort den Delegaten mit dem Spielzeug auf, der vor der Rückgabe PutAway aufrufen muss, sonst löst die Methode „RequestToy“ eine Ausnahme aus.Ich erhebe keinen Anspruch auf die Sinnhaftigkeit der Anwendung dieser Technik – in der Tat ist in allen „etwas ist schief gelaufen“-Beispielen eine Ausnahme mit ziemlicher Sicherheit die bessere Wahl – aber ich denke, sie kommt Ihrer ursprünglichen Anfrage so nahe wie möglich.

Andere Tipps

Ich weiß, dass dies Ihre Frage nicht direkt beantwortet, aber wenn in Ihrer Funktion „etwas schief gelaufen ist“ (unerwartete Umstände), sollten Sie meiner Meinung nach eine Ausnahme auslösen, anstatt Statusrückgabecodes zu verwenden.

Überlassen Sie es dann dem Aufrufer, diese Ausnahme abzufangen und zu behandeln, wenn dies möglich ist, oder lassen Sie zu, dass sie sich ausbreitet, wenn der Aufrufer nicht in der Lage ist, mit der Situation umzugehen.

Die ausgelöste Ausnahme könnte gegebenenfalls einen benutzerdefinierten Typ haben.

Für erwartet alternative Ergebnisse, ich stimme dem Vorschlag von @Jon Limjap zu.Ich mag einen bool-Rückgabetyp und stelle dem Methodennamen „Try“ voran, a la:

bool TryMyFunction(out Status status)
{
}

Wenn Sie wirklich möchten, dass der Benutzer das Ergebnis von MyFunction abruft, möchten Sie es möglicherweise stattdessen ungültig machen und eine Out- oder Ref-Variable verwenden, z. B.

void MyFunction(out Status status)
{
}

Es sieht vielleicht hässlich aus, stellt aber zumindest sicher, dass eine Variable an die Funktion übergeben wird, die das gewünschte Ergebnis abruft.

@Ian,

Das Problem bei Ausnahmen besteht darin, dass Sie möglicherweise zu viele Systemressourcen für die Ausnahme aufwenden, wenn sie etwas zu häufig passieren.Eine Ausnahme sollte wirklich verwendet werden außergewöhnlich Fehler, nicht völlig erwartete Nachrichten.

Sogar System.Net.WebRequest löst eine Ausnahme aus, wenn der zurückgegebene HTTP-Statuscode ein Fehlercode ist.Die typische Art, damit umzugehen, besteht darin, es mit einem Try/Catch zu umschließen.Sie können den Statuscode im Catch-Block weiterhin ignorieren.

Sie könnten jedoch den Parameter Action< Status> verwenden, sodass der Aufrufer gezwungen wird, eine Rückruffunktion zu übergeben, die einen Status akzeptiert, und dann prüft, ob er ihn aufgerufen hat.

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());

Wenn Sie befürchten, dass sie IsOK() aufrufen, ohne etwas zu tun, verwenden Sie stattdessen Expression< Func< Status,bool>> und analysieren Sie dann das Lambda, um zu sehen, was sie mit dem Status machen:

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());

Oder verzichten Sie ganz auf die Statusklasse und lassen Sie sie einen Delegaten für Erfolg und einen anderen für einen Fehler übergeben:

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

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

Die Verwendung von Status als Rückgabewert erinnert mich an die „alten Zeiten“ der C-Programmierung, als man eine Ganzzahl unter 0 zurückgab, wenn etwas nicht funktionierte.

Wäre es nicht besser, wenn Sie eine Ausnahme auslösen würden, wenn (wie Sie es ausdrücken) etwas ist schief gelaufen?Wenn ein „Lazy Code“ Ihre Ausnahme nicht abfängt, wissen Sie es sicher.

Anstatt jemanden zu zwingen, den Status zu überprüfen, sollten Sie meiner Meinung nach davon ausgehen, dass der Programmierer sich der Risiken bewusst ist, wenn er dies nicht tut, und einen Grund hat, diese Vorgehensweise zu ergreifen.Sie wissen nicht, wie die Funktion in Zukunft genutzt werden soll, und eine solche Einschränkung schränkt nur die Möglichkeiten ein.

Es wäre sicherlich schön, wenn der Compiler dies überprüfen würde, anstatt dies anhand eines Ausdrucks zu tun.:/ Sehen Sie jedoch keine Möglichkeit, das zu tun ...

Sie können eine Ausnahme auslösen, indem Sie:

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 ) { }
    }

Die obige Ausnahme kann vollständig an Ihre Anforderungen angepasst werden.

Eine Sache, die ich sagen würde, ist Folgendes: Ich würde es dem Anrufer überlassen, den Rückkehrcode zu überprüfen. Es liegt in seiner Verantwortung, nur die Mittel und die Schnittstelle bereitzustellen.Außerdem ist es viel effizienter, Rückgabecodes zu verwenden und den Status mit einer if-Anweisung zu überprüfen, als Ausnahmen auszulösen.Wenn es wirklich ein ist Außergewöhnlich Umstand, dann unbedingt wegwerfen...Wenn Sie jedoch beispielsweise ein Gerät nicht öffnen konnten, ist es möglicherweise sinnvoller, beim Rückkehrcode zu bleiben.

@Paul, mit dem du es zur Kompilierzeit machen könntest Erweiterbares C#.

GCC hat eine warn_unused_result Attribut, das für so etwas ideal ist.Vielleicht haben die Microsoft-Compiler etwas Ähnliches.

Ein Muster, das manchmal hilfreich sein kann, wenn das Objekt, an das der Code Anforderungen sendet, nur von einem einzelnen Thread (*) verwendet wird, besteht darin, das Objekt in einem Fehlerzustand zu halten und anzugeben, dass das Objekt bei einem Fehlschlag einer Operation bis zum nächsten Mal unbrauchbar ist Der Fehlerstatus wird zurückgesetzt (zukünftige Anforderungen sollten sofort fehlschlagen, vorzugsweise durch Auslösen einer sofortigen Ausnahme, die Informationen sowohl zum vorherigen Fehler als auch zur neuen Anforderung enthält).In Fällen, in denen der aufrufende Code zufällig ein Problem vorhersieht, kann dies dazu führen, dass der aufrufende Code das Problem sauberer behandelt, als wenn eine Ausnahme ausgelöst würde.Probleme, die vom aufrufenden Code nicht ignoriert werden, lösen in der Regel ziemlich bald nach ihrem Auftreten eine Ausnahme aus.

(*) Wenn mehrere Threads auf eine Ressource zugreifen, erstellen Sie ein Wrapper-Objekt für jeden Thread und lassen Sie die Anforderungen jedes Threads seinen eigenen Wrapper durchlaufen.

Dieses Muster ist auch in Kontexten verwendbar, in denen es keine Ausnahmen gibt, und kann in solchen Fällen manchmal sehr praktisch sein.Im Allgemeinen sind jedoch einige Variationen des Try/Do-Musters meist besser.Lassen Sie Methoden bei einem Fehler eine Ausnahme auslösen, es sei denn, der Aufrufer gibt dies ausdrücklich an (durch Verwendung von a TryXX Methode), dass Ausfälle zu erwarten sind.Wenn Anrufer sagen, dass Ausfälle zu erwarten seien, sich aber nicht darum kümmern, ist das ihr Problem.Man könnte den Versuch mit einer zweiten Schutzschicht kombinieren, indem man das obige Schema verwendet, aber ich bin mir nicht sicher, ob sich die Kosten dafür lohnen würden.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top