62 votes

Pourquoi le casting d'une dynamique de type objet vers objet déclenche-t-il une exception de référence nulle ?

J'ai la fonction suivante :

public static T TryGetArrayValue<T>(object[] array_, int index_)
{
    ... //some checking goes up here not relevant to question

    dynamic boxed = array_[index_];
    return (T)boxed;
}

Quand je l'appelle de la manière suivante ,

object a = new object();
object v = TUtils.TryGetArrayValue<object>(new object[] { a }, 0);

(T)boxed lance une exception de référence nulle.

Tout autre type que "objet" que j'y mets fonctionne parfaitement.
Vous avez une idée de ce que c'est, et pourquoi cette exception est lancée ?

Éditer : La raison pour laquelle j'utilise la dynamique est d'éviter les exceptions lors de la conversion des types, par exemple :

double a = 123;
int v = TUtils.TryGetArrayValue<int>(new object[] { a }, 0);

43voto

Eric Lippert Points 300275

Je suis d'accord avec les autres répondants qui disent que cela ressemble à un bug. Plus précisément, il semble qu'il s'agisse d'un bogue dans la couche de liaison de l'exécution C#, bien que je n'aie pas étudié la question en profondeur.

Je m'excuse pour l'erreur. Je vais la signaler à l'équipe de test de C# 5 et nous verrons si elle a déjà été signalée et corrigée dans C# 5. (Elle se reproduit dans la récente version bêta, il est donc peu probable qu'elle ait déjà été signalée et corrigée). Si ce n'est pas le cas, il est peu probable qu'un correctif soit intégré à la version finale. Dans ce cas, nous l'envisagerons pour une éventuelle version de service.

Merci d'avoir porté cela à notre attention. Si vous avez envie de saisie d'un numéro de Connect pour le suivre, n'hésitez pas à le faire et à inclure un lien vers cette question StackOverflow. Si vous ne le faites pas, pas de problème, l'équipe de test le saura de toute façon.

14voto

Reed Copsey Points 315315

Il s'agit d'un problème lié au mode de fonctionnement de la dynamique : le liant d'exécution a un problème avec les conversions de l'expression System.Object mais dans la pratique, ce n'est pas vraiment un problème.

Je pense que c'est parce que dynamic au moment de l'exécution, est lui-même toujours System.Object . La spécification de langage C# dans 4.7 déclare : "Le type dynamic est indiscernable de object at run-time". En tant que tel, tout objet utilisé comme dynamic est simplement stocké comme un objet.

Lorsque vous mettez une instance réelle de System.Object dans une dynamique, il se passe quelque chose dans la résolution de liaison d'exécution qui provoque une exception de référence nulle.

Cependant, tout autre type qui n'est pas System.Object fonctionne - même les types de référence et autres, sans faille. En tant que tel, cela devrait vous fournir le comportement approprié, puisqu'il n'y a vraiment aucune raison de créer une instance de System.Object elle-même qui serait transmise - vous voudrez toujours une sous-classe avec d'autres informations sur le type.

Dès que vous utilisez un type "réel", cela fonctionne bien. Par exemple, ce qui suit fonctionne, même s'il est passé et traité comme Object :

public class Program
{
    public static T TryGetArrayValue<T>(object[] array_, int index_)
    {

        dynamic boxed = array_[index_];
        return (T)boxed;
    }

    private static void Main()
    {
        int p = 3;
        object a = p;
        var objects = new[] { a, 4.5 };

        // This works now, since the object is pointing to a class instance
        object v = TryGetArrayValue<object>(objects, 0);
        Console.WriteLine(v);

        // These both also work fine...
        double d = TryGetArrayValue<double>(objects, 1);
        Console.WriteLine(d);
        // Even the "automatic" int conversion works now
        int i = TryGetArrayValue<int>(objects, 1);
        Console.WriteLine(i);
        Console.ReadKey();
    }
}

6voto

Elian Ebbing Points 8363

C'est un comportement vraiment étrange, et cela ressemble en effet à un bug dans l'implémentation de dynamic . J'ai découvert que cette variante ne lève pas d'exception et renvoie bien l'objet :

public static T TryGetArrayValue<T>(object[] array, int index) where T : class
{
    dynamic boxed = array[index];
    return boxed as T;
}

Notez que j'ai dû ajouter une contrainte générique dans la signature de la méthode parce que la méthode as L'opérateur ne fonctionne que si T est un type de référence.

Si vous cherchez une solution de contournement, vous pouvez utiliser ceci (et je sais que c'est moche) :

public static T TryGetArrayValue<T>(object[] array, int index)
{
    dynamic boxed = array[index];

    if (typeof(T) == typeof(object))
        return (T)(boxed as object);

    return (T)boxed;
}

2voto

Jetti Points 1238

Cela a quelque chose à voir avec le mot-clé dynamique. Si je change le type en T pour boxed, cela fonctionne.

    static void Main(string[] args)
    {
        object a = new object();
        object v = TryGetArrayValue<object>(new object[] { a }, 0);

        Console.ReadLine();
    }

    public static T TryGetArrayValue<T>(object[] array_, int index_)
    {

            T boxed = (T)array_[index_];
            return boxed;

    }

Y a-t-il une raison particulière pour laquelle vous utilisez la dynamique ? Vous n'en avez pas vraiment besoin dans ce cas, car vous connaissez le type à l'avance. Si vous regardez, dans votre version, le type de boxed n'est pas object mais dynamic{object}, ce qui pourrait être le problème lorsque vous essayez de faire un cast vers object. Si vous regardez cette version que j'ai postée, vous obtenez un type d'objet et aucune erreur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X