it-swarm-eu.dev

Wann kann ein Konstruktor eine Ausnahme auslösen?

Wann kann ein Konstruktor eine Ausnahme auslösen? (Oder im Fall von Ziel C: Wann ist es für einen Initiator richtig, nil zurückzugeben?)

Es scheint mir, dass ein Konstruktor fehlschlagen sollte - und sich daher weigern sollte, ein Objekt zu erstellen -, wenn das Objekt nicht vollständig ist. Das heißt, der Konstruktor sollte einen Vertrag mit seinem Aufrufer haben, um ein funktionales und funktionierendes Objekt bereitzustellen, auf dem Methoden sinnvoll aufgerufen werden können. Ist das vernünftig?

197
Mark R Lindsey

Der Konstrukteur hat die Aufgabe, das Objekt in einen brauchbaren Zustand zu versetzen. Grundsätzlich gibt es zwei Denkrichtungen.

Eine Gruppe bevorzugt den zweistufigen Aufbau. Der Konstruktor versetzt das Objekt lediglich in einen Ruhezustand, in dem er sich weigert, irgendwelche Arbeiten auszuführen. Es gibt eine zusätzliche Funktion, die die eigentliche Initialisierung vornimmt.

Ich habe die Gründe für diesen Ansatz nie verstanden. Ich bin fest in der Gruppe, die eine einstufige Konstruktion unterstützt, bei der das Objekt vollständig initialisiert ist und nach der Konstruktion verwendet werden kann.

Einstufige Konstruktoren sollten werfen, wenn sie das Objekt nicht vollständig initialisieren können. Wenn das Objekt nicht initialisiert werden kann, darf es nicht existieren, daher muss der Konstruktor werfen.

261
Sebastian Redl

Eric Lippert sagt Es gibt 4 Arten von Ausnahmen.

  • Schwerwiegende Ausnahmen sind nicht Ihre Schuld, Sie können sie nicht verhindern und Sie können sie nicht vernünftig beseitigen.
  • Boneheaded-Ausnahmen sind Ihre eigene verdammte Schuld, Sie hätten sie verhindern können und daher sind sie Fehler in Ihrem Code.
  • Ärgerliche Ausnahmen sind das Ergebnis unglücklicher Designentscheidungen. Ärgerliche Ausnahmen werden unter ganz normalen Umständen ausgelöst und müssen daher immer wieder abgefangen und behandelt werden.
  • Und schließlich scheinen exogene Ausnahmen eher ärgerliche Ausnahmen zu sein, mit der Ausnahme, dass sie nicht das Ergebnis unglücklicher Designentscheidungen sind. Sie sind vielmehr das Ergebnis unordentlicher äußerer Realitäten, die sich auf Ihre schöne, klare Programmlogik auswirken.

Ihr Konstruktor sollte niemals selbst eine schwerwiegende Ausnahme auslösen, aber der von ihm ausgeführte Code kann eine schwerwiegende Ausnahme verursachen. So etwas wie "out of memory" können Sie nicht steuern, aber wenn es in einem Konstruktor auftritt, passiert es.

Boneheaded-Ausnahmen sollten in keinem Ihrer Codes vorkommen, daher sind sie ausgeschlossen.

Ärgerliche Ausnahmen (das Beispiel ist Int32.Parse()) sollten nicht von Konstruktoren geworfen werden, da sie keine nicht außergewöhnlichen Umstände haben.

Schließlich sollten exogene Ausnahmen vermieden werden. Wenn Sie jedoch in Ihrem Konstruktor etwas tun, das von äußeren Umständen (wie dem Netzwerk oder Dateisystem) abhängt, ist es angebracht, eine Ausnahme auszulösen.

Referenzlink: https://blogs.msdn.Microsoft.com/ericlippert/2008/09/10/vexing-exceptions/

56
Jacob Krall

