32
votes

Quand un vérification nul peut-il lancer une nullReferenceException

Je sais que cela peut sembler impossible au début et cela me semblait également par le fait, mais récemment, j'ai vu exactement ce type de code lancer un nullReferenceException , donc c'est certainement possible.

Malheureusement, il n'y a à peu près aucun résultat sur Google qui explique lorsque le code comme foo == null peut lancer un NRE, ce qui peut rendre difficile le débogage et la compréhension de la raison pour laquelle cela s'est produit. Ainsi, dans l'intérêt de documenter les façons possibles, cet événement apparemment bizarre pourrait se produire.

de quelles manières ce code foo == null lance un nullReferenceException ?


11 commentaires

Le type statique de foo implémente-t-il l'opérateur == ?


@JOesewell: La classe que foo est une instance d'implémentation de l'opérateur == , mais je pense qu'il devrait y avoir une implémentation par défaut. Quoi qu'il en soit, je n'ai pas l'intention que cette question concerne mon cas particulier. Je préférerais que ce genre de chose peut se produire en général.


Si vous pouvez reproduire l'exception dans le débogueur, vous pouvez simplement configurer le débogueur pour s'arrêter sur la première exception pour NullReferenceException. Cela vous permettra de voir où l'exception est réellement lancée (y compris les sorties, les opérateurs surchargés, etc.).


Si vous souhaitez être en toute sécurité lorsque vous vérifiez si une instance est nul et ignorez les remplacements de l'opérateur que vous pouvez faire foo est null . C'est la même chose que l'appel ReferenceEquals (foo, null); .


Je pense que la question devrait être rouverte. Il n'est pas censé être sur mon code pour m'aider à le réparer. En fait, j'ai déjà corrigé mon code avant de poser cette question. Cette question est principalement destinée à explorer les raisons pour lesquelles quelque chose d'inattendu comme celui-ci peut se produire et aider d'autres personnes qui pourraient entrer dans cette position. Il ne sert donc à rien de me fournir plus de détails de débogage. Cela ne ferait que parcourir la question du but prévu.


Je suis d'accord que cette question n'aurait pas dû être fermée. Bien que des questions similaires puissent nécessiter des détails de débogage, je crois que celui-ci est suffisamment étroit et peut servir de bon poste canonique, donc mon vote pour rouvrir.


"Cette question est principalement destinée à explorer les raisons ..." - Stack Overflow n'est pas le lieu pour "explorer des raisons". Ces questions sont trop larges, manquant de focus et ne respectent pas les normes du site de toutes sortes de manières. Le fait est: vous obtenez une exception que vous ne pouvez pas expliquer, et la seule façon de l'expliquer est de fournir le code qui lance l'exception, que vous n'avez pas fait . ...


... La seule réponse qui peut être fournie est une pure spéculation; Vous pouvez obtenir une douzaine de réponses, et pourtant, aucun d'entre eux ne résout vraiment votre problème (accordé, l'un des écureuils aveugles pourrait trouver leur gland ... mais cela ne justifie pas la question).


@Peterduniho La question est suffisamment focalisée de manière étroite. Il y a au plus une poignée de raisons et je ne vois pas de dizaines de réponses ici. Celui qui prend les propriétés est le plus hors de propos ici, car la question concerne une variable.


@Peterduniho: J'ai édité ma question pour espérer rendre mes intentions plus claires. Si je comprends bien, il devrait être normal de poser des questions sur toutes les façons possibles que X pourrait se produire, surtout lorsque X est une telle chose et une chose rare. Encore une fois, j'ai déjà corrigé mon propre code et il ne s'agit en aucun cas. Il était simplement motivé par celui-ci et par l'absence de liens utiles sur ce sujet lorsque je l'ai Google. Je veux simplement faciliter que les futurs personnes à déboguer et à comprendre pourquoi leur chèque nul lançait un NRE. N'est-ce pas beaucoup dans l'esprit de répondre à des questions de programmation comme celle-ci?


Je suggère de jeter un œil à la classe NullReferenceException. Il énumère diverses raisons pour lesquelles vous rencontrerez cette exception. docs.microsoft.com/en-us/ dotnet / api /…


5 Réponses :


38
votes

En C #, vous pouvez surcharger les opérateurs pour ajouter une logique personnalisée sur une comparaison comme celle-ci. Par exemple:

Test test1 = null;
bool x = test1 == null;

alors cela produirait une exception de référence nul:

class Test
{
    public string SomeProp { get; set; }
    
    public static bool operator ==(Test test1, Test test2)
    {
        return test1.SomeProp == test2.SomeProp;
    }

    public static bool operator !=(Test test1, Test test2)
    {
        return !(test1 == test2);
    }
}


6 commentaires

Terminologie Remarque: Ceci est surcharge - Vous ne pouvez pas Opérateurs de remplacement en C #.


