2012-05-24 12 views
12

Teniendo en cuenta lo siguiente:Pruebas de referencia nula en Fa #

[<DataContract>] 
type TweetUser = { 
    [<field:DataMember(Name="followers_count")>] Followers:int 
    [<field:DataMember(Name="screen_name")>] Name:string 
    [<field:DataMember(Name="id_str")>] Id:int 
    [<field:DataMember(Name="location")>] Location:string} 

[<DataContract>] 
type Tweet = { 
    [<field:DataMember(Name="id_str")>] Id:string 
    [<field:DataMember(Name="text")>] Text:string 
    [<field:DataMember(Name="retweeted")>] IsRetweeted:bool 
    [<field:DataMember(Name="created_at")>] DateStr:string 
    [<field:DataMember(Name="user", IsRequired=false)>] User:TweetUser 
    [<field:DataMember(Name="sender", IsRequired=false)>] Sender:TweetUser 
    [<field:DataMember(Name="source")>] Source:string} 

deserializar con DataContractJsonSerializer(typeof<Tweet[]>) resultará en el Usuario o el campo Remitente siendo nulo (al menos eso es lo que el depurador me está diciendo).

Si trato de escribir lo siguiente:

let name = if tweet.User <> null 
        then tweet.User.Name 
        else tweet.Sender.Name 

el compilador emite el error: "El tipo 'TweetUser' no tiene 'nulo' como un valor apropiado"

¿Cómo se prueba valores nulos en este caso?

+1

¿Tiene 'si tweet.User <> Unchecked.defaultof <_>' funciona? Si no, siempre está el atributo ['AllowNullLiteral'] (http://msdn.microsoft.com/en-us/library/ee353608.aspx). – ildjarn

+0

Unchecked.defaultof <_> compila, pero no funciona en el tiempo de ejecución (no coincide correctamente nulo). AllowNullLiteral no es válido para un campo de registro. Buenas sugerencias sin embargo. –

Respuesta

17

Para ampliar de forma cíclica en respuesta @Tomas'; -]

let name = if not <| obj.ReferenceEquals (tweet.User, null) 
       then tweet.User.Name 
       else tweet.Sender.Name 

o

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

Unchecked.defaultof<_> está haciendo lo correcto y la producción de los nulos para los tipos de registros; el problema es que el operador de igualdad predeterminado utiliza la comparación estructural genérica, que espera que siempre juegue según las reglas de F # cuando use tipos de F #. En cualquier caso, un cheque nulo realmente solo garantiza una comparación referencial en primer lugar.

+0

Tiene sentido una vez que entiendes el concepto. ¡Gracias! –

+0

¿Hay una ventaja de usar 'Unchecked.defaultof <_>' aquí en lugar de simplemente 'null'? Parece que este último evitaría una llamada de función, además de permitirle devolver el resultado correcto para las estructuras. –

+0

@DaxFohl: Las instancias de los tipos F # no se pueden instanciar como 'null' de forma predeterminada (consulte [' AllowNullLiteralAttribute'] (http://msdn.microsoft.com/en-us/library/ee353608.aspx)). Las restricciones utilizadas aquí prohíben específicamente los tipos de valor, ya que no tienen sentido aquí semánticamente. Por último, 'Unchecked.defaultof' es un compilador intrínseco (como' default' en C#) por lo que no hay ninguna llamada de función allí. – ildjarn

13

Para agregar algunos detalles al comentario de @ildjarn, aparece el mensaje de error, porque F # no permite usar null como un valor de los tipos que se declaran en F #. La motivación para esto es que F # intenta eliminar los valores null (y NullReferenceException) de los programas F # puros.

Sin embargo, si usted está utilizando tipos que no están definidos en F #, usted todavía está permitido el uso de null (por ejemplo, cuando se llama a una función que toma como argumento System.Random, puede darle null). Esto es necesario para la interoperabilidad, ya que es posible que necesite pasar null a una biblioteca de .NET o aceptarlo como resultado.

En su ejemplo, es una TweetUser (registro) tipo declarado en Fa #, por lo que el lenguaje no permite el tratamiento de null como un valor de tipo TweetUser. Sin embargo, aún puede obtener el valor null a través de Reflection o del código C#, por lo que F # proporciona una función "insegura" que crea un valor null de cualquier tipo, incluidos los registros F #, que normalmente no deberían tener el valor null. Esta es la función Unchecked.defaultOf<_> y se puede utilizar para implementar un ayudante de la siguiente manera:

let inline isNull x = x = Unchecked.defaultof<_> 

Alternativamente, si se marca un tipo con el atributo AllowNullLiteral, entonces usted está diciendo a la F # compilador que debe permitir null como un valor para ese tipo específico, incluso si es un tipo declarado en F # (y normalmente no permitiría null).

+0

AllowNullLiteral no permitido en los campos de registro. Programa las fallas al acceder a tweet.User cuando se compara con Unchecked.defaultof <>. En otras palabras, solo haciendo referencia a tweet.User para hacer la comparación cuando es nulo es suficiente para causar un error. Extraño. –

+1

@MikeWard El atributo debe aplicarse al tipo - en su caso 'TweetUser' - luego especifica que cualquier aparición del tipo (no solo en el campo, sino también en la expresión' if') puede tener 'null' valor. –

+1

Lo intenté también. AllowNullLiteral no se puede aplicar a los tipos de registro. La cosa reference.equals funciona como se esperaba. Solo una de esas rarezas de interoperabilidad de F # con las que tendremos que vivir. Realmente me gusta el idioma general. –

1

Aunque esta pregunta es antigua, no vi ningún ejemplo de boxeo para resolver el problema. En los casos en que mi presentador no permite literales nulos, pero se puede establecer desde una vista, prefiero usar el boxeo.

isNull <| box obj 

o

let isMyObjNull = isNull <| box obj 

o

match box obj with 
| isNull -> (* obj is null *) 
| _ -> (* obj is not null *) 
Cuestiones relacionadas