Es gibt im Allgemeinen nichts, was durch Trennen der Objektinitialisierung von der Konstruktion erreicht werden kann. RAII ist korrekt. Ein erfolgreicher Aufruf des Konstruktors sollte entweder zu einem vollständig initialisierten Live-Objekt führen oder fehlschlagen und ALL zu jedem Zeitpunkt in einem beliebigen Code fehlschlagen path sollte immer eine Ausnahme auslösen. Mit einer separaten init () -Methode erhalten Sie nichts außer zusätzlicher Komplexität auf einer bestimmten Ebene. Der ctor-Vertrag sollte entweder sein, dass er ein funktionsfähiges Objekt zurückgibt oder dass er nach sich selbst aufräumt und wirft.

Wenn Sie eine separate init-Methode implementieren, müssen Sie sie noch aufrufen. Es wird immer noch das Potenzial haben, Ausnahmen auszulösen, sie müssen trotzdem behandelt werden und sie müssen praktisch immer sofort nach dem Konstruktor aufgerufen werden, außer jetzt haben Sie 4 mögliche Objektzustände anstelle von 2 (IE, konstruiert, initialisiert, nicht initialisiert, und fehlgeschlagen vs nur gültig und nicht vorhanden).

Auf jeden Fall habe ich in 25 Jahren OO Entwicklungsfällen, in denen es so aussieht, als ob eine separate Init-Methode ein Problem lösen würde, Designfehler gesehen. Wenn Sie ein Objekt JETZT nicht benötigen, sollten Sie es jetzt nicht erstellen. Wenn Sie es jetzt benötigen, müssen Sie es initialisieren. KISS sollte immer das Prinzip sein, zusammen mit dem einfachen Konzept, dass das Verhalten, der Zustand und die API einer Schnittstelle widerspiegeln sollten, WAS das Objekt tut, nicht WIE es es tut, der Client-Code sollte es nicht einmal wissen dass das Objekt einen beliebigen internen Zustand hat, der initialisiert werden muss, daher verstößt das init-after-Muster gegen dieses Prinzip.

30
Alhazred

Wegen all der Schwierigkeiten, die eine teilweise erstellte Klasse verursachen kann, würde ich nie sagen.

Wenn Sie während der Erstellung etwas validieren müssen, machen Sie den Konstruktor privat und definieren Sie eine öffentliche statische Factory-Methode. Die Methode kann werfen, wenn etwas ungültig ist. Aber wenn alles auscheckt, ruft es den Konstruktor auf, der garantiert nicht wirft.

6
Michael L Perry

Ein Konstruktor sollte eine Ausnahme auslösen, wenn er die Konstruktion des Objekts nicht abschließen kann.

Wenn der Konstruktor beispielsweise 1024 KB RAM zuweisen soll und dies nicht der Fall ist, sollte er eine Ausnahme auslösen. Auf diese Weise weiß der Aufrufer des Konstruktors, dass das Objekt nicht zur Verwendung bereit ist und ein Fehler vorliegt irgendwo muss das behoben werden.

Objekte, die zur Hälfte initialisiert und zur Hälfte tot sind, verursachen nur Probleme und Probleme, da der Anrufer wirklich keine Möglichkeit hat, dies zu erkennen. Ich möchte lieber, dass mein Konstruktor einen Fehler auslöst, wenn etwas schief geht, als dass er sich auf die Programmierung verlassen muss, um einen Aufruf der Funktion isOK () auszuführen, die true oder false zurückgibt.

5
Denice

Es ist immer ziemlich zwielichtig, besonders wenn Sie Ressourcen innerhalb eines Konstruktors zuweisen. Abhängig von Ihrer Sprache wird der Destruktor nicht aufgerufen, daher müssen Sie manuell bereinigen. Es hängt davon ab, wie die Lebensdauer eines Objekts in Ihrer Sprache beginnt.

Das einzige Mal, dass ich es wirklich getan habe, war, dass irgendwo ein Sicherheitsproblem aufgetreten ist, das bedeutet, dass das Objekt nicht erstellt werden sollte und nicht erstellt werden kann.

