2016-04-13 2 views
9

Я пытаюсь создать Атрибут, который проверяет определенный экземпляр типа.Циркулярная ссылка и конструкторы

Для этого мне необходимо нанести ObjectInstance на этот тип.

И мне нужно установить атрибут на члене этого типа.

Поэтому нам необходимо прибегнуть к ключевому слову and для кругового определения.

Однако в следующем случае я получаю ошибку, что

Пользовательский атрибут должен вызывать конструктор объекта

На линии, отмеченной ниже.

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 

Я попытался вручную вызова конструктора, например:

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

Но никто из них не принимаются системой.

Может ли гуру F # помочь мне здесь?

ответ

3

Одним из решений было бы сначала описать ваши типы в файлах подписи.

Поскольку атрибут задается в файле подписи, не нужно снова добавить его в файле реализации:

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 

См https://msdn.microsoft.com/en-us/library/dd233196.aspx для справки

7

Интересный. Кажется, что вывод типа действительно не так прав. Правильный синтаксис для использования здесь - [<MyAttribute()>], но, несмотря на то, что вы используете ключевое слово and, класс MyAttribute еще не известен.

Вот обходной путь: Сначала проверьте, что объект для проверки действительно правильного типа, а затем использовать отражение для вызова метода проверки:

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

Редактировать: чтобы сделать это немного чище, вы может добавить интерфейс, который вы используете в своем атрибуте проверки, а затем реализовать в своем классе.

type IIsValid = 
    abstract member IsValid: unit -> bool 

Ваш IsValid метод становится

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

в своем классе, это выглядит следующим образом:

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

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

Это является возможным решением, но это совсем (мягко сказано) загрязнен. Спасибо! – Snake

+0

Не утверждая, что это самое чистое решение :-) Позвольте мне добавить еще один. –

+0

Мне нравится ваш новый обходной путь лучше. Я оставляю его открытым немного дольше, может быть, кто-то приходит с реальным решением. Если нет, то ваши очки. Я также подал ошибку в проекте FSharp на GitHub: https://github.com/fsharp/fsharp/issues/565 – Snake

3

Одна вещь, которую вы можете сделать, чтобы избавиться от взаимной рекурсии, чтобы разбить MyClass, чтобы добавить элементы, которые вы хотите пометить атрибутом.

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 

Это ближе к тому, о чем вы просите, но мне нравится идея интерфейса лучше.

Смежные вопросы