2009-03-21 2 views
29

Ein häufiges Problem in jeder Sprache besteht darin, zu bestätigen, dass Parameter, die an eine Methode gesendet werden, Ihren Anforderungen entsprechen, und wenn dies nicht der Fall ist, um nette, informative Fehlermeldungen zu senden. Diese Art von Code wird immer wieder wiederholt, und wir versuchen oft, Helfer dafür zu schaffen. In C# scheint es jedoch, dass diese Helfer gezwungen sind, sich mit einigen Duplikaten zu befassen, die uns von der Sprache und dem Compiler auferlegt wurden. Um zu zeigen, was ich meine, lassen Sie mich etwas rohen Code ohne Helfer vorstellen, gefolgt von einem möglichen Helfer. Dann werde ich auf die Duplizierung im Helfer hinweisen und meine Frage genau formulieren.Was ist die glatteste, ansprechendste Syntax, die Sie gefunden haben, um Parameterkorrektheit in C# zu bestätigen?

Zuerst wird der Code ohne Helfer:

public void SomeMethod(string firstName, string lastName, int age) 
{ 
    if(firstName == null) 
    { 
      throw new WhateverException("The value for firstName cannot be null."); 
    } 

    if(lastName == null) 
    { 
      throw new WhateverException("The value for lastName cannot be null."); 
    } 

    // Same kind of code for age, making sure it is a reasonable range (< 150, for example). 
    // You get the idea 
} 

}

der Code mit einem vernünftigen Versuch eines Helfer nun:

public void SomeMethod(string firstName, string lastName, int age) 
{ 
     Helper.Validate(x=> x !=null, "firstName", firstName); 
     Helper.Validate(x=> x!= null, "lastName", lastName); 
} 

der Haupt Frage ist das: Beachten Sie, wie der Code passen muss s der Wert des Parameters und der Name des Parameters ("firstName" und firstName). Dies ist so die Fehlermeldung kann sagen, "Blah bla bla der Wert für den firstName Parameter." Hast du irgendeinen Weg gefunden, um dies durch Reflexion oder irgendetwas anderes zu umgehen? Oder eine Möglichkeit, es weniger schmerzhaft zu machen?

Und ganz allgemein, haben Sie andere Möglichkeiten gefunden, um diese Aufgabe der Validierung von Parametern zu rationalisieren und gleichzeitig die Code-Duplizierung zu reduzieren?

EDIT: Ich habe gelesen, dass Menschen über die Verwendung der Parameter-Eigenschaft sprechen, aber nie einen Weg um die Duplizierung gefunden. Hat jemand Glück dabei?

Danke!

+0

Verwendung von 'ApplicationException' Klasse wird nicht empfohlen. –

+0

Nitpicking: _Direct_ Verwendung von 'ApplicationException' wird nicht empfohlen –

+0

OK, ich habe es in Ausnahme geändert, nur um Ablenkungen zu vermeiden.Abgesehen davon, wenn die Empfehlung auf Kommentaren der Jungs basiert, die das "Framework Guidelines" -Buch geschrieben haben, bin ich nicht wirklich davon überzeugt (obwohl diese Jungs ein paar gute Sachen geschrieben haben). Aber das ist eine Diskussion für einen anderen Tag. –

Antwort

6
+0

Danke! Dies führte zu zwei Ansätzen, die mich beide umgehauen haben! Und ich kann sehen, sie beide zu benutzen. –

+0

Diese Implementierung der fließenden Validierung ist ziemlich glatt huh. – core

+0

Ja, es ist sehr glatt. Ich hatte nicht bemerkt, dass Sie eine Erweiterungsmethode für eine Variable aufrufen können, die null ist. Ich weiß nicht, ob Anders damit gemeint hat oder ob es ein Nebeneffekt ist. Wie auch immer, es ist sehr hilfreich. –

0

In diesem Fall eher als die eigenen Ausnahmetyp zu verwenden, oder wirklich allgemeine Typen wie Application .. Ich denke, es am besten ist es, den eingebauten Ausnahmetypen zu verwenden, die für diese Anwendung speziell bestimmt sind:

Unter denen, .. System.ArgumentException, System.ArgumentNullException ...

+0

OK, aber bitte ... konzentrieren wir uns nicht auf den Ausnahmetyp. Das ist eine Nebensache zum Kernthema und deshalb tippte ich einfach das erste, was mir in den Sinn kam. –

+0

Gut .. aber ich glaube, dass jedes Mal, wenn Sie Ausnahmen werfen, ein nützlicher Ausnahme-Typ unbedingt erforderlich ist. – markt

15

Sie sollten überprüfen, Code Contracts; Sie tun ziemlich genau das, was Sie fragen. Beispiel:

[Pure] 
public static double GetDistance(Point p1, Point p2) 
{ 
    CodeContract.RequiresAlways(p1 != null); 
    CodeContract.RequiresAlways(p2 != null); 
    // ... 
} 
+0

Hey, das ist cool ... vor allem, dass es in C# 4.0 in die Sprache eingebaut wird. Aber trotzdem ... was können wir in der Zwischenzeit tun? –

+0

Nun, sie werden in .NET 4 gebacken, aber Sie können sie jetzt noch verwenden. Sie hängen nicht von etwas ab, das für .NET 4 spezifisch ist, also können Sie den ganzen Weg zurück zu 2.0 gehen, wenn Sie möchten. Das Projekt ist hier: http://research.microsoft.com/en-us/projects/contracts/ –

+0

Sie können an Ihren eigenen Klassen arbeiten, die so aussehen, aber Ausnahmen für Bibliotheken werfen. Ziemlich einfach, eins zu schreiben. – user7116

0

Sie wissen nicht, ob diese Technik Transfers von C/C++ zu C#, aber ich habe dies getan, mit Makros:

 

    #define CHECK_NULL(x) { (x) != NULL || \ 
      fprintf(stderr, "The value of %s in %s, line %d is null.\n", \ 
      #x, __FILENAME__, __LINE__); } 
+0

Das ist genau die Art von was ich hoffe, aber glaube nicht, dass es direkt in C# getan werden kann. –

+0

Die #x heißt hier der Stringisizing-Operator und nein, wir haben in C nichts dergleichen. # –

+0

C# unterstützt nicht die Art von Präprozessor-Tricks, die man in C++ - Compilern findet. –

1

Meine Präferenz nur wäre, den Zustand zu bewerten und Übergeben Sie das Ergebnis, statt einen auszuwertenden Ausdruck und den Parameter, an dem er ausgewertet werden soll, zu übergeben. Außerdem bevorzuge ich die Möglichkeit, die gesamte Nachricht anzupassen. Beachten Sie, dass dies nur Präferenzen sind - ich sage nicht, dass Ihre Stichprobe falsch ist - aber es gibt Fälle, in denen dies sehr nützlich ist.

Helper.Validate(firstName != null || !string.IsNullOrEmpty(directoryID), 
       "The value for firstName cannot be null if a directory ID is not supplied."); 
5

Mit meiner Bibliothek The Helper Trinity:

public void SomeMethod(string firstName, string lastName, int age) 
{ 
    firstName.AssertNotNull("firstName"); 
    lastName.AssertNotNull("lastName"); 

    ... 
} 

unterstützt Sie auch, dass Aufzählung Parameter korrekt sind behauptet, Sammlungen und deren Inhalte sind nicht null, String-Parameter nicht leer etcetera sind. Ausführliche Beispiele finden Sie in der Benutzerdokumentation here.

+0

Schön. Ich werde das überprüfen. –

+0

+1. Ich mag die Verwendung von Erweiterungsmethoden, um die Anzahl der Wörter zu minimieren. Ich würde stattdessen die Methode MustNotBeNull oder etwas aufrufen, aber das ist nur eine persönliche Vorliebe. –

+0

Was passiert in diesem Code, wenn firstName null ist? Würde kein NullRef folgen? Habe nie versucht zu sehen, ob Extension-Methoden immer noch an einen Nullzeiger anhängen können. –

7

Ich habe dieses genaue Problem vor ein paar Wochen angegangen, nachdem ich dachte, dass es seltsam ist, wie das Testen von Bibliotheken eine Million verschiedene Versionen von Assert benötigt, um ihre Nachrichten beschreibend zu machen.

Here's my solution.

Kurze Zusammenfassung - gegeben dieses Stück Code:

int x = 3; 
string t = "hi"; 
Assert(() => 5*x + (2/t.Length) < 99); 

Meine Assert-Funktion aus der folgenden Zusammenfassung drucken können, was ihm übergeben wird:

(((5 * x) + (2/t.Length)) < 99) == True where 
{ 
    ((5 * x) + (2/t.Length)) == 16 where 
    { 
    (5 * x) == 15 where 
    { 
     x == 3 
    } 
    (2/t.Length) == 1 where 
    { 
     t.Length == 2 where 
     { 
     t == "hi" 
     } 
    } 
    } 
} 

So alle Bezeichnernamen und Werte Die Struktur des Ausdrucks kann in die Ausnahmebedingungsnachricht eingefügt werden, ohne dass Sie sie in Anführungszeichen setzen müssen.

+0

Wow, das ist ausgezeichnet. Ich weiß nicht, warum ich nicht daran gedacht habe (gute Ideen provozieren oft dieses Gefühl). Ich schreibe eine Berechnungs-App, für die das sehr schön sein könnte, um die Antworten zu erklären. –

+0

Die Kombination von Reflexions- und Ausdrucksbäumen ermöglicht eine Menge Dinge, die Sie überraschen - besonders wenn Sie (wie ich) aus einem C/C++ - Hintergrund kommen, wo die Laufzeit nicht existiert. Wir müssen einen Haufen Dinge neu erfinden, die Lispers seit einem halben Jahrhundert macht! –

+0

Nette Idee, und auch für eine Calc App zu verwenden! – flq

11

Wow, ich etwas wirklich interessant hier. Chris oben gab einen Link zu einer anderen Stack Overflow Frage. Eine der Antworten darauf, dort zu einer Blog-Post, die beschreibt, wie der Code wie folgt erhalten:

public static void Copy<T>(T[] dst, long dstOffset, T[] src, long srcOffset, long length) 
{ 
    Validate.Begin() 
      .IsNotNull(dst, “dst”) 
      .IsNotNull(src, “src”) 
      .Check() 
      .IsPositive(length) 
      .IsIndexInRange(dst, dstOffset, “dstOffset”) 
      .IsIndexInRange(dst, dstOffset + length, “dstOffset + length”) 
      .IsIndexInRange(src, srcOffset, “srcOffset”) 
      .IsIndexInRange(src, srcOffset + length, “srcOffset + length”) 
      .Check(); 

    for (int di = dstOffset; di < dstOffset + length; ++di) 
     dst[di] = src[di - dstOffset + srcOffset]; 
} 

ich nicht davon überzeugt bin, es ist die beste Antwort noch nicht, aber es ist sicherlich interessant. Here's the blog post, von Rick Brewster.

6

Okay Leute, ich bin es wieder, und ich habe etwas anderes gefunden, das erstaunlich und entzückend ist. Es ist noch ein anderer Blog-Eintrag, auf den von der anderen SO-Frage Bezug genommen wird, den Chris oben erwähnt hat.

Dieser Ansatz Kerl können Sie schreiben:

public class WebServer 
    { 
     public void BootstrapServer(int port, string rootDirectory, string serverName) 
     { 
      Guard.IsNotNull(() => rootDirectory); 
      Guard.IsNotNull(() => serverName); 

      // Bootstrap the server 
     } 
    } 

Beachten Sie, dass keine Zeichenfolge ist „rootDirectory“ und keine Zeichenfolge mit „Servername“ enthalten !! Und dennoch kann seine Fehlermeldung etwas wie "Der rootDirectory-Parameter darf nicht null sein" sagen.

Das ist genau das, was ich wollte und mehr als ich erhofft hatte. Here's the link to the guy's blog post.

Und die Umsetzung ist ziemlich einfach, wie folgt:

public static class Guard 
    { 
     public static void IsNotNull<T>(Expression<Func<T>> expr) 
     { 
      // expression value != default of T 
      if (!expr.Compile()().Equals(default(T))) 
       return; 

      var param = (MemberExpression) expr.Body; 
      throw new ArgumentNullException(param.Member.Name); 
     } 
    } 

Beachten Sie, dass diese Verwendung von „statischen Reflexion“ macht, so in einer engen Schleife oder etwas, Sie könnten Rick Brewster-Ansatz verwenden wollen oben .

Sobald ich dies posten werde ich stimmen Chris, und die Antwort auf die andere SO Frage. Das sind gute Sachen !!!

+1

Hallo, ein bisschen spät, wenn ich das bemerke, aber ich denke, es fehlt die allgemeine Kraft der Ausdrücke ein bisschen.Sehen Sie sich meine Antwort noch einmal an - Sie müssen keine separate Funktion für jede Art von Assertion schreiben, die Sie vornehmen möchten. Schreiben Sie einfach einen Ausdruck in die normale C# -Syntax: 'X.Assert (() => serverName! = Null);' Es gibt keinen guten Grund, eine Menge spezieller Methoden (IsNull, IsNotNull, AreEqual usw.) für jede Art zu sammeln der Behauptung, weil wir eine vollständige Beschreibung eines Ausdrucks und aller darin enthaltenen Werte erhalten können, unabhängig davon, was der Ausdruck gerade ist. –

+0

Earwicker, das ist eine interessante Beobachtung. Ich mag es, obwohl es kein Slam Dunk ist und es könnte Fälle geben, wo ich mit dem Ansatz mit vielen Methoden (IsNull, IsNotNull, etc.) gehen würde. Oder zumindest würde ich mit mir darüber diskutieren. Die Fehlermeldung kann schöner sein, dass "Vorname nicht null sein kann" als "Die Assertion (Vorname! = Null) fehlgeschlagen". Plus, wenn Sie hundert Stellen haben, wo Sie behaupten (etwas! = Null), fühlt sich das an wie Code-Duplizierung. Ich bin mir nicht sicher, ob es sich um eine schädliche Vervielfältigung handelt oder nicht. Hängt wahrscheinlich davon ab. Aber coole Beobachtung, danke. –

0

Es ist nicht überall anwenden, aber es könnte in vielen Fällen helfen:

Ich nehme an, dass „Somemethod“ ist auf den Daten einige Verhaltens Operation durchführenden „Nachname“, „Vorname“ und „Alter“ . Bewerten Sie Ihr aktuelles Code-Design. Wenn die drei Daten nach einer Klasse schreien, lege sie in eine Klasse. In dieser Klasse können Sie auch Ihre Schecks ablegen. Dies würde "SomeMethod" von der Eingabeüberprüfung befreien.

Das Ergebnis Ende so etwas wie dies wäre:

public void SomeMethod(Person person) 
{ 
    person.CheckInvariants(); 
    // code here ... 
} 

Der Anruf etwas so sein würde (wenn Sie .NET 3.5 verwenden):

SomeMethod(new Person { FirstName = "Joe", LastName = "White", Age = 12 }); 

unter der Annahme, dass die Klasse würde sieht so aus:

public class Person 
{ 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public int Age { get; set; } 

    public void CheckInvariants() 
    { 
     assertNotNull(FirstName, "first name"); 
     assertNotNull(LastName, "last name"); 
    } 

    // here are your checks ... 
    private void assertNotNull(string input, string hint) 
    { 
     if (input == null) 
     { 
      string message = string.Format("The given {0} is null.", hint); 
      throw new ApplicationException(message); 
     } 
    } 

Anstelle des syntaktischen Zuckers von .NET 3.5 können Sie auch Konstruktor verwenden Argumente zum Erstellen eines Person-Objekts.

0

Genau wie ein Kontrast, this Beitrag von Miško Hevery auf der Google Testing Blog argumentiert, dass diese Art der Parameterprüfung möglicherweise nicht immer eine gute Sache sein. Die daraus resultierende Debatte in den Kommentaren wirft auch einige interessante Punkte auf.

+0

Es gibt die Wahrheit zu diesem Gesichtspunkt. Ich spreche nicht unbedingt von religiöser Durchsetzung von Verträgen (ich denke, das ist in manchen Fällen gut, aber nicht in allen Fällen). Einige Methoden müssen jedoch bestimmte Prüfungen durchführen, und oft fehlt es Ihnen an Hilfe für diese Fälle. –

+0

Sicherlich sind diese Arten von Kontrollen in vielen Situationen nützlich, ich wollte nur die gegensätzliche Meinung für zukünftige Generationen :) –

2

Die Lokad Shared Libraries haben auch eine IL-Parsing-basierte Implementierung von diesem, die vermeiden, den Parameternamen in einer Zeichenfolge zu duplizieren.

Zum Beispiel:

Enforce.Arguments(() => controller,() => viewManager,() => workspace); 

eine Ausnahme mit dem entsprechenden Parameternamen werfen, wenn eine der aufgeführten Argumente null ist. Es hat auch eine wirklich ordentliche richtlinienbasierte Implementierung.

z.B.

Enforce.Argument(() => username, StringIs.Limited(3, 64), StringIs.ValidEmail); 
+0

Nizza. Danke, dass du mich da gezeigt hast. –

3

Hier ist meine Antwort auf das Problem. Ich nenne es "Guard Claws". Es nutzt den IL-Parser von dem Lokad Geteilt Libs hat aber einen einfacheren Ansatz, um die Ist-Schutzklauseln besagt:

string test = null; 
Claws.NotNull(() => test); 

Sie können weitere Beispiele sehen davon Nutzung ist in den specs.

Da es echte lambdas als Eingabe verwendet und den IL Parser nur zum Generieren der Ausnahme im Falle einer Verletzung verwendet, sollte es auf dem "glücklichen Pfad" besser funktionieren als die Expression-basierten Designs an anderer Stelle in diesen Antworten.

Die Links nicht funktionieren, hier ist die URL: http://github.com/littlebits/guard_claws/

+0

Kühl. Gibt es Pläne, params-Argumente zu verwenden, um mehrere Variablen verifizieren zu lassen? –

Verwandte Themen