4
blowdart

Es ist sinnvoll, dass ein Konstruktor eine Ausnahme auslöst, solange er sich selbst ordnungsgemäß bereinigt. Wenn Sie dem Paradigma RAII (Ressourcenbeschaffung ist Initialisierung) folgen, ist es für einen Konstrukteur durchaus üblich, sinnvolle Arbeit zu leisten; Ein gut geschriebener Konstruktor bereinigt sich selbst, wenn er nicht vollständig initialisiert werden kann.

4
Matt Dillard

Soweit ich das beurteilen kann, präsentiert niemand eine ziemlich offensichtliche Lösung, die das Beste aus einstufiger und zweistufiger Konstruktion verkörpert.

note: Diese Antwort setzt C # voraus, aber die Prinzipien können in den meisten Sprachen angewendet werden.

Erstens, die Vorteile beider:

Eine Bühne

Eine einstufige Konstruktion kommt uns zugute, indem verhindert wird, dass Objekte in einem ungültigen Zustand existieren, wodurch alle Arten von fehlerhafter Zustandsverwaltung und alle damit verbundenen Fehler vermieden werden. Einige von uns fühlen sich jedoch komisch, weil wir nicht möchten, dass unsere Konstruktoren Ausnahmen auslösen, und manchmal ist dies das, was wir tun müssen, wenn Initialisierungsargumente ungültig sind.

public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    public Person(string name, DateTime dateOfBirth)
    {
        if (string.IsNullOrWhitespace(name))
        {
            throw new ArgumentException(nameof(name));
        }

        if (dateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(dateOfBirth));
        }

        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }
}

Zweistufig über die Validierungsmethode

Die zweistufige Konstruktion hat den Vorteil, dass unsere Validierung außerhalb des Konstruktors ausgeführt werden kann und daher keine Ausnahmen innerhalb des Konstruktors ausgelöst werden müssen. Es verbleiben jedoch "ungültige" Instanzen, was bedeutet, dass der Status für die Instanz nachverfolgt und verwaltet werden muss, oder wir werfen ihn sofort nach der Heap-Zuweisung weg. Es stellt sich die Frage: Warum führen wir eine Heap-Zuordnung und damit eine Speichersammlung für ein Objekt durch, das wir nicht einmal verwenden?

public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    public Person(string name, DateTime dateOfBirth)
    {
        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }

    public void Validate()
    {
        if (string.IsNullOrWhitespace(Name))
        {
            throw new ArgumentException(nameof(Name));
        }

        if (DateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(DateOfBirth));
        }
    }
}

Einstufig über privaten Konstruktor

Wie können wir also Ausnahmen von unseren Konstruktoren fernhalten und verhindern, dass wir Heap-Zuweisungen für Objekte ausführen, die sofort verworfen werden? Es ist ziemlich einfach: Wir machen den Konstruktor privat und erstellen Instanzen über eine statische Methode, die dazu bestimmt ist, eine Instanziierung und daher eine Heap-Zuweisung durchzuführen. Nur nach Validierung.

public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    private Person(string name, DateTime dateOfBirth)
    {
        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }

    public static Person Create(
        string name,
        DateTime dateOfBirth)
    {
        if (string.IsNullOrWhitespace(Name))
        {
            throw new ArgumentException(nameof(name));
        }

        if (dateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(DateOfBirth));
        }

        return new Person(name, dateOfBirth);
    }
}

Async Single-Stage über einen privaten Konstruktor

Abgesehen von den oben genannten Vorteilen bei der Validierung und Verhinderung der Heap-Zuweisung bietet uns die vorherige Methodik einen weiteren Vorteil: die asynchrone Unterstützung. Dies ist nützlich, wenn Sie sich mit mehrstufiger Authentifizierung befassen, z. B. wenn Sie ein Inhaber-Token abrufen müssen, bevor Sie Ihre API verwenden. Auf diese Weise erhalten Sie keinen ungültigen "abgemeldeten" API-Client. Stattdessen können Sie den API-Client einfach neu erstellen, wenn Sie beim Versuch, eine Anforderung auszuführen, einen Autorisierungsfehler erhalten.

