Práctica recomendada para usar tipos de referencia anulables para DTO

20

Tengo un DTO que se completa leyendo desde una tabla DynamoDB. Digamos que se ve así actualmente:

public class Item
{
    public string Id { get; set; } // PK so technically cannot be null
    public string Name { get; set; } // validation to prevent nulls but this doesn't stop database hacks
    public string Description { get; set; } // can be null
}

¿Existe alguna práctica recomendada para hacer frente a esto? Prefiero evitar un constructor sin parámetros ya que eso juega mal con el ORM en el SDK de Dynamo (así como otros).

Me parece extraño escribir public string Id { get; set; } = "";porque esto nunca sucederá ya que Ides un PK y nunca puede ser nulo. ¿Qué uso sería ""incluso si de alguna manera lo hiciera?

Entonces, ¿alguna mejor práctica en esto?

  • ¿Debo marcarlos a todos string?para decir que pueden ser nulos, aunque algunos nunca deberían serlo?
  • Debería inicializar Idy Namecon ""porque nunca deberían ser nulos y esto muestra la intención a pesar de ""que nunca se usaría.
  • Alguna combinación de arriba

Tenga en cuenta: se trata de los tipos de referencia anulables C # 8. Si no sabe cuáles son los mejores, no responda.

Desarrollador Británico
fuente
Está un poco sucio, pero puedes simplemente abofetear #pragma warning disable CS8618en la parte superior del archivo.
Veinte
77
En lugar de = ""eso, puede usar = null!para inicializar una propiedad que sabe que nunca será efectiva null(cuando el compilador no tiene forma de saberlo). Si Descriptionpuede ser legalmente null, debe declararse a string?. Alternativamente, si la comprobación de nulabilidad para el DTO es más molesta que ayuda, simplemente puede ajustar el tipo #nullable disable/ #nullable restoredesactivar los NRT solo para este tipo.
Jeroen Mostert
@JeroenMostert Deberías poner eso como respuesta.
Magnus
3
@Magnus: soy reacio a responder cualquier pregunta que pida "mejores prácticas"; tales cosas son amplias y subjetivas. Espero que el OP pueda usar mi comentario para desarrollar su propia "mejor práctica".
Jeroen Mostert
1
@ IvanGarcíaTopete: Si bien estoy de acuerdo en que usar una cadena para una clave primaria es inusual, e incluso puede ser desaconsejable según las circunstancias, la elección del tipo de datos por parte del OP es bastante irrelevante para la pregunta. Esto podría aplicarse fácilmente a una propiedad de cadena requerida, no anulable, que no es la clave primaria, o incluso a un campo de cadena que es parte de una clave primaria compuesta, y la pregunta aún se mantendría.
Jeremy Caney

Respuestas:

12

Como opción, puede usar el defaultliteral en combinación con elnull forgiving operator

public class Item
{
    public string Id { get; set; } = default!;
    public string Name { get; set; } = default!;
    public string Description { get; set; } = default!;
}

Dado que su DTO se rellena desde DynamoDB, puede usar MaybeNull/NotNull atributos posteriores a la condición para controlar la nulabilidad

  • MaybeNull Un valor de retorno no anulable puede ser nulo.
  • NotNull Un valor de retorno anulable nunca será nulo.

Pero estos atributos solo afectan el análisis anulable para los llamantes de los miembros que están anotados con ellos. Por lo general, aplica estos atributos a retornos de métodos, propiedades e indexadores getters.

Por lo tanto, puede considerar todas sus propiedades no anulables y decorarlas con un MaybeNullatributo, lo que indica que devuelven un nullvalor posible

public class Item
{
    public string Id { get; set; } = "";
    [MaybeNull] public string Name { get; set; } = default!;
    [MaybeNull] public string Description { get; set; } = default!;
}

El siguiente ejemplo muestra el uso de la Itemclase actualizada . Como puede ver, la segunda línea no muestra advertencia, pero la tercera sí

var item = new Item();
string id = item.Id;
string name = item.Name; //warning CS8600: Converting null literal or possible null value to non-nullable type.

O puede hacer que todas las propiedades sean anulables y usar NoNullpara indicar que el valor de retorno no puede ser null( Idpor ejemplo)

