27 votes

Peut-être un compilateur C# bug dans Visual Studio 2015

Je pense que c'est un bug du compilateur.

L'application de console suivante compile et exécute parfaitement lorsqu'il est compilé avec VS 2015:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = MyStruct.Empty;
        }

        public struct MyStruct
        {
            public static readonly MyStruct Empty = new MyStruct();
        }
    }
}

Mais maintenant, il se fait bizarre: Ce code compile, mais il jette à un TypeLoadException lors de l'exécution.

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = MyStruct.Empty;
        }

        public struct MyStruct
        {
            public static readonly MyStruct? Empty = null;
        }
    }
}

Avez-vous rencontrez le même problème? Si oui, je vais déposer un problème à Microsoft.

Le code est absurde, mais je l'utiliser pour améliorer la lisibilité et à atteindre la désambiguïsation.

J'ai méthodes différentes surcharges comme

void DoSomething(MyStruct? arg1, string arg2)

void DoSomething(string arg1, string arg2)

L'appel d'une méthode de cette façon...

myInstance.DoSomething(null, "Hello world!")

... ne compile pas.

L'appel

myInstance.DoSomething(default(MyStruct?), "Hello world!")

ou

myInstance.DoSomething((MyStruct?)null, "Hello world!")

fonctionne, mais semble laid. Je préfère de cette façon:

myInstance.DoSomething(MyStruct.Empty, "Hello world!")

Si je mets de la Empty variable dans une autre classe, tout fonctionne bien:

public static class MyUtility
{
    public static readonly MyStruct? Empty = null;
}

Comportement étrange, n'est-ce pas?


Mise à JOUR 2016-03-29

J'ai ouvert un ticket ici: http://github.com/dotnet/roslyn/issues/10126


Mise à JOUR 2016-04-06

Un nouveau ticket a été ouvert ici: https://github.com/dotnet/coreclr/issues/4049

16voto

Eric Lippert Points 300275

Tout d'abord, il est important lors de l'analyse de ces questions pour faire un minimum de tête, afin que nous puissions affiner où est le problème. Dans le code d'origine il y a trois harengs rouges: l' readonly, l' static et de la Nullable<T>. Aucun sont nécessaires pour reproduire le problème. Voici un minimum de repro:

struct N<T> {}
struct M { public N<M> E; }
class P { static void Main() { var x = default(M); } }

Cette compile dans la version actuelle de VS, mais jette un type de charge exception à l'exécution.

  • L'exception n'est pas déclenchée par l'utilisation d' E. Il est déclenché par toute tentative d'accès de type M. (Comme on pourrait s'y attendre dans le cas d'un type de charge exception).
  • L'exception reproduit si le champ est statique ou de l'instance, en lecture seule ou non; cela n'a rien à voir avec la nature du terrain. (Toutefois, il doit être un champ! La question n'est pas de repro si c'est, par exemple, une méthode.)
  • L'exception n'a rien à voir avec "l'invocation"; rien n'est "invoquée" dans le minimum de repro.
  • L'exception n'a rien à voir avec l'accès des membres de l'opérateur ".". Il n'apparaît pas dans le minimum de repro.
  • L'exception n'a rien à voir avec nullable; rien n'est nullable dans le minimum de repro.

Maintenant, nous allons faire plus d'expériences. Que faire si nous faisons N et M des classes? Je vais vous dire les résultats:

  • Le comportement ne se reproduit que lorsque les deux sont des structures.

Nous pourrions continuer à discuter pour savoir si le problème se reproduit uniquement lorsque M dans un certain sens, "directement", mentionne lui-même, ou si un "indirecte" cycle se reproduit aussi le bug. (Le dernier est vrai). Et comme Corey notes, dans sa réponse, on pourrait aussi se demander "est-ce que les types doivent être générique?" Non; il y a une tête encore plus réduit que celui-ci avec pas de génériques.

Cependant, je pense que nous avons assez de terminer notre discussion de la tête et de passer à la question à portée de main, ce qui est "est-ce un bug, et si oui, en quoi?"

