Marcar parâmetros como NÃO anuláveis ​​em C # /. NET?

98

Existe um atributo simples ou contrato de dados que posso atribuir a um parâmetro de função que evita que nullseja passado em C # / .net? Idealmente, isso também verificaria em tempo de compilação para ter certeza de que o literal nullnão está sendo usado em qualquer lugar para ele e em tempo de execução ArgumentNullException.

Atualmente escrevo algo como ...

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

... para cada argumento que espero não ser null.

Na mesma nota, existe um oposto Nullable<>pelo qual o seguinte falharia:

NonNullable<string> s = null; // throw some kind of exception
Neil C. Obremski
fonte
7
Com versões recentes do C #, usar "nameof ()" funciona melhor: throw new ArgumentNullException (nameof (arg)); Dessa forma, se você refatorar o nome, se houver ondulações em sua instrução de lançamento
Flydog57 01 de
C # 8 agora oferece suporte a tipos de referência anuláveis, que é uma opção do compilador que você pode ativar em seu projeto. docs.microsoft.com/en-us/dotnet/csharp/nullable-references
Paul Stegler

Respostas:

69

Não há nada disponível em tempo de compilação, infelizmente.

Eu tenho uma solução um pouco hacky que postei no meu blog recentemente, que usa uma nova estrutura e conversões.

No .NET 4.0 com as coisas de Code Contracts , a vida será muito mais agradável. Ainda seria muito bom ter a sintaxe de linguagem real e suporte em torno da não nulidade, mas os contratos de código ajudarão muito.

Também tenho um método de extensão em MiscUtil chamado ThrowIfNull que o torna um pouco mais simples.

Um último ponto - alguma razão para usar " if (null == arg)" em vez de " if (arg == null)"? Acho o último mais fácil de ler, e o problema que o primeiro resolve em C não se aplica a C #.

Jon Skeet
fonte
2
> Não há nada disponível em tempo de compilação, infelizmente. > ...> Ainda seria muito bom ter a sintaxe da linguagem real e suporte em torno da não anulação, eu concordo. Eu também gostaria de ver erros de tempo de compilação sendo levantados.
AndrewJacksonZA,
2
@Jon, "if (arg = null)" não funciona se houver uma conversão implícita para bool definido? Admito que pode parecer perverso, mas compila ...
Thomas S. Trias
11
@ ThomasS.Trias: Sim, naquele caso extremamente obscuro, combinado com a existência de um erro de digitação, combinado com a falta de testes em torno desse código, você acabaria com um problema. Nesse ponto, acho que você tem problemas maiores :)
Jon Skeet
4
@Jon: Concedido. Acho que vou desistir de minhas amadas condições Yoda. :-)
Thomas S. Trias
1
@SebastianMach: Não acredito que a razão para isso if (null == x)seja devido a diferenças naturais de linguagem. Eu acredito que é realmente sobre um estilo obsoleto que apenas se propaga por meio de exemplos etc.
Jon Skeet
21

Sei que estou incrivelmente atrasado para essa pergunta, mas sinto que a resposta se tornará relevante conforme a última grande iteração do C # se aproxima do lançamento e, em seguida, é lançada. No C # 8.0, uma grande mudança ocorrerá, o C # assumirá que todos os tipos são considerados não nulos.

De acordo com Mads Torgersen:

O problema é que as referências nulas são muito úteis. Em C #, eles são o valor padrão de cada tipo de referência. Qual seria o valor padrão? Que outro valor uma variável teria, até que você possa decidir o que mais atribuir a ela? Com qual outro valor poderíamos pavimentar uma nova matriz de referências alocada, até que você consiga preenchê-la?

Além disso, às vezes nulo é um valor lógico por si só. Às vezes, você deseja representar o fato de que, digamos, um campo não tem um valor. Não há problema em passar “nada” para um parâmetro. A ênfase está em algumas vezes, no entanto. E aqui está outra parte do problema: linguagens como C # não permitem que você expresse se um valor nulo aqui é uma boa ideia ou não.

