【问题标题】:Circular reference and constructors循环引用和构造函数
【发布时间】:2016-08-04 13:07:39
【问题描述】:

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

为此,我必须将 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# 大师能帮我吗?

【问题讨论】:

    标签: f# circular-dependency circular-reference


    【解决方案1】:

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

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

    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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

        【解决方案3】:

        有趣的一个。似乎类型推断真的没有做到这一点。此处使用的正确语法是 [&lt;MyAttribute()&gt;],但尽管您使用了 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
        

        【讨论】:

        • 这是一个可能的解决方案,但它非常(轻描淡写)肮脏。谢谢!
        • 不要声称这是最干净的解决方案 :-) 让我再添加一个。
        • 我更喜欢你的新解决方法。我让它打开一段时间,也许有人会提供一个真正的解决方案。如果没有,积分是你的。我还在 GitHub 上提交了 FSharp 项目的 bug:github.com/fsharp/fsharp/issues/565
        • 我喜欢这个想法 - 你将只有一个属性类型 + 一个通常有用的接口来实现,而不是创建大量的一次性属性类型。 ComponentModel 一团糟。
        • Don Syme 刚刚确认这实际上是一个编译器错误,请参阅@Snake 在GitHub 上的报告
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-08-22
        • 2012-11-04
        • 1970-01-01
        • 2015-08-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多