循环引用和构造函数

Circular reference and constructors

我正在尝试构建一个属性来验证某个类型的特定实例。

为此,我必须将 ObjectInstance 转换为该类型。

并且我需要在该类型的成员上设置属性。

所以我们需要借助and关键字来进行循环定义。

但是在下面的情况下,我得到的错误是

A custom attribute must invoke an object constructor

在下面标记的行上。

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()>]

但是系统接受了none个

F# 大师可以帮我解决这个问题吗?

有趣的一个。似乎类型推断真的没有做到这一点。此处使用的正确语法是 [<MyAttribute()>],但尽管您使用了 and 关键字,但 MyAttribute class 尚不清楚。

这里有一个解决方法:首先检查要验证的对象是否确实是正确的类型,然后使用反射调用验证方法:

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

编辑:为了使它稍微干净一些,您可以添加一个接口,您在验证属性中使用该接口,然后在您的 class 中实现。

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

在您的 class 中,它看起来像:

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

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

要摆脱相互递归,您可以做的一件事是将 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

这更接近您的要求,但我更喜欢界面创意。

一个解决方案是首先在签名文件中描述您的类型。

由于签名文件中已经指定了该属性,所以在实现文件中不需要再添加:

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