循环引用和构造函数

时间:2016-04-13 12:27:19

标签: f# circular-dependency circular-reference

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

为了做到这一点,我必须将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#guru可以帮助我吗?

3 个答案:

答案 0 :(得分: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

答案 1 :(得分: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

这更接近你所要求的,但我更喜欢界面的想法。

答案 2 :(得分: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以获取参考资料