¿Marcar parámetros como NO anulables en C # /. NET?

98

¿Existe un atributo simple o contrato de datos que pueda asignar a un parámetro de función que evite que nullse pase en C # / .NET? Idealmente, esto también se verificaría en tiempo de compilación para asegurarse de que el literal nullno se esté usando en ningún lugar para él y en el lanzamiento en tiempo de ejecución ArgumentNullException.

Actualmente escribo algo como ...

if (null == arg)
  throw new ArgumentNullException("arg");

... por cada argumento que espero no sea null.

En la misma nota, ¿existe un opuesto al Nullable<>que fallaría lo siguiente?

NonNullable<string> s = null; // throw some kind of exception
Neil C. Obremski
fuente
7
Con las versiones recientes de C #, el uso de "nameof ()" funciona mejor: lanza una nueva ArgumentNullException (nameof (arg)); De esa manera, si refactoriza el nombre, si se refleja en su declaración de lanzamiento
Flydog57
C # 8 ahora admite tipos de referencia que aceptan valores NULL, que es una opción del compilador que puede activar en su proyecto. docs.microsoft.com/en-us/dotnet/csharp/nullable-references
Paul Stegler

Respuestas:

69

Desafortunadamente, no hay nada disponible en tiempo de compilación.

Tengo una solución un poco hacky que publiqué en mi blog recientemente, que usa una nueva estructura y conversiones.

En .NET 4.0 con las cosas de Code Contracts , la vida será mucho mejor. Aún sería bastante bueno tener una sintaxis de lenguaje real y soporte en torno a la no nulabilidad, pero los contratos de código ayudarán mucho.

También tengo un método de extensión en MiscUtil llamado ThrowIfNull que lo hace un poco más simple.

Un último punto: ¿alguna razón para usar " if (null == arg)" en lugar de " if (arg == null)"? Encuentro que el último es más fácil de leer, y el problema que el primero resuelve en C no se aplica a C #.

Jon Skeet
fuente
2
> Desafortunadamente, no hay nada disponible en tiempo de compilación. > ...> Sería bastante bueno tener sintaxis de lenguaje real y soporte en torno a la no nulabilidad, estoy de acuerdo. También me gustaría que se produjeran errores en tiempo de compilación.
AndrewJacksonZA
2
@Jon, ¿no funciona "if (arg = null)" si hay una conversión implícita en bool definido? Admito que puede parecer perverso, pero compila ...
Thomas S. Trias
11
@ ThomasS.Trias: Sí, en ese caso extremo increíblemente oscuro, combinado con un error tipográfico, combinado con la falta de pruebas en torno a ese código, terminaría con un problema. En ese punto, creo que tienes problemas más grandes :)
Jon Skeet
4
@Jon: De acuerdo. Supongo que renunciaré a mis amadas condiciones de Yoda. :-)
Thomas S. Trias
1
@SebastianMach: Sin embargo, no creo que la razón if (null == x)se deba a diferencias de lenguaje natural. Creo que realmente se trata de un estilo obsoleto que simplemente se propaga a través de ejemplos, etc.
Jon Skeet
21

Sé que llego increíblemente tarde a esta pregunta, pero creo que la respuesta será relevante a medida que la última versión importante de C # se acerque al lanzamiento y luego al lanzamiento. En C # 8.0 ocurrirá un cambio importante, C # asumirá todos tipos no se consideran nulos.

Según Mads Torgersen:

El problema es que las referencias nulas son muy útiles. En C #, son el valor predeterminado de cada tipo de referencia. ¿Qué más sería el valor predeterminado? ¿Qué otro valor tendría una variable, hasta que pueda decidir qué más asignarle? ¿Con qué otro valor podríamos allanar una matriz de referencias recién asignada, hasta que llegues a completarla?

Además, a veces nulo es un valor sensible en sí mismo. A veces desea representar el hecho de que, digamos, un campo no tiene un valor. Que está bien pasar "nada" como parámetro. Sin embargo, el énfasis está en algunas veces. Y aquí radica otra parte del problema: los lenguajes como C # no le permiten expresar si un nulo aquí es una buena idea o no.

Entonces, la resolución esbozada por Mads es:

  1. Creemos que es más común querer que una referencia no sea nula. Los tipos de referencia que aceptan valores NULL serían los más raros (aunque no tenemos buenos datos que nos indiquen cuánto), por lo que son los que deberían requerir una nueva anotación.

  2. El lenguaje ya tiene una noción y una sintaxis para tipos de valores que aceptan valores NULL. La analogía entre los dos haría que la adición del lenguaje sea conceptualmente más fácil y lingüísticamente más simple.

  3. Parece correcto que no deba agobiarse a sí mismo ni a su consumidor con valores nulos engorrosos a menos que haya decidido activamente que los quiere. Los nulos, no la ausencia de ellos, deberían ser lo que explícitamente debe aceptar.

