2012-07-27 9 views
21

Para el programa de ejemplo:tipo no tiene null como un valor adecuado

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 

me sale el error "El tipo 'MiClase' no tiene nulo como un valor apropiado" cuando vaya a utilizar esta clase como valor de retorno de las funciones implícitamente tipadas y compárelo con null (b/c de requisitos de compatibilidad con la inyección de dependencia C# No puedo confiar en los tipos de opción F #).

puedo solucionar este cambiando el cheque nulo:

if instance :> obj = null then 

Sin embargo, sé ("sentir") esto es completamente "equivocado". Especialmente cuando considero cómo MyClass es un tipo de referencia que no debería estar encasillado (hablando desde un fondo de C#).

He leído sobre "F # Value Restriction" y cómo afecta la inferencia de tipo, pero parece que no puedo ver cómo se aplica a este escenario.

P: ¿Hay alguna otra manera de hacerlo?

Aparte # 1: He encontrado un método más sencillo de conseguir el error ...

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

Aparte # 2: Yo probé System.Nullable sin pensar ... es un MiClase tipo de referencia y no un tipo de valor (struct) que requiere Nullable < _>. Entonces, solo me asegura que REALMENTE estoy tratando con un tipo de referencia y me hace preguntarme por qué un objeto lanzado de repente hace que esto funcione.

Actualización: Para cualquier persona interesada, utilicé esto como una solución para el Localizador de servicios comunes con las tres funciones siguientes. Cada servicio solicitado debe ser compatible con nula, por lo que si la clase de servicio se define en Fa #, es necesario agregar la [<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 

Respuesta

39

utilizar el atributo [<AllowNullLiteral>]:

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

Por defecto, F # tipos no permita nulo (¡gracias a Dios!). Este atributo es útil para la interoperabilidad con otros lenguajes .NET y permite la asignación/comparación con null.

+0

Doh! Después de ver esto, lo reconocí de la tercera oración en MSDN "Null Values ​​(F #)": http://msdn.microsoft.com/en-us/library/dd233197(v=vs.110).aspx –

+2

Solo para add - es más idiomático usar 'Option 't' si no hace interop –

+0

John - Sí, he mencionado que no puedo usar Option. Por lo tanto, casi todas las clases expuestas a C# necesitarían '[]' trabajar como se esperaría un desarrollador de C# ... :( –

14

El problema con el atributo AllowNullLiteral es que, además de lo que le permite comparar sus objetos de valor nulo, sino que también hace que sea posible establecer sus objetos a nula.

Suponiendo que esto no es deseable para su uso-caso, no es una alternativa fácil con impacto en el rendimiento no observable:

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

A continuación, en lugar de hacer if instance = null then, hacer if isNull instance then lugar.

Esto funcionará para cualquier tipo de referencia (incluidos registros y DU), pero no introduce la posibilidad de establecer objetos de sus tipos F # nulos desde F # - lo mejor de ambos mundos.

+3

Utilicé este enfoque en un proyecto de gran tamaño una vez, pero terminé arrepintiéndolo. Si un tipo puede ser nulo (ya sea en F # o debido a interoperabilidad) es mejor que sea explícito. Un ejemplo es el patrón ¿Qué es mejor, definir un patrón activo de null-checking o 'match value with null -> ...'? Al final, tales soluciones se sentían hacky. Para la interoperabilidad, simplemente agrega '[]' a tus tipos y abrazo nulo. – Daniel

+0

@Daniel: obtengo el im pression que el OP simplemente quiere usar tipos F # de C#, así que no sé cuánta interoperación real está ocurriendo ... – ildjarn

+0

La definición anterior de null produjo el error de sintaxis: '' Los parámetros de tipo explícito solo se pueden usar en el módulo o miembros vinculados'' He intentado esto en su lugar: '' let isNull (x: obj) = obj.ReferenceEquals (x, Unchecked.defaultof <_>) '' Por supuesto, aceptará cualquier valor ya que el enlace de parámetros automáticamente up-cast a object (con el consiguiente esfuerzo de cuadro). Por lo tanto, se pierde la advertencia del compilador para el valor que no es de referencia. – George

Cuestiones relacionadas