public class RestApiClient
{
    public RestApiClient(HttpClient httpClient)
    {
        this.httpClient = new httpClient;
    }

    public async Task<RestApiClient> Create(string username, string password)
    {
        if (username == null)
        {
            throw new ArgumentNullException(nameof(username));
        }

        if (password == null)
        {
            throw new ArgumentNullException(nameof(password));
        }

        var basicAuthBytes = Encoding.ASCII.GetBytes($"{username}:{password}");
        var basicAuthValue = Convert.ToBase64String(basicAuthBytes);

        var authenticationHttpClient = new HttpClient
        {
            BaseUri = new Uri("https://auth.example.io"),
            DefaultRequestHeaders = {
                Authentication = new AuthenticationHeaderValue("Basic", basicAuthValue)
            }
        };

        using (authenticationHttpClient)
        {
            var response = await httpClient.GetAsync("login");
            var content = response.Content.ReadAsStringAsync();
            var authToken = content;
            var restApiHttpClient = new HttpClient
            {
                BaseUri = new Uri("https://api.example.io"), // notice this differs from the auth uri
                DefaultRequestHeaders = {
                    Authentication = new AuthenticationHeaderValue("Bearer", authToken)
                }
            };

            return new RestApiClient(restApiHttpClient);
        }
    }
}

Die Nachteile dieser Methode sind meiner Erfahrung nach gering.

Im Allgemeinen bedeutet diese Methode, dass Sie die Klasse nicht mehr als DTO verwenden können, da die Deserialisierung für ein Objekt ohne einen öffentlichen Standardkonstruktor bestenfalls schwierig ist. Wenn Sie das Objekt jedoch als DTO verwenden, sollten Sie das Objekt selbst nicht wirklich validieren, sondern die Werte für das Objekt ungültig machen, wenn Sie versuchen, sie zu verwenden, da die Werte technisch gesehen nicht "ungültig" sind zum DTO.

Dies bedeutet auch, dass Sie am Ende Factory-Methoden oder -Klassen erstellen, wenn Sie zulassen müssen, dass ein IOC Container das Objekt erstellt, da der Container sonst nicht weiß, wie das Objekt instanziiert wird. In vielen Fällen sind die Factory-Methoden jedoch eine von Create Methoden.

4
cwharris

Beachten Sie, dass beim Auslösen einer Ausnahme in einem Initialisierer ein Leck auftritt, wenn Code das Muster [[[MyObj alloc] init] autorelease] Verwendet, da durch die Ausnahme die Autorelease übersprungen wird.

Siehe diese Frage:

Wie können Sie Undichtigkeiten beim Auslösen einer Ausnahme in init verhindern?

3
stevex

Wenn Sie UI-Controls (ASPX, WinForms, WPF, ...) schreiben, sollten Sie vermeiden, Ausnahmen im Konstruktor auszulösen, da der Designer (Visual Studio) diese beim Erstellen der Controls nicht verarbeiten kann. Kennen Sie Ihren Steuerungslebenszyklus (Steuerungsereignisse) und verwenden Sie nach Möglichkeit eine verzögerte Initialisierung.

3
Nick

Siehe C++ FAQ Abschnitte 17.2 und 17.4 .

Im Allgemeinen habe ich festgestellt, dass Code einfacher zu portieren und zu verwalten ist, wenn Konstruktoren so geschrieben werden, dass sie nicht fehlschlagen. Code, der fehlschlagen kann, wird in eine separate Methode gestellt, die einen Fehlercode zurückgibt und das Objekt in einem inerten Zustand belässt .

3
moonshadow