public class Item
{
    [NotNull] public string? Id { get; set; }
    public string? Name { get; set; }
    public string? Description { get; set; }
}

La advertencia será la misma con el ejemplo anterior.

También hay AllowNull/DisallowNull atributos de precondición para parámetros de entrada, propiedades y definidores de indexadores, que funcionan de manera similar.

  • AllowNull Un argumento de entrada no anulable puede ser nulo.
  • DisallowNull Un argumento de entrada anulable nunca debe ser nulo.

No creo que le ayude, ya que su clase se llena desde la base de datos, pero puede usarlos para controlar la nulabilidad de los establecedores de propiedades, como este para la primera opción

[MaybeNull, AllowNull] public string Description { get; set; }

Y para el segundo

[NotNull, DisallowNull] public string? Id { get; set; }

Algunos detalles útiles y ejemplos de post / condiciones previas se pueden encontrar en este artículo de devblog

Pavel Anikhouski
fuente
6

La respuesta del libro de texto en este escenario es usar un string?para su Idpropiedad, pero también decorarlo con el [NotNull]atributo:

public class Item
{
  [NotNull] public string? Id { get; set; }
  public string Name { get; set; }
  public string? Description { get; set; }
}

Referencia: Según la documentación , el [NotNull]atributo "especifica que una salida no es nula, incluso si el tipo correspondiente lo permite".

Entonces, ¿qué está pasando exactamente aquí?

  1. En primer lugar, el string?tipo de retorno impide que el compilador de aviso de que la propiedad no está inicializado durante la construcción y por lo tanto será por defecto a null.
  2. Luego, el [NotNull]atributo evita una advertencia al asignar la propiedad a una variable no anulable o al intentar desreferenciarla, ya que está informando al análisis de flujo estático del compilador que, en la práctica , esta propiedad nunca lo será null.

Advertencia: Al igual que con todos los casos que involucran el contexto de nulabilidad de C #, técnicamente no hay nada que le impida devolver un nullvalor aquí y, por lo tanto, potencialmente, introducir algunas excepciones posteriores; es decir, no hay una validación de tiempo de ejecución lista para usar. Todo lo que C # ofrece es una advertencia del compilador. Cuando presentas, [NotNull]estás anulando efectivamente esa advertencia al darle una pista sobre la lógica de tu negocio. Como tal, cuando anota una propiedad con [NotNull], está asumiendo la responsabilidad de su compromiso de que "esto nunca sucederá ya que Ides un PK y nunca puede ser nulo".

A fin de ayudar a mantener ese compromiso, es posible que además desee para anotar la propiedad con el [DisallowNull]atributo:

public class Item
{
  [NotNull, DisallowNull] public string? Id { get; set; }
  public string Name { get; set; }
  public string? Description { get; set; }
}

Referencia: de acuerdo con la documentación , el [DisallowNull]atributo "especifica que nullno se permite como entrada, incluso si el tipo correspondiente lo permite".

Esto puede no ser relevante en su caso, ya que los valores se asignan a través de la base de datos, pero el [DisallowNull]atributo le dará una advertencia si alguna vez intenta asignar un nullvalor (capaz) a Id, aunque el tipo de retorno de lo contrario permita que sea nula . A este respecto, Idactuaría exactamente como una stringmedida de lo análisis de flujo estático C # 's se refiere, mientras que también permite que el valor de permanecer sin inicializar entre la construcción del objeto y la población de la propiedad.

Nota: Como otros han mencionado, también puede lograr un resultado prácticamente idéntico al asignar el Idvalor predeterminado de cualquiera default!o null!. Esto es, sin duda, una preferencia estilística. Prefiero usar las anotaciones de nulabilidad ya que son más explícitas y proporcionan un control granular, mientras que es fácil abusar de ellas !como una forma de cerrar el compilador. La inicialización explícita de una propiedad con un valor también me molesta si sé que nunca voy a usar ese valor, incluso si es el valor predeterminado.

Jeremy Caney
fuente
-2

La cadena es un tipo de referencia y siempre anulable, no necesita hacer nada especial. Podría tener problemas solo más tarde si desea asignar este tipo de objeto a otro, pero puede manejarlo más tarde.

dino
fuente
8
Él está hablando sobre los tipos de referencia Nullable en C # 8
Magnus