Bevor ich Code Contracts verwendet habe, bin ich bei der Verwendung von Konstruktorverkettungen manchmal auf die Fehlerhaftigkeit der Parametervalidierung gestoßen.
Dies lässt sich am einfachsten mit einem (erfundenen) Beispiel erklären:
%Vor% Ich möchte, dass der Test(string)
-Konstruktor den Test(int)
-Konstruktor verkettet, und dazu verwende ich int.Parse()
.
Natürlich gefällt es int.Parse()
nicht, ein Null-Argument zu haben. Wenn also s null ist, wird es geworfen, bevor ich die Validierungszeilen erreiche:
was diese Überprüfung unbrauchbar macht.
Wie behebt man das? Nun, manchmal habe ich das gemacht:
%Vor%Das ist ein bisschen fummelig, und der Stack-Trace ist nicht ideal, wenn er fehlschlägt, aber es funktioniert.
Jetzt kommen Code Contracts, also benutze ich sie:
%Vor%Alles gut und gut. Es funktioniert gut. Aber dann entdecke ich, dass ich das tun kann:
%Vor% Und wenn ich var test = new Test(null)
mache, wird Contract.Requires(s != null)
vor this(int.Parse(s))
ausgeführt. Das bedeutet, dass ich den convertArg()
-Test ganz weglassen kann!
Also zu meinen aktuellen Fragen:
Ja, das Verhalten ist in der Definition von "precondition" dokumentiert, und in der Art und Weise, wie Legacy Verification (if / then / throw) ohne Aufruf von Contract.EndContractBlock
behandelt wird.
Wenn Sie Contract.Requires
nicht verwenden möchten, können Sie Ihren Konstruktor in
Wenn Sie einen Contract.*
-Aufruf in Ihren Code einfügen, rufen Sie nicht tatsächlich ein Mitglied im System.Diagnostics.Contracts
-Namespace auf. Zum Beispiel ist Contract.Requires(bool)
wie folgt definiert:
AssertMustUseRewriter
gibt bedingungslos eine ContractException
aus. Wenn die kompilierte Binärdatei nicht überschrieben wird, stürzt der Code einfach ab, wenn CONTRACTS_FULL
definiert ist. Wenn es nicht definiert ist, wird die Vorbedingung niemals überprüft, da der Aufruf von Requires
vom C # -Compiler aufgrund des Vorhandenseins von Attribut [Conditional]
.
Ausgehend von den in den Projekteigenschaften ausgewählten Einstellungen definiert Visual Studio CONTRACTS_FULL
und ruft ccrewrite
auf, um die entsprechende IL zu generieren, um die Verträge zur Laufzeit zu überprüfen.
Beispielvertrag:
%Vor% Kompiliert mit csc program.cs /out:nocontract.dll
, erhalten Sie:
Kompiliert mit csc program.cs /define:CONTRACTS_FULL /out:prerewrite.dll
und durchlaufen ccrewrite -assembly prerewrite.dll -out postrewrite.dll
erhalten Sie den Code, der tatsächlich Laufzeitprüfung durchführt:
Von vorrangigem Interesse ist, dass unser Ensures
(eine Nachbedingung) an den unteren Rand der Methode gerückt wurde und unser Requires
(eine Voraussetzung) nicht wirklich verschoben wurde, da es bereits an der Spitze der Methode war.
Dies passt zu der Definition der Dokumentation :
[Vorbedingungen] sind Verträge über den Zustand der Welt, wenn eine Methode aufgerufen wird.
...
Nachbedingungen sind Verträge über den Zustand einer Methode beim Beenden. Mit anderen Worten, die Bedingung wird kurz vor dem Beenden einer Methode überprüft.
Nun besteht die Komplexität in Ihrem Szenario bereits in der Definition einer Vorbedingung. Ausgehend von der oben aufgeführten Definition wird die Vorbedingung ausgeführt, bevor die Methode ausgeführt wird. Das Problem ist, dass die C # -Spezifikation besagt, dass der Konstruktorinitialisierer (verketteter Konstruktor) unmittelbar vor dem Konstruktorkörper aufgerufen werden muss. [CSHARP 10.11.1] , was im Widerspruch zur Definition einer Vorbedingung steht.
Der Code, den ccrewrite
generiert, kann daher nicht als C # dargestellt werden, da die Sprache keinen Mechanismus zum Ausführen von Code vor dem verketteten Konstruktor bietet (außer durch Aufrufen von statischen Methoden in der Liste der verketteten Konstruktorparameter wie erwähnt). ccrewrite
, wie von der Definition gefordert, nimmt Ihren Konstruktor
welches als
kompiliert wird
und verschiebt den Aufruf zu Requests vor dem Aufruf des verketteten Konstruktors:
Um zu vermeiden, dass Sie bei der Argumentvalidierung auf statische Methoden zurückgreifen müssen, verwenden Sie den Contract Rewriter. Sie können den Rewriter mit Contract.Requires
aufrufen oder indem Sie angeben, dass ein Codeblock Voraussetzung ist, indem Sie ihn mit Contract.EndContractBlock();
beenden. Dadurch wird der Rewriter vor dem Aufruf des Konstruktorinitialisierers an den Anfang der Methode gesetzt.
Tags und Links c# code-contracts constructor-chaining