Clairement quelque chose qui est foiré ici, et je n'ai pas le temps aujourd'hui de faire le tri où le blâme doit tomber. Voici quelques réflexions:

  • La règle contre les structures contenant des membres d'eux-mêmes, manifestement, ne s'applique pas ici. (Voir la section 11.3.1 du C# 5 cahier des charges, qui est celui que j'ai présent à portée de main. Je note que cette section pourrait bénéficier d'une attention réécriture avec les génériques dans l'esprit; certains de la langue ici est un peu imprécis.) Si E est statique alors que l'article ne s'applique pas; si elle n'est pas statique, alors que les dispositions de l' N<M> et M peut à la fois être calculée indépendamment.

  • Je ne connais pas de règle dans le langage C# qui interdirait cet arrangement de types.

  • Il pourrait être le cas que le CLR spécification interdit cet arrangement de types, et le CLR est en droit de lever une exception ici.

Donc résumons maintenant les possibilités:

  • Le CLR a un bug. Ce type de topologie doit être légale, et il est faux de le CLR à jeter ici.

  • Le CLR comportement est correct. Ce type de topologie est illégal, et qu'il est correct de le CLR à jeter ici. (Dans ce cas, il peut être le cas que le CLR a un spec bogue, dans la que ce fait ne peut pas être expliquée de manière adéquate dans le cahier des charges. Je n'ai pas le temps de faire CLR spec plongée aujourd'hui.)

Supposons pour les besoins de la discussion que la seconde est vrai. Que peut-on dire aujourd'hui sur le C#? Quelques possibilités:

  • La spécification du langage C# interdit ce programme, mais la mise en œuvre permet. La mise en œuvre a un bug. (Je crois que ce scénario est faux.)

  • La spécification du langage C# n'interdit pas ce programme, mais il peut être fait pour le faire à une mise en œuvre raisonnable des coûts. Dans ce scénario, la spécification C# est en faute, il doit être fixé, et la mise en œuvre doit être adaptée.

  • La spécification du langage C# n'interdit pas le programme, mais la détection du problème au moment de la compilation ne peut pas être fait à un coût raisonnable. C'est le cas avec à peu près tout runtime accident, votre programme s'est écrasé au moment de l'exécution, car le compilateur ne pouvais pas vous arrêter de l'écriture d'un buggy programme. C'est juste un buggy programme; malheureusement, il n'avait aucune raison de savoir qu'il était buggé.

En résumé, nos possibilités sont:

  • Le CLR a un bug
  • Le C# spec a un bug
  • Le C# de la mise en œuvre a un bug
  • Le programme a un bug

Un de ces quatre doit être vrai. Je ne sais pas qui il est. Ont été, j'ai demandé à deviner, je choisirais le premier; je ne vois pas pourquoi le type CLR chargeur devrait reculer sur ce point. Mais peut-être il ya une bonne raison que je ne sais pas; j'espère un expert sur le type CLR chargement de la sémantique sonne dans.


Mise à JOUR:

Cette question est suivie ici:

https://github.com/dotnet/roslyn/issues/10126

Pour résumer les conclusions de l'équipe C# dans cette question:

  • Le programme est légal selon la CLI et C# spécifications.
  • Le C# 6 compilateur permet au programme, mais certaines implémentations de l'interface CLI jeter un type de charge exception. C'est un bug dans les implémentations.
  • Le CLR de l'équipe est au courant du bug, et apparemment, il est difficile de fixer sur la poussette, les implémentations.
  • L'équipe C# est en considérant rendre le code juridique de produire un avertissement, ce sera un échec lors de l'exécution sur certains, mais pas tous, les versions de la CLI.

Le C# et le CLR équipes sont sur le présent; le suivi avec eux. Si vous avez plus de soucis avec ce problème, veuillez post pour le suivi de la question, ce n'est pas ici.

10voto

Corey Points 4845

Ce n'est pas un bug en 2015, mais éventuellement un langage C# bug. La discussion ci-dessous se rapporte à pourquoi les membres de l'instance ne peuvent mettre en place des boucles, et pourquoi un Nullable<T> sera la cause de cette erreur, mais ne devrait pas s'appliquer aux membres statiques.

Je dirais que comme une langue de bug, pas un bug du compilateur.


La compilation de ce code dans VS2013 donne l'erreur de compilation suivante:

Struct membre 'ConsoleApplication1.Programme.MyStruct.Vide' de type 'System.Nullable " provoque un cycle dans la structure de mise en page

Une recherche rapide avère que cette réponse , qui stipule:

Ce n'est pas légal d'avoir une structure qui contient lui-même en tant que membre.

Malheureusement, l' System.Nullable<T> type qui est utilisé pour les valeurs null des instances de types de valeur est également un type de valeur et doit donc avoir une taille fixe. Il est tentant de penser d' MyStruct? comme un type de référence, mais il est vraiment pas. La taille de l' MyStruct? est basée sur la taille de l' MyStruct... ce qui apparemment introduit une boucle dans le compilateur.

Prenons, par exemple:

public struct Struct1
{
    public int a;
    public int b;
    public int c;
}

public struct Struct2
{
    public Struct1? s;
}

À l'aide de System.Runtime.InteropServices.Marshal.SizeOf() vous trouverez qu' Struct2 est de 16 octets de long, indiquant qu' Struct1? n'est pas une référence, mais une structure qui est de 4 octets (standard rembourrage taille) de plus que l' Struct1.


Ce qui est pas ce qui se passe ici

En réponse à Jules Depulla de réponse et les commentaires, voici ce qui est réellement qui se passe lorsque vous accédez à un static Nullable<T> champ. À partir de ce code:

public struct foo
{
    public static int? Empty = null;
}

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

Ici est généré en IL de LINQPad:

IL_0000:  ldsflda     UserQuery+foo.Empty
IL_0005:  call        System.Nullable<System.Int32>.get_HasValue
IL_000A:  ldc.i4.0    
IL_000B:  ceq         
IL_000D:  call        System.Console.WriteLine
IL_0012:  ret         

La première instruction obtient l'adresse du champ statique foo.Empty et le pousse sur la pile. Cette adresse est la garantie d'être non-nulle que Nullable<Int32> est une structure et non pas un type de référence.

Suivant l' Nullable<Int32> cachés de la fonction membre get_HasValue est appelée pour récupérer l' HasValue de la valeur de propriété. Cela ne peut pas entraîner une référence nulle, puisque, comme mentionné précédemment, l'adresse d'une valeur type de domaine doit être non null, indépendamment de la valeur contenue à l'adresse.

Le reste n'est que de comparer le résultat à 0 et d'envoyer le résultat à la console.

À aucun moment dans ce processus est-il possible de 'invoquer un nul sur un type de" ce que cela signifie. Types de valeur n'ont pas de valeur null adresses, de sorte que l'invocation de méthode sur les types de valeur ne peut pas directement le résultat dans un objet null erreur de référence. C'est pourquoi nous ne les appelons pas des types référence.

1voto

Corey Points 4845

Maintenant que nous avons eu une longue discussion à propos de quoi et pourquoi, voici une façon de contourner le problème sans avoir à attendre sur les différents .NET équipes pour suivre la question et de déterminer si quelque chose va être fait à ce sujet.

Le problème semble être limité aux types de champs qui sont les types de valeurs de référence qui de retour de ce type, d'une certaine façon, que ce soit en tant que paramètres génériques ou des membres statiques. Par exemple:

public struct A { public static B b; }
public struct B { public static A a; }

Ugh, je me sens sale maintenant. Mauvaise programmation orientée objet, mais il démontre que le problème existe, sans invoquer les médicaments génériques en quelque sorte.

Parce qu'ils sont des types de valeur le chargeur de type détermine qu'il existe une circularité concernées qui doivent être ignorées en raison de l' static mot-clé. Le compilateur C# a été assez intelligent pour le comprendre. Si elle doit avoir ou pas, c'est pour les specs, sur lequel je n'ai aucun commentaire.

Cependant, en changeant A ou B de class le problème s'évapore:

public struct A { public static B b; }
public class B { public static A a; }

Donc, le problème peut être évité en utilisant un type de référence pour stocker la valeur réelle et de convertir le domaine d'une propriété:

public struct MyStruct
{
    private static class _internal { public static MyStruct? empty = null; }
    public static MyStruct? Empty => _internal.empty;
}

C'est un tas lent parce que c'est une propriété au lieu d'un champ et d'appels à la il invoquera l' get méthode, je ne voudrais pas l'utiliser pour des performances de code critique, mais comme une solution de contournement au moins vous permet de faire le travail jusqu'à ce qu'une solution appropriée est disponible.

Et si il s'avère que ce n'est pas se régler, au moins, nous avons une bidouille que nous pouvons utiliser pour le contourner.

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