Je voudrais ajouter, imo c'est une mauvaise conception d'opérateur si vous ne vérifiez pas si les arguments sont null , je tiens généralement compte de la nuls dans la vérification de l'équivalence, par exemple dans == Si les deux sont null , retournez vrai, si l'on est vrai mais pas les deux, renvoyez false. IIRC C'est ainsi que certaines classes l'ont également fait dans la source de référence .NET.


@JRH Assurez-vous simplement d'utiliser object.referenceequal ou vous obtiendrez une boucle infinie.


@Kirkwoll oui, cela fait un moment que je n'ai pas fait cela, mais je pense que j'ai utilisé quelque chose comme ce


@Kirkwoll: ou mieux (plus concis et plus idiomatique de nos jours), utilisez est null .


@Jonskeet oh wow, n'avait pas envisagé de l'utiliser pour ce scénario. Impressionnant!



16
votes

Un exemple est avec Getters:

class Program
{
    static void Main(string[] args)
    {
        new Example().Test();
    }
}

class Example
{
    private object foo
    {
        get => throw new NullReferenceException();
    }

    public void Test()
    {
        Console.WriteLine(foo == null);
    }
}

Ce code produira une NullReferenceException.


0 commentaires

9
votes

Bien que tout à fait ésotérique, il est possible de provoquer ce type de comportement via des implémentations personnalisées de dynamicMetaObject . Ce serait un exemple rare mais intéressant de ce que cela pourrait se produire:

void Main()
{
    dynamic foo = new TestDynamicMetaObjectProvider();
    object foo2 = 0;
    
    Console.WriteLine(foo == foo2);
}

public class TestDynamicMetaObjectProvider : IDynamicMetaObjectProvider
{
    public DynamicMetaObject GetMetaObject(Expression parameter)
    {
        return new TestMetaObject(parameter, BindingRestrictions.Empty, this);
    }
}

public class TestMetaObject : DynamicMetaObject
{
    public TestMetaObject(Expression expression, BindingRestrictions restrictions)
        : base(expression, restrictions)
    {
    }

    public TestMetaObject(Expression expression, BindingRestrictions restrictions, object value)
        : base(expression, restrictions, value)
    {
    }

    public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg)
    {
        // note it doesn't have to be an explicit throw.  Any improper property
        // access could bubble a NullReferenceException depending on the 
        // custom implementation.
        throw new NullReferenceException();
    }
}


0 commentaires

7
votes

Pas littéralement votre code, mais en attente d'une tâche nulle, je vais également lancer:

public class Program
{
    public static async Task Main()
    {
        var s = ReadStringAsync();
        if (await s == null)
        {
            Console.WriteLine("s is null");
        }
    }

    // instead of Task.FromResult<string>(null);
    private static Task<string> ReadStringAsync() => null;
}

Notez cependant que le débogueur peut faire mal l'emplacement de la mise en place des déclarations. Il pourrait montrer l'exception lancée sur la vérification de l'égalité, tandis qu'elle se produit au code précédent.


5 commentaires

"Notez cependant que le débogueur peut faire mal l'emplacement des instructions de lancer. Il peut montrer l'exception lancée sur le contrôle d'égalité, alors qu'elle se produit au code précédent." Je n'en ai jamais entendu parler auparavant. Pouvez-vous expliquer comment ou quand cela peut se produire et s'il existe un moyen d'éviter cela?


Une cause évidente consiste à déboguer une version de version ou à utiliser des PDB obsolètes, mais également à coder avec plusieurs blocs de lancement d'essai. Voir aussi mauvais numéro de ligne sur la trace de pile et numéros de lignewrong dans la trace de pile .


Notez que la tâche NULL est à peu près garantie de se produire dans les tests unitaires qui se maîtrisent les interfaces. C'est à dire. Vous manquez des conditions de correspondance pour la configuration dans votre moq et vous obtenez le résultat par défaut null pour la tâche. Personne de manière fiable les gens plusieurs fois.


@Alexei setup () toutes les choses et mockBehavior.strict tout le long, à l'exception des journalistes.


@Theredfox: Je l'ai vu. Le regroupement des parenthèses suit. Soit il s'agit de PDBS obsolètes, soit (la ligne précédente s'est terminée en exécutant un appel de fonction vide et cette fonction a lancé le null et (cette fonction a été inclinée par la gigue ou n'a pas été considérée comme un code débordement)) ou la ligne précédente était un lancer déclaration.



1
votes

foo == null fait en effet la résolution de surcharge de l'opérateur, et l'opérateur en question n'a pas géré d'être transmis un null. Nous commençons à envisager d'écrire foo == null obsolète et préférer (prendre une page de Visual Basic) foo est null ou ! (Foo est null) qui sera bientôt Full n'est pas nul pour en informer explicitement une vérification du pointeur nul.

Corrigez votre opérateur == IMPLICATION. Il ne devrait pas lancer, mais c'est le cas.


1 commentaires

"Correction de votre Operator == IMPLICATION" Quelle implémentation? L'OP n'a rien mentionné sur la surcharge de l'opérateur == . De plus, cette possibilité est déjà couverte dans Réponse de Jonesopolis Quoi qu'il en soit.