Wenn Sie das Objekt im Konstruktor nicht initialisieren können, lösen Sie eine Ausnahme aus. Ein Beispiel sind ungültige Argumente.

Als Faustregel gilt, dass eine Ausnahme immer so schnell wie möglich ausgelöst werden sollte, da dies das Debuggen erleichtert, wenn die Ursache des Problems näher an der Methode liegt, die signalisiert, dass etwas nicht stimmt.

2
user14070

Sie sollten unbedingt eine Ausnahme von einem Konstruktor auslösen, wenn Sie kein gültiges Objekt erstellen können. Auf diese Weise können Sie die richtigen Invarianten in Ihrer Klasse bereitstellen.

In der Praxis müssen Sie möglicherweise sehr vorsichtig sein. Denken Sie daran, dass in C++ der Destruktor nicht aufgerufen wird. Wenn Sie also nach dem Zuweisen Ihrer Ressourcen werfen, müssen Sie sehr sorgfältig damit umgehen!

Diese Seite hat eine gründliche Diskussion der Situation in C++.

2
Luke Halliwell

Ich kann in Objective-C keine bewährten Methoden ansprechen, aber in C++ ist es in Ordnung, wenn ein Konstruktor eine Ausnahme auslöst. Zumal es keine andere Möglichkeit gibt, um sicherzustellen, dass ein außergewöhnlicher Zustand bei der Erstellung gemeldet wird, ohne eine isOK () -Methode aufzurufen.

Die Funktion try block wurde speziell zur Unterstützung von Fehlern bei der Initialisierung von Konstruktormitgliedern entwickelt (obwohl sie auch für reguläre Funktionen verwendet werden kann). Dies ist die einzige Möglichkeit, die ausgelösten Ausnahmeinformationen zu ändern oder zu erweitern. Aufgrund seines ursprünglichen Entwurfszwecks (Verwendung in Konstruktoren) ist es jedoch nicht zulässig, die Ausnahme durch eine leere catch () - Klausel zu verschlucken.

1
mlbrock

Ich bin mir nicht sicher, ob eine Antwort völlig sprachunabhängig sein kann. Einige Sprachen behandeln Ausnahmen und Speicherverwaltung unterschiedlich.

Ich habe vorher unter Codierungsstandards gearbeitet, die erfordern, dass niemals Ausnahmen verwendet werden und nur Fehlercodes auf Initialisierern, weil Entwickler von der Sprache gebrannt wurden, die Ausnahmen schlecht handhabt. Sprachen ohne Garbage Collection behandeln Heap und Stack sehr unterschiedlich, was für Nicht-RAII-Objekte von Bedeutung sein kann. Es ist jedoch wichtig, dass sich ein Team für die Konsistenz entscheidet, damit es standardmäßig weiß, ob es nach Konstruktoren Initialisierer aufrufen muss. Alle Methoden (einschließlich Konstruktoren) sollten auch gut dokumentiert sein, welche Ausnahmen sie auslösen können, damit die Aufrufer wissen, wie sie damit umgehen sollen.

