2016-04-13 16 views
9

Sto provando a creare un attributo che convalida una determinata istanza di un tipo.Riferimento circolare e costruttori

Per fare questo devo trasmettere il ObjectInstance a quel tipo.

E ho bisogno di impostare l'attributo sul membro di quel tipo.

Quindi dobbiamo ricorrere alla parola chiave and per la definizione circolare.

Tuttavia nel seguente caso ottengo l'errore che

un attributo personalizzato deve invocare un costruttore di oggetto

Sulla linea tracciata qui sotto.

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 

Ho provato a invocare manualmente il costruttore, come ad esempio:

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

Ma nessuno di loro è accettato dal sistema.

Un guru del F # può aiutarmi qui?

risposta

3

Una soluzione potrebbe essere quella di descrivere prima i tipi in un file di firma.

Dal momento che l'attributo è specificato nel file di firma, non è necessario aggiungere nuovamente nel file di implementazione:

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 

Vedi https://msdn.microsoft.com/en-us/library/dd233196.aspx per riferimento

7

Interessante. Sembra che l'inferenza di tipo non abbia davvero ragione. La sintassi corretta da utilizzare qui è [<MyAttribute()>], ma nonostante tu abbia utilizzato la parola chiave and, la classe MyAttribute non è ancora nota.

Ecco una soluzione: in primo luogo verificare che l'oggetto di convalidare è davvero del tipo giusto, quindi utilizzare la reflection per invocare un metodo di validazione:

[<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 

Edit: per far sì che un po 'più pulita, è potrebbe aggiungere un'interfaccia, che si utilizza nell'attributo di convalida e successivamente implementare nella classe.

type IIsValid = 
    abstract member IsValid: unit -> bool 

Il tuo metodo IsValid diventa allora

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") 

nella tua classe, questo appare come:

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

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

Questa è una possibile soluzione, ma è abbastanza (eufemismo) sporca. Grazie! – Snake

+0

Non sostenere che questa sia la soluzione più pulita :-) Lascia che ne aggiunga un'altra. –

+0

Mi piace di più la tua nuova soluzione. Lascio aperto ancora un po ', forse qualcuno arriva con una soluzione reale. In caso contrario, i punti sono tuoi. Ho anche archiviato un bug sul progetto FSharp su GitHub: https://github.com/fsharp/fsharp/issues/565 – Snake

3

Una cosa che si può fare per sbarazzarsi di mutua ricorsione è quello di rompere MyClass definizione in due e utilizzare il tipo di conversione per aggiungere i membri che si desidera contrassegnare con l'attributo.

type MyClass(someValue: int) = 
    member internal this.InternalSomeValue = someValue 

type MyAttribute() = 
    inherit ValidationAttribute() 
    (* you can refer to MyClass here *) 

type MyClass with 
    [<MyAttribute()>] 
    member this.SomeValue = this.InternalSomeValue 

Questo è più vicino a quello che stai chiedendo, ma mi piace l'idea dell'interfaccia migliore.

Problemi correlati