2016-04-13 15 views
9

Ich versuche, ein Attribut zu erstellen, die eine bestimmte Instanz eines Typs überprüft.Circular Referenz und Konstruktoren

Um dies zu tun, muss ich die ObjectInstance auf diesen Typ zu werfen.

Und ich muss das Attribut für das Mitglied dieses Typs festlegen.

Also müssen wir auf das Schlüsselwort and für die zirkuläre Definition zurückgreifen.

jedoch im folgenden Fall erhalte ich die Fehlermeldung, dass

Ein benutzerdefiniertes Attribut ein Objekt Konstruktor

Auf der Linie unten markiert aufrufen müssen.

namespace Test 

open System 
open System.ComponentModel.DataAnnotations 

[<AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)>] 
type MyAttribute() = 
    class 
    inherit ValidationAttribute() 

    override this.IsValid (value: Object, validationContext: ValidationContext) = 
     match validationContext.ObjectInstance with 
     | :? MyClass as item -> 
      // TODO more validation 
      ValidationResult.Success 
     | _ -> 
      new ValidationResult("No no no") 
    end 
and MyClass(someValue) = 
    [<Required>] 
    [<Range(1, 7)>] 
    //vvvvvvvvvvvvvvv 
    [<MyAttribute>] 
    //^^^^^^^^^^^^^^^ 
    member this.SomeValue : int = someValue 

Ich habe versucht Aufruf manuell den Konstruktor, wie zum Beispiel:

[<MyAttribute()>] 
// or 
[<new MyAttribute()>] 

Aber keiner von ihnen werden vom System akzeptiert.

Kann ein F # Guru mir hier helfen?

Antwort

3

Eine Lösung wäre, zuerst Ihre Typen in einer Signaturdatei zu beschreiben.

Da das Attribut in der Signaturdatei angegeben ist, brauchen Sie nicht wieder in der Implementierungsdatei hinzuzufügen:

Foo.fsi:

namespace Foo 

open System 

[<AttributeUsage(AttributeTargets.Property)>] 
type MyAttribute = 
    inherit System.Attribute 

    new : unit -> MyAttribute 

    member Foo : unit -> MyClass 

and MyClass = 
    new : someValue : int -> MyClass 

    [<MyAttribute()>] 
    member SomeValue : int 

Foo.fs:

namespace Foo 

open System 

[<AttributeUsage(AttributeTargets.Property)>] 
type MyAttribute() = 
    inherit Attribute() 

    member this.Foo() = 
     new MyClass(1) 

and MyClass(someValue) = 
    // [<MyAttribute()>] -> specified in the fsi, still appears in compiled code 
    member this.SomeValue : int = someValue 

Siehe https://msdn.microsoft.com/en-us/library/dd233196.aspx Referenz

7

Interessant. Es scheint, dass die Typinferenz wirklich nicht richtig ist. Die richtige Syntax, um hier zu verwenden ist [<MyAttribute()>], aber trotz der Verwendung des Schlüsselwortes and ist die Klasse MyAttribute noch nicht bekannt. Hier

ist eine Abhilfe: Überprüfen Sie zunächst, dass das Objekt zu validieren wirklich der richtige Typ ist, dann Reflektion verwenden, um eine Validierungsmethode aufzurufen:

[<AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)>] 
type MyAttribute() = 
    inherit ValidationAttribute() 

    override this.IsValid (value: Object, validationContext: ValidationContext) = 
     let t = validationContext.ObjectInstance.GetType() 
     if t.FullName = "Test.MyClass" then 
      let p = t.GetMethod("IsValid") 
      if p.Invoke(validationContext.ObjectInstance, [| |]) |> unbox<bool> then 
       ValidationResult.Success 
      else 
       ValidationResult("failed") 
     else 
      new ValidationResult("No no no") 

type MyClass(someValue: int) = 
    [<Required>] 
    [<Range(1, 7)>] 
    [<MyAttribute()>] 
    member this.SomeValue = someValue 

    member this.IsValid() = someValue <= 7 

bearbeiten: dass etwas sauberer zu machen, Sie könnte eine Schnittstelle hinzufügen, die Sie in Ihrem Validierungsattribut verwenden und später in Ihrer Klasse implementieren.

type IIsValid = 
    abstract member IsValid: unit -> bool 

Ihre IsValid Methode wird dann

override this.IsValid (value: Object, validationContext: ValidationContext) = 

     match validationContext.ObjectInstance with 
     | :? IIsValid as i -> 
      if i.IsValid() then 
       ValidationResult.Success 
      else 
       ValidationResult("failed") 
     | _ -> 
      ValidationResult("No no no") 

in Ihrer Klasse, das wie folgt aussieht:

type MyClass(someValue: int) = 
    [<Required>] 
    [<Range(1, 7)>] 
    [<MyAttribute()>] 
    member this.SomeValue = someValue 

    interface IIsValid with 
     member this.IsValid() = someValue <= 7 
+0

, dass eine mögliche Lösung ist, aber es ist ganz (Untertreibung) verschmutzt. Vielen Dank! – Snake

+0

Nicht behaupten, das ist die sauberste Lösung :-) Lassen Sie mich ein weiteres hinzufügen. –

+0

Ich mag Ihre neue Workaround besser. Ich lasse es etwas länger offen, vielleicht kommt jemand mit einer echten Lösung. Wenn nicht, sind die Punkte deine. Ich habe auch einen Fehler auf dem FSharp-Projekt auf GitHub: https://github.com/fsharp/fsharp/issues/565 – Snake

3

Eine Sache, die Sie tun können, die gegenseitigen Rekursion loszuwerden, ist bis zu brechen MyClass Definition in zwei und verwenden Sie Typ Augmentation, um die Mitglieder hinzuzufügen, die Sie mit dem Attribut markieren möchten.

Das ist näher an dem, was Sie verlangen, aber ich mag die Schnittstelle Idee besser.