Ich bin generell für eine einstufige Konstruktion, da man leicht vergisst, ein Objekt zu initialisieren, aber es gibt viele Ausnahmen.

  • Ihre Sprachunterstützung für Ausnahmen ist nicht sehr gut.
  • Sie haben einen dringenden Designgrund, new und delete weiterhin zu verwenden.
  • Ihre Initialisierung ist prozessorintensiv und sollte asynchron zu dem Thread ausgeführt werden, der das Objekt erstellt hat.
  • Sie erstellen eine DLL, die möglicherweise Ausnahmen außerhalb der Schnittstelle zu einer Anwendung in einer anderen Sprache auslöst. In diesem Fall handelt es sich möglicherweise weniger darum, keine Ausnahmen auszulösen, sondern sicherzustellen, dass sie vorhanden sind werden vor der öffentlichen Schnittstelle abgefangen. (Sie können C++ - Ausnahmen in C # abfangen, aber es gibt Rahmen, durch die Sie springen können.)
  • Statische Konstruktoren (C #)
1
Denise Skidmore

Ja, wenn der Konstruktor einen seiner internen Teile nicht erstellt, kann es - nach Wahl - seine Verantwortung sein, eine explizite Ausnahme auszulösen (und in einer bestimmten Sprache zu deklarieren) Konstruktordokumentation.

Dies ist nicht die einzige Option: Sie könnte den Konstruktor beenden und ein Objekt erstellen, aber mit einer Methode 'isCoherent ()', die false zurückgibt, um einen inkohärenten Zustand signalisieren zu können (dies ist in bestimmten Fällen vorzuziehen) um eine brutale Unterbrechung des Ausführungs-Workflows aufgrund einer Ausnahme zu vermeiden)
Warnung: Wie von EricSchaefer in seinem Kommentar erwähnt, kann dies zu einer gewissen Komplexität beim Testen von Einheiten führen (ein Wurf kann die zyklomatische Komplexität der Funktion aufgrund der auslösenden Bedingung erhöhen es)

Wenn es aufgrund des Aufrufers fehlschlägt (wie ein vom Aufrufer bereitgestelltes Nullargument, bei dem der aufgerufene Konstruktor ein Nicht-Null-Argument erwartet), löst der Konstruktor trotzdem eine ungeprüfte Laufzeitausnahme aus.

1
VonC

Das Auslösen einer Ausnahme während der Erstellung ist eine großartige Möglichkeit, Ihren Code komplexer zu gestalten. Dinge, die einfach erscheinen, werden plötzlich hart. Angenommen, Sie haben einen Stapel. Wie kann man den Stack platzieren und den Top-Wert zurückgeben? Nun, wenn die Objekte im Stapel ihre Konstruktoren einbinden können (die temporäre Instanz erstellen, um zum Aufrufer zurückzukehren), können Sie nicht garantieren, dass Sie keine Daten verlieren (Stapelzeiger dekrementieren, Rückgabewert mit dem Konstruktor copy von value in erstellen) Stapel, der wirft und jetzt einen Stapel hat, der gerade einen Gegenstand verloren hat)! Aus diesem Grund gibt std :: stack :: pop keinen Wert zurück und Sie müssen std :: stack :: top aufrufen.

Dieses Problem ist gut beschrieben hier , überprüfen Sie Punkt 10 und schreiben Sie ausnahmesicheren Code.

1
Don Neufeld

Der übliche Vertrag in OO ist, dass Objektmethoden tatsächlich funktionieren.

Um ein Zombie-Objekt niemals aus einem Konstruktor/init zurückzugeben.

Ein Zombie funktioniert nicht und möglicherweise fehlen interne Komponenten. Nur eine Nullzeiger-Ausnahme, die darauf wartet, passiert zu werden.

Ich habe vor vielen Jahren Zombies in Objective C gemacht.

Wie bei allen Faustregeln gibt es eine "Ausnahme".

Es ist durchaus möglich, dass eine spezifische Schnittstelle einen Vertrag hat, der besagt, dass eine Methode "initialize" existiert, die eine Ausnahme auslösen darf. Dass ein Objekt, das diese Schnittstelle implementiert, möglicherweise nicht korrekt auf Aufrufe mit Ausnahme von Eigenschaftssetzern reagiert, bis initialize aufgerufen wurde. Ich habe dies für Gerätetreiber in einem OO Betriebssystem während des Startvorgangs verwendet, und es war funktionsfähig.

Im Allgemeinen möchten Sie keine Zombie-Objekte. In Sprachen wie Smalltalk mit werden werden die Dinge ein wenig sprudelnd, aber ein übermäßiger Gebrauch von wird auch zu einem schlechten Stil. Become lässt ein Objekt in situ in ein anderes Objekt verwandeln, sodass weder Envelope-Wrapper (Advanced C++) noch das Strategiemuster (GOF) erforderlich sind.