Un ejemplo de la característica deseada:

public class Person
{
     public string Name { get; set; } // Not Null
     public string? Address { get; set; } // May be Null
}

La vista previa está disponible para Visual Studio 2017, 15.5.4+ vista previa.

Greg
fuente
¿Alguien sabe si esto alguna vez terminó siendo parte de C # 8.0?
TroySteven
@TroySteven Sí, lo es, aunque debe habilitarlo a través de la configuración de Visual Studio.
Greg
@TroySteven Aquí está la documentación al respecto. docs.microsoft.com/en-us/dotnet/csharp/nullable-references
Greg
Bueno, parece que tienes que habilitarlo antes de que comience a funcionar, eso es bueno para la compatibilidad con versiones anteriores.
TroySteven
15

Sé que esta es una pregunta MUY antigua, pero esta faltaba aquí:

Si usa ReSharper / Rider, puede usar el marco anotado .

Editar : Acabo de obtener un -1 aleatorio para esta respuesta. Esta bien. Solo tenga en cuenta que sigue siendo válido, aunque ya no es el enfoque recomendado para los proyectos de C # 8.0 + (para comprender por qué, consulte la respuesta de Greg ).

rsenna
fuente
1
Curiosamente, que de forma predeterminada su aplicación compilada no tendrá referencia a JetBrains.Annotations.dll, por lo que no tendrá que distribuirla con la aplicación: Cómo usar JetBrains Annotations para mejorar las inspecciones de
ReSharper
9

Consulte los validadores en la biblioteca empresarial. Puedes hacer algo como:

private MyType _someVariable = TenantType.None;
[NotNullValidator(MessageTemplate = "Some Variable can not be empty")]
public MyType SomeVariable {
    get {
        return _someVariable;
    }
    set {
        _someVariable = value;
    }
}

Luego, en su código cuando desee validarlo:

Microsoft.Practices.EnterpriseLibrary.Validation.Validator myValidator = ValidationFactory.CreateValidator<MyClass>();

ValidationResults vrInfo = InternalValidator.Validate(myObject);
Yo no
fuente
0

no el más bonito pero:

public static bool ContainsNullParameters(object[] methodParams)
{
     return (from o in methodParams where o == null).Count() > 0;
}

también podría ser más creativo en el método ContainsNullParameters:

public static bool ContainsNullParameters(Dictionary<string, object> methodParams, out ArgumentNullException containsNullParameters)
       {
            var nullParams = from o in methodParams
                             where o.Value == null
                             select o;

            bool paramsNull = nullParams.Count() > 0;


            if (paramsNull)
            {
                StringBuilder sb = new StringBuilder();
                foreach (var param in nullParams)
                    sb.Append(param.Key + " is null. ");

                containsNullParameters = new ArgumentNullException(sb.ToString());
            }
            else
                containsNullParameters = null;

            return paramsNull;
        }

Por supuesto, podría usar un interceptor o un reflejo, pero estos son fáciles de seguir / usar con poca sobrecarga.

Jeff Grizzle
fuente
3
Muy mala práctica con este tipo de consultas: return (from o in methodParams where o == null).Count() > 0; Uso: return methodParams.Any(o=>o==null);será mucho más rápido en colecciones grandes
Yavanosta
¿Por qué pasar la excepción? ¿Por qué no simplemente tirarlo?
Martin Capodici
-4

Ok, esta respuesta es un poco tarde, pero así es como la estoy resolviendo:

public static string Default(this string x)
{
    return x ?? "";
}

Utilice este método de exención para que pueda tratar las cadenas nulas y vacías como lo mismo.

P.ej

if (model.Day.Default() == "")
{
    //.. Do something to handle no Day ..
}

No es ideal, lo sé, ya que debes recordar llamar a predeterminado en todas partes, pero es una solución.

Martín Capodici
fuente
5
¿cómo es esto mejor / más fácil que las comprobaciones (x == null)? o la función String.IsNotNullOrEmpty.
Batavia
No es mucho mejor que string.IsNotNullOrEmptyotra cosa que el azúcar de tener lo que te importa a la izquierda. Puede introducirse en otras funciones (longitud, concatenación), etc. Un poco mejor.
Martín Capodici
@MartinCapodici Además, esto crea la ilusión de que es seguro llamar al método de instancia ( Default()) en un null (model.Day). Yo sé los métodos de extensión no se comprueban contra nula, pero mis ojos no son conscientes y que ya se imaginaron el?en el código:model?.Day?.Default():)
Alb