Portanto, a resolução delineada por Mads é:

  1. Acreditamos que é mais comum querer que uma referência não seja nula. Os tipos de referência anuláveis ​​seriam os mais raros (embora não tenhamos bons dados para nos dizer por quanto), portanto, eles são os que devem exigir uma nova anotação.

  2. A linguagem já tem uma noção de - e uma sintaxe para - tipos de valor anuláveis. A analogia entre os dois tornaria a adição da linguagem conceitualmente mais fácil e linguisticamente mais simples.

  3. Parece certo que você não deve sobrecarregar a si mesmo ou ao consumidor com valores nulos pesados, a menos que tenha decidido ativamente que os deseja. Nulos, e não a ausência deles, deve ser algo que você explicitamente deve aceitar.

Um exemplo do recurso desejado:

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

A visualização está disponível para Visual Studio 2017, visualização 15.5.4+.

Greg
fonte
Alguém sabe se isso alguma vez acabou fazendo parte do C # 8.0?
TroySteven
@TroySteven Sim, você deve ativá-lo por meio de configurações no Visual Studio.
Greg
@TroySteven Aqui está a documentação sobre ele. docs.microsoft.com/en-us/dotnet/csharp/nullable-references
Greg
Bom, então parece que você precisa habilitá-lo antes de começar a funcionar, isso é bom para compatibilidade com versões anteriores.
TroySteven
15

Eu sei que esta é uma pergunta MUITO antiga, mas esta estava faltando aqui:

Se você usar ReSharper / Rider, poderá usar a Estrutura Anotada .

Edit : Acabei de receber um -1 aleatório para esta resposta. Isso é bom. Esteja ciente de que ainda é válido, embora não seja mais a abordagem recomendada para projetos C # 8.0 + (para entender o porquê, consulte a resposta de Greg ).

rsenna
fonte
1
Curiosamente, que por padrão seu aplicativo compilado não terá referência ao JetBrains.Annotations.dll, então você não precisa distribuí-lo com o aplicativo: Como usar o JetBrains Annotations para melhorar as inspeções ReSharper
Lu55
9

Confira os validadores na biblioteca corporativa. Você pode fazer algo como:

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

Então, em seu código, quando quiser validá-lo:

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

ValidationResults vrInfo = InternalValidator.Validate(myObject);
Eu não
fonte
0

não o mais bonito, mas:

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

você também pode ser mais criativo no 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;
        }

claro que você pode usar um interceptor ou reflexão, mas estes são fáceis de seguir / usar com pouca sobrecarga

Jeff Grizzle
fonte
3
Prática muito ruim para usar essas consultas: return (from o in methodParams where o == null).Count() > 0; Use: return methodParams.Any(o=>o==null);será muito mais rápido em grandes coleções
Yavanosta
Por que desmaiar a exceção? Por que não apenas jogá-lo?
Martin Capodici
-4

Ok, esta resposta está um pouco atrasada, mas aqui está como estou resolvendo:

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

Use este método de extensão para tratar strings nulas e vazias como a mesma coisa.

Por exemplo

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

Não é o ideal, eu sei que você deve se lembrar de chamar o padrão em todos os lugares, mas é uma solução.

Martin Capodici
fonte
5
como isso é melhor / mais fácil do que as verificações (x == null)? ou a função String.IsNotNullOrEmpty.
Batavia
Não é muito melhor do que string.IsNotNullOrEmptyrealmente diferente do açúcar de ter o que é importante para você à esquerda. Pode ser alimentado em outras funções (comprimento, concatenação) etc. Marginalmente melhor.
Martin Capodici
@MartinCapodici Além disso, isso cria uma ilusão de que você está seguro para chamar o método de instância ( Default()) em um null ( model.Day). Eu sei que os métodos de extensão não são verificados em relação ao nulo, mas meus olhos não estão cientes e eles já imaginaram o ?no código: model?.Day?.Default():)
Alb