1
Tim Williscroft

Die Frage des OP ist mit einem "sprachunabhängigen" Tag versehen. Diese Frage kann nicht für alle Sprachen/Situationen auf die gleiche Weise sicher beantwortet werden.

Die Klassenhierarchie des folgenden C # -Beispiels führt zum Konstruktor von Klasse B und überspringt einen sofortigen Aufruf von Klasse A IDisposeable.Dispose beim Verlassen von using des Mains, wobei die explizite Entsorgung der Ressourcen der Klasse A übersprungen wird.

Wenn zum Beispiel Klasse A beim Bau ein Socket erstellt hätte, das mit einer Netzwerkressource verbunden ist, wäre dies wahrscheinlich nach dem using -Block immer noch der Fall (eine relativ versteckte Anomalie).

class A : IDisposable
{
    public A()
    {
        Console.WriteLine("Initialize A's resources.");
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose A's resources.");
    }
}

class B : A, IDisposable
{
    public B()
    {
        Console.WriteLine("Initialize B's resources.");
        throw new Exception("B construction failure: B can cleanup anything before throwing so this is not a worry.");
    }

    public new void Dispose()
    {
        Console.WriteLine("Dispose B's resources.");
        base.Dispose();
    }
}
class C : B, IDisposable
{
    public C()
    {
        Console.WriteLine("Initialize C's resources. Not called because B throws during construction. C's resources not a worry.");
    }

    public new void Dispose()
    {
        Console.WriteLine("Dispose C's resources.");
        base.Dispose();
    }
}


class Program
{
    static void Main(string[] args)
    {
        try
        {
            using (C c = new C())
            {
            }
        }
        catch
        {           
        }

        // Resource's allocated by c's "A" not explicitly disposed.
    }
}
1
Ashley

Ich lerne gerade Objective C, kann also nicht wirklich aus Erfahrung sprechen, habe aber in den Apple-Dokumenten darüber gelesen.

http://developer.Apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/chapter_3_section_6.html

Hier erfahren Sie nicht nur, wie Sie mit der gestellten Frage umgehen sollen, sondern es wird auch eine gute Erklärung dafür geliefert.

0
Scott Swezey

Der beste Rat, den ich in Bezug auf Ausnahmen gesehen habe, ist, eine Ausnahme auszulösen, wenn die Alternative darin besteht, eine Post-Bedingung nicht zu erfüllen oder eine Invariante beizubehalten.

Dieser Ratschlag ersetzt eine unklare subjektive Entscheidung (ist es eine gute Idee) durch eine technische, präzise Frage, die auf Entwurfsentscheidungen (invarianten und Post-Bedingungen) basiert, die Sie bereits hätten treffen sollen.

Konstruktoren sind nur ein besonderer, aber kein besonderer Fall für diesen Rat. Es stellt sich also die Frage, welche Invarianten eine Klasse haben soll. Befürworter einer separaten Initialisierungsmethode, die nach der Erstellung aufgerufen werden soll, schlagen vor, dass die Klasse zwei oder mehr Betriebsmodus, mit einem noch nicht vorhandenen Modus nach der Erstellung und mindestens einem - aufweist. Bereit Modus, der nach der Initialisierung aufgerufen wird. Das ist eine zusätzliche Komplikation, aber akzeptabel, wenn die Klasse sowieso mehrere Betriebsmodi hat. Es ist schwer zu erkennen, wie sich diese Komplikation lohnt, wenn die Klasse sonst keine Betriebsmodi hätte.

Beachten Sie, dass Sie durch das Übertragen von Einstellungen in eine separate Initialisierungsmethode nicht vermeiden können, dass Ausnahmen ausgelöst werden. Ausnahmen, die Ihr Konstruktor möglicherweise ausgelöst hat, werden jetzt von der Initialisierungsmethode ausgelöst. Alle nützlichen Methoden Ihrer Klasse müssen Ausnahmen auslösen, wenn sie für ein nicht initialisiertes Objekt aufgerufen werden.

