类型不具备 null 作为适当的值。

34

对于这个示例程序:

type public MyClass(reasonForLiving:string) =
    member x.ReasonForLiving with get() = reasonForLiving

let classFactory () = MyClass("up to you")
let live () =
    let instance = classFactory()
    if instance = null then raise(System.Exception("null is not living... that's why OO languages die from bugs"))
    instance

当我将这个类作为隐式类型函数的返回值并将其与null进行比较时,出现错误"The type 'MyClass' does not have null as a proper value"("MyClass"类型没有"null"作为合适的值)。由于需要与C#依赖注入兼容,我不能依赖F#选项类型。我可以通过将null检查改为以下内容轻松修复此问题:

if instance :> obj = null then

然而,我知道(“感觉”)这是完全“错误”的。特别是当考虑到MyClass是一个引用类型,不应该需要被装箱(从C#的背景中说)。

我读过有关“F#值约束”的文章以及它如何影响类型推断,但我似乎无法理解它如何适用于这种情况。

问:还有其他方法可以做到这一点吗?

附注1:我找到了一个更简单的获取错误的方法...

type public MyClass(reasonForLiving:string) =
    member x.ReasonForLiving with get() = reasonForLiving
let nullMyClass : MyClass = null
< p > < em > 另外 #2: 我尝试过使用System.Nullable,但没有考虑到... MyClass是一个引用类型而不是值类型(结构体),这正是Nullable<_>所需要的。所以,这只是向我确认了我确实正在处理一个引用类型,并让我想知道为什么对象转换突然使其工作。

更新: 对于任何有兴趣的人,我使用以下三个函数之一作为Common Service Locator的解决方案之一。每个请求的服务都必须支持null,因此,如果服务类在F#中定义,则需要添加[<AllowNullLiteral>]

let private getServiceLocator () =
    try Some(Microsoft.Practices.ServiceLocation.ServiceLocator.Current)
    with | _ -> None

let private getService serviceFactory =
    let serviceLocator = getServiceLocator()
    let service = match serviceLocator with 
                  | None -> serviceFactory()
                  | _ -> 
                    match serviceLocator.Value.GetInstance<'a>() with
                    | null -> serviceFactory()
                    | svc -> svc
    match service with
    | null -> None
    | _ -> Some(service)

let private getRequiredService serviceFactory =
    let service = getService serviceFactory
    match service with
    | None -> raise(MissingServiceException(""))
    | _ -> service.Value
2个回答

52

使用 [<AllowNullLiteral>] 属性:

[<AllowNullLiteral>]
type public MyClass(reasonForLiving:string) =
    member x.ReasonForLiving with get() = reasonForLiving

默认情况下,F# 类型不允许 null(感谢上天!)。这个属性对于与其他 .NET 语言的互操作非常有用,并允许将 null 赋值/比较。


糟糕!看到这个后,我发现它来自MSDN上关于“Null Values(F#)”的第三句话:http://msdn.microsoft.com/en-us/library/dd233197(v=vs.110).aspx - Eric Swanson
3
补充一下 - 如果你不进行互操作,使用Option 't更符合习惯用法。 - John Palmer
约翰 - 是的,我提到不能使用Option。 所以,几乎每个暴露给C#的类都需要[<AllowNullLiteral>]才能按照C#开发人员的期望工作... :( - Eric Swanson
3
无论是否存在AllowNullLiteral,从C#中可以将类、记录和DUs设置为null值——毕竟,在底层它们仍然是引用类型。该特性只允许从F#中将某些东西设置为null。 - ildjarn

23
< p > AllowNullLiteral 属性的问题在于除了允许您将对象与 null 进行比较外,还可以使您将对象设置为 null。

假设这对于您的用例不是理想的情况,那么有一个简单的替代方案,其性能影响不可观察:

let inline isNull (x:^T when ^T : not struct) = obj.ReferenceEquals (x, null)

如果要判断一个实例是否为空,不要使用if instance = null then,而应该使用if isNull instance then

这适用于任何引用类型(包括记录和DUs),但不会从F#中引入将F#类型的对象设置为空的可能性 - 两全其美。


3
我曾经在一个比较大的项目上采用过这种方法,但最终后悔了。如果类型可以为空(无论是在F#中还是由于互操作性),最好明确指定。例如模式匹配。哪种更好,定义一个检查null的活动模式还是match value with null -> ...?最终,这些解决办法感觉很不自然。对于互操作性,只需向您的类型添加 [<AllowNullLiteral>] 并接受空值。 - Daniel
@Daniel: 我的印象是OP只是想从C#中使用F#类型,所以我不知道到底有多少真正的交互操作。 - ildjarn
以上的isNull定义产生了语法错误:“显式类型参数只能用于模块或成员绑定”我尝试使用这个代替:let isNull (x:obj) = obj.ReferenceEquals (x, Unchecked.defaultof<_>)当然,它将接受任何值,因为参数绑定会自动向上转换为对象(伴随着相应的装箱操作)。所以编译器对非引用值的警告被忽略了。 - George
@George:鉴于这是一个通用的实用函数,我假设它将在模块范围内定义,在这种情况下就不会出现语法错误。我已经更新了答案,使用可以在模块范围或函数范围内工作的语法,感谢您让我知道这个问题。:-] - ildjarn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接