2016-01-15 5 views
10

Ich erstelle eine einfache Guard-API, um zu verhindern, dass illegale Parameter an Funktionen übergeben werden und so weiter.So erstellen Sie eine Fluent Nested Guard-API

Ich habe den folgenden Code:

public static class Guard 
{ 
    public static GuardArgument<T> Ensure<T>(T value, string argumentName) 
    { 
     return new GuardArgument<T>(value, argumentName); 
    } 
} 

public class GuardArgument<T> 
{ 
    public GuardArgument(T value, string argumentName) 
    { 
     Value = value; 
     Name = Name; 
    } 

    public T Value { get; private set; } 
    public string Name { get; private set; } 
} 

// Example extension for validity checks 
public static GuardArgument<T> IsNotNull<T>(this GuardArgument<T> guardArgument, string errorMessage) 
{ 
    if (guardArgument.Value == null) 
    { 
     throw new ArgumentNullException(guardArgument.Name, errorMessage); 
    } 

    return guardArgument; 
}  

Im Moment kann der Code in ähnlicher Weise verwendet wird (beachten Sie, das nur ein dummes Beispiel ist):

void DummyMethod(int? someObject) { 

    Guard.Ensure(someObject, "someObject") 
     .IsNotNull() 
     .IsGreaterThan(0) 
     .IsLessThan(10); 
} 

Dies alles funktioniert fein. Was ich möchte jetzt tun können ist die API erweitern, um Kind Eigenschaften bei den Kontrollen in der folgenden Art und Weise beinhaltet:

Guard.Ensure(someObject, "someObject") 
    .IsNotNull() 
    .Property(
     (x => x.ChildProp1, "childProp1") 
      .IsNotNull() 
      .IsGreaterThan(10) 
    ) 
    .Property(
     (x => x.ChildProp2, "childProp2") 
      .IsNotNull() 
      .IsLessThan(10) 
    ); 

Offensichtlich ist die neue .Property Methode, um die Mutter GuardArgument um Kette zurückkehren muss. Außerdem muss die untergeordnete Eigenschaft in der Lage sein, die vorhandenen Prüfmethoden (IsNotNull() usw.) zu verwenden, um eine Code-Duplizierung zu vermeiden.

Ich kann nicht herausfinden, wie die Lambda/Eigenschaft Funktion Parameter zu bauen oder wo die .Property Methode sollte gefunden werden - dh sollte es eine Eigenschaft auf der GuardArgument oder woanders sein, oder auch wenn es eine bessere Struktur der API ist .

+1

Könnte ein Tippfehler sein, aber der GuardArgument Konstruktor setzt 'Name = Name' –

+1

Sie sagen:„Offensichtlich ist die neue '.Property' Methode muss das übergeordnete' GuardArgument', um zurückzukehren ketten. " - Nein. Das ist nicht korrekt. Wenn Sie versuchen, eine fließende API zu erstellen, sollten Sie die Eltern nicht übergeben - Sie sollten ein neues 'GuardArgument' konstruieren, das die Eltern verschachtelt, ansonsten können Sie schreckliche Fehler erzeugen, indem Sie Referenzen entlang der Kette beibehalten und dann versuchen, neue Ketten zu erstellen von ihnen. – Enigmativity

Antwort

7

Die folgende Funktion ermöglicht eine ähnliche Syntax wie gewünscht.

public static GuardArgument<T> Property<T, TProp>(this GuardArgument<T> guardArgument, Func<T, TProp> getProperty, string propertyName, Action<GuardArgument<TProp>> validate) 
{ 
    GuardArgument<TProp> propertyGuardArgument = new GuardArgument<TProp>(getProperty(guardArgument.Value), propertyName); 

    validate(propertyGuardArgument); 

    return guardArgument; 
} 

Die Funktion erstellt ein neues GuardArgument für das ausgewählte Objekt und übergibt dann diese in den Action Parameter, den Sie erlauben zu überprüfen, wie Sie möchten.

Dies ermöglicht auch unendliche Verkettung von Eigenschaften, obwohl ich nicht sicher bin, dass das besonders gut lesbar wäre.

Verbrauch:

Guard.Ensure(someObject, "someObject") 
    .IsNotNull() 
    .Property(x => x.ChildProp1, "childProp1", childProp1 => 
     childProp1.IsNotNull() 
        .IsLessThan(10) 
        .Property(y => y.InnerChildProperty, "innerChildProperty", innerChildProperty => 
         innerChildProperty.IsNotNull() 
        ) 
    ) 
    .Property(x => x.ChildProp2, "childProp2", childProp2 => 
     childProp2.IsNotNull() 
        .IsGreaterThan(10) 
    ); 
+0

Sieht aus wie JavaScript "Pyramide des Verderbens";) – jlvaquero

+0

Wie auch immer, denken Sie daran, dass Sie sie zuerst Func und Action außerhalb des fließenden erklären und erstellen und dann an Property() übergeben können. – jlvaquero

0

Ich denke, Sie haben keinen Vorteil davon, die Eigenschaftsprüfungen in die Kette der übergeordneten Objektprüfungen zu setzen. Daher würde ich empfehlen, eine Kette für das Elternobjekt und für jede Eigenschaft eine andere Kette zu bilden. Dies ist viel besser lesbar:

Guard.Ensure(a, "a") 
    .IsNotNull("a is null"); 
    Guard.Ensure(a.p0, "a.p0") 
    .IsGreaterThan(10); 
    Guard.Ensure(a.p1, "a.p1") 
    .IsGreaterThan(5); 
0

Ich denke, Sie erfinden hier ein Rad neu. Installieren Sie diese Erweiterung - Code Contracts und hier ist docs wie Sie es verwenden.

Neben Code basiert behauptet ähnlich wie bei Ihnen, das heißt:

public int[] Bar(){ 
    Contract.Ensures(Contract.ForAll(0, Contract.Result<int[]>().Length, index => Contract.Result<int[]>()[index] > 0)); 

.... 
} 

oder

Contract.Requires<ArgumentNullException>(x.Value.NestedObject != null, ”x.Value.NestedObject”); 

Aber auch Attribute und umfangreiche Funktionen für die Überprüfung Schnittstellen, schöne Pre- und Post Bedingungen usw. schau es dir an!

+2

Ich habe Code Contracts für ein Projekt verwendet und fand sie sehr mühsam. Sie reduzieren die Build-Geschwindigkeit auf einen Crawl. Nach ein paar Monaten nahmen wir sie heraus und gingen stattdessen für etwas wie die Frage. – Sean

+2

Vielen Dank @Sean, wir haben vor kurzem begonnen, sie zu verwenden, und haben nicht viel, also habe nicht bemerkt, Build-Zeit noch viel schlimmer zu sein. – vittore

+0

Ich habe mir Code Contracts schon angeschaut und wie Sean, Build Geschwindigkeit war langsam und wurde nur wirklich für die Bewachung Methode Eingang verwendet. Daher die Notwendigkeit für etwas viel leichteres Gewicht. – Graham