Beachten Sie auch, dass es in vielen Standardbibliotheken mühsam ist, zu vermeiden, dass Ihr Konstruktor Ausnahmen auslöst, und in vielen Fällen nmöglich. Dies liegt daran, dass die Designer dieser Bibliotheken glauben, dass es eine gute Idee ist, Ausnahmen von Konstruktoren zu werfen. Insbesondere kann jede Operation, die versucht, eine nicht gemeinsam nutzbare oder endliche Ressource abzurufen (z. B. das Zuweisen von Speicher), fehlschlagen. Dieser Fehler wird normalerweise in OO Sprachen und Bibliotheken durch Auslösen einer Ausnahme angezeigt.

0
Raedwald

Wenn Sie einen Konstruktor ausschließlich aus Sicht von Java mit unzulässigen Werten initialisieren, sollte er eine Ausnahme auslösen. Auf diese Weise wird es nicht in einem schlechten Zustand gebaut.

0
scubabbl

Für mich ist es eine etwas philosophische Designentscheidung.

Es ist sehr schön, Instanzen zu haben, die so lange gültig sind, wie sie existieren. In vielen nichttrivialen Fällen kann es erforderlich sein, Ausnahmen vom ctor auszulösen, wenn keine Speicher-/Ressourcenzuweisung vorgenommen werden kann.

Einige andere Ansätze sind die init () -Methode, die einige eigene Probleme mit sich bringt. Eine davon ist, sicherzustellen, dass init () tatsächlich aufgerufen wird.

Eine Variante verwendet einen trägen Ansatz, um init () automatisch aufzurufen, wenn ein Accessor/Mutator zum ersten Mal aufgerufen wird, dies erfordert jedoch, dass sich jeder potenzielle Aufrufer Sorgen machen muss, dass das Objekt gültig ist. (Im Gegensatz zu "es existiert, daher ist es gültige Philosophie").

Ich habe verschiedene vorgeschlagene Entwurfsmuster gesehen, die sich auch mit diesem Problem befassen. B. in der Lage sein, ein erstes Objekt über ctor zu erstellen, aber init () aufrufen müssen, um mit accesors/mutators an ein enthaltenes, initialisiertes Objekt zu gelangen.

Jeder Ansatz hat seine Höhen und Tiefen; Ich habe alle diese erfolgreich eingesetzt. Wenn Sie keine gebrauchsfertigen Objekte erstellen, sobald sie erstellt wurden, empfehle ich eine große Menge von Zusicherungen oder Ausnahmen, um sicherzustellen, dass Benutzer nicht vor init () interagieren.

Nachtrag

Ich schrieb aus der Perspektive eines C++ - Programmierers. Ich gehe auch davon aus, dass Sie das RAII-Idiom ordnungsgemäß verwenden, um Ressourcen zu behandeln, die freigegeben werden, wenn Ausnahmen ausgelöst werden.

0
nsanders

Mit Factorys oder Factory-Methoden für die gesamte Objekterstellung können Sie ungültige Objekte vermeiden, ohne Ausnahmen von Konstruktoren auszulösen. Die Erstellungsmethode sollte das angeforderte Objekt zurückgeben, wenn es eines erstellen kann, oder null, wenn dies nicht der Fall ist. Sie verlieren ein wenig Flexibilität bei der Behandlung von Konstruktionsfehlern beim Benutzer einer Klasse, da Sie durch die Rückgabe von null nicht wissen, was bei der Objekterstellung falsch gelaufen ist. Es wird jedoch auch vermieden, dass bei jeder Anforderung eines Objekts die Komplexität mehrerer Ausnahmebehandlungsroutinen erhöht wird und das Risiko besteht, dass Ausnahmen abgefangen werden, die Sie nicht behandeln sollten.

0