我正在尝试构建一个验证某个类型实例的属性。
为了做到这一点,我必须将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可以帮助我吗?
答案 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以获取参考资料