Quel est le cas de coin le plus étrange que vous ayez vu en C # ou .NET? [fermé]

322

Je collectionne quelques cas d'angle et des casse - tête et j'aimerais toujours en savoir plus. La page ne couvre vraiment que les bits et bobs du langage C #, mais je trouve aussi les choses de base .NET intéressantes aussi. Par exemple, en voici une qui n'est pas sur la page, mais que je trouve incroyable:

string x = new string(new char[0]);
string y = new string(new char[0]);
Console.WriteLine(object.ReferenceEquals(x, y));

Je m'attends à ce que pour imprimer Faux - après tout, "nouveau" (avec un type de référence) crée toujours un nouvel objet, n'est-ce pas? Les spécifications pour C # et l'interface CLI indiquent que cela devrait. Eh bien, pas dans ce cas particulier. Il imprime True et l'a fait sur chaque version du cadre avec lequel je l'ai testé. (Je ne l'ai pas essayé sur Mono, certes ...)

Juste pour être clair, ce n'est qu'un exemple du genre de chose que je recherche - je ne cherchais pas particulièrement une discussion / explication de cette bizarrerie. (Ce n'est pas la même chose que l'internement de chaîne normal; en particulier, l'internement de chaîne ne se produit normalement pas lorsqu'un constructeur est appelé.) Je demandais vraiment un comportement étrange similaire.

D'autres joyaux se cachent là-bas?

Jon Skeet
la source
64
Testé sur Mono 2.0 rc; retourne vrai
Marc Gravell
10
les deux chaînes finissent par être des chaînes. Vide et il semble que le cadre ne conserve qu'une seule référence à cela
Adrian Zanescu
34
C'est une chose de conservation de la mémoire. Recherchez la documentation MSDN pour la chaîne de méthode statique.Intern. Le CLR gère un pool de chaînes. C'est pourquoi les chaînes avec un contenu identique apparaissent comme des références à la même mémoire, c'est-à-dire l'objet.
John Leidegren
12
@John: l'internement de chaînes ne se produit automatiquement que pour les littéraux . Ce n'est pas le cas ici. @DanielSwe: L'internement n'est pas requis pour rendre les chaînes immuables. Le fait que cela soit possible est un joli corollaire de l'immuabilité, mais l'internement normal ne se produit pas ici de toute façon.
Jon Skeet
3
Le détail de l'implémentation à l'origine de ce comportement est expliqué ici: blog.liranchen.com/2010/08/brain-teasing-with-strings.html
Liran

Réponses:

394

Je pense que je vous ai montré celui-ci auparavant, mais j'aime le plaisir ici - cela a pris un peu de débogage pour retrouver! (le code d'origine était évidemment plus complexe et subtil ...)

    static void Foo<T>() where T : new()
    {
        T t = new T();
        Console.WriteLine(t.ToString()); // works fine
        Console.WriteLine(t.GetHashCode()); // works fine
        Console.WriteLine(t.Equals(t)); // works fine

        // so it looks like an object and smells like an object...

        // but this throws a NullReferenceException...
        Console.WriteLine(t.GetType());
    }

Alors, quel était T ...

Réponse: tout Nullable<T>- comme int?. Toutes les méthodes sont remplacées, sauf GetType () qui ne peut pas l'être; il est donc converti (encadré) en objet (et donc en null) pour appeler object.GetType () ... qui appelle null ;-p


Mise à jour: l'intrigue s'épaissit ... Ayende Rahien a lancé un défi similaire sur son blog , mais avec where T : class, new():

private static void Main() {
    CanThisHappen<MyFunnyType>();
}

public static void CanThisHappen<T>() where T : class, new() {
    var instance = new T(); // new() on a ref-type; should be non-null, then
    Debug.Assert(instance != null, "How did we break the CLR?");
}

Mais il peut être vaincu! Utiliser la même indirection utilisée par des choses comme la télécommande; avertissement - ce qui suit est du mal pur :

class MyFunnyProxyAttribute : ProxyAttribute {
    public override MarshalByRefObject CreateInstance(Type serverType) {
        return null;
    }
}
[MyFunnyProxy]
class MyFunnyType : ContextBoundObject { }

Avec cela en place, l' new()appel est redirigé vers le proxy ( MyFunnyProxyAttribute), qui revient null. Maintenant va te laver les yeux!

Marc Gravell
la source
9
Pourquoi ne peut-on pas définir Nullable <T> .GetType ()? Le résultat ne devrait-il pas être de typeof (Nullable <T>)?
Drew Noakes
69
Drew: le problème est que GetType () n'est pas virtuel, donc il n'est pas remplacé - ce qui signifie que la valeur est encadrée pour l'appel de méthode. La boîte devient une référence nulle, d'où le NRE.
Jon Skeet
10
@A dessiné; en outre, il existe des règles de boxe spéciales pour Nullable <T>, ce qui signifie qu'un Nullable <T> vide à la case Null, et non une boîte qui contient un Nullable <T> vide (et un null un-box à un Nullable <T vide >)
Marc Gravell
29
Très cool. D'une manière peu cool. ;-)
Konrad Rudolph
6
Constructor-constraint, 10.1.5 dans la spécification de langage C # 3.0
Marc Gravell
216

Arrondissement des banquiers.

Celui-ci n'est pas tant un bug ou un dysfonctionnement du compilateur, mais certainement un cas étrange ...

Le .Net Framework utilise un schéma ou un arrondi connu sous le nom d'arrondi bancaire.

Dans l'arrondi des banquiers, les 0,5 sont arrondis au nombre pair le plus proche, donc

Math.Round(-0.5) == 0
Math.Round(0.5) == 0
Math.Round(1.5) == 2
Math.Round(2.5) == 2
etc...

Cela peut entraîner des bogues inattendus dans les calculs financiers basés sur l'arrondi arrondi plus connu.

Cela est également vrai pour Visual Basic.

Samuel Kim
la source
22
Cela me semblait étrange aussi. C'est, au moins, jusqu'à ce que j'aie autour d'une grande liste de nombres et calcule leur somme. Vous réalisez alors que si vous arrondissez simplement, vous vous retrouverez avec une différence potentiellement énorme par rapport à la somme des nombres non arrondis. Très mauvais si vous faites des calculs financiers!
Tsvetomir Tsonev
255
Dans le cas où les gens ne savaient pas, vous pouvez faire: Math.Round (x, MidpointRounding.AwayFromZero); Pour modifier le schéma d'arrondi.
ICR
26
D'après les documents: Le comportement de cette méthode suit la norme IEEE 754, section 4. Ce type d'arrondi est parfois appelé arrondi au plus proche, ou arrondi bancaire. Il minimise les erreurs d'arrondi résultant de l'arrondi constant d'une valeur médiane dans une seule direction.
ICR
8
Je me demande si c'est pourquoi je vois int(fVal + 0.5)si souvent même dans les langues qui ont une fonction d'arrondi intégrée.
Ben Blank
32
Ironie du sort, je travaillais dans une banque une fois et les autres programmeurs commencé à péter les plombs à ce sujet, pensant que l' arrondissement a été brisé dans le cadre
dan
176

Que fera cette fonction si elle est appelée as Rec(0)(pas sous le débogueur)?

static void Rec(int i)
{
    Console.WriteLine(i);
    if (i < int.MaxValue)
    {
        Rec(i + 1);
    }
}

Répondre:

  • Sur JIT 32 bits, cela devrait entraîner une exception StackOverflowException
  • Sur JIT 64 bits, il devrait imprimer tous les nombres dans int.MaxValue

En effet, le compilateur JIT 64 bits applique l'optimisation des appels de queue , contrairement au JIT 32 bits.

Malheureusement, je n'ai pas de machine 64 bits à portée de main pour vérifier cela, mais la méthode remplit toutes les conditions pour l'optimisation des appels de queue. Si quelqu'un en a un, je serais intéressé de voir si c'est vrai.

Greg Beech
la source
10
Doit être compilé en mode release, mais fonctionne très certainement sur x64 =)
Neil Williams
3
pourrait être utile de mettre à jour votre réponse lorsque VS 2010 sortira, car tous les JIT actuels effectueront alors le TCO en mode Release
ShuggyCoUk
3
Je viens d'essayer sur VS2010 Beta 1 sur WinXP 32 bits. Obtenez toujours une StackOverflowException.
squillman
130
+1 pour la StackOverflowException
calvinlough
7
Cela ++m'a totalement ébranlé. Tu ne peux pas appeler Rec(i + 1)comme une personne normale?
configurateur
111

Attribuez ceci!


C'est celui que j'aime demander lors des fêtes (c'est probablement pourquoi je ne suis plus invité):

Pouvez-vous compiler le morceau de code suivant?

    public void Foo()
    {
        this = new Teaser();
    }

Une triche simple pourrait être:

string cheat = @"
    public void Foo()
    {
        this = new Teaser();
    }
";

Mais la vraie solution est la suivante:

public struct Teaser
{
    public void Foo()
    {
        this = new Teaser();
    }
}

C'est donc un fait peu connu que les types de valeurs (structs) peuvent réaffecter leur thisvariable.

Omer Mor
la source
3
Les classes C ++ peuvent le faire aussi ... comme je l'ai découvert un peu récemment, seulement pour être crié pour avoir réellement essayé de l'utiliser pour une optimisation: p
mpen
1
J'utilisais en fait du nouveau sur place. Je voulais juste un moyen efficace de mettre à jour tous les champs :)
mpen
70
C'est aussi une triche: //this = new Teaser();:-)
AndrewJacksonZA
17
:-) Je préfère ces tricheurs dans mon code de production, que cette abomination de réaffectation ...
Omer Mor
2
Depuis CLR via C #: La raison pour laquelle ils ont fait cela est que vous pouvez appeler le constructeur sans paramètre d'une structure dans un autre constructeur. Si vous souhaitez uniquement initialiser une valeur d'une structure et que les autres valeurs soient nulles / nulles (par défaut), vous pouvez écrire public Foo(int bar){this = new Foo(); specialVar = bar;}. Ce n'est pas efficace et pas vraiment justifié ( specialVarest attribué deux fois), mais juste FYI. (C'est la raison donnée dans le livre, je ne sais pas pourquoi nous ne devrions pas le faire public Foo(int bar) : this())
kizzx2
100

Il y a quelques années, lorsque nous travaillions sur un programme de fidélité, nous avions un problème avec le nombre de points accordés aux clients. Le problème était lié à la conversion / conversion double en int.

Dans le code ci-dessous:

double d = 13.6;

int i1 = Convert.ToInt32(d);
int i2 = (int)d;

est-ce que i1 == i2 ?

Il s'avère que i1! = I2. En raison des différentes politiques d'arrondi dans Convertir et convertir l'opérateur, les valeurs réelles sont les suivantes:

i1 == 14
i2 == 13

Il est toujours préférable d'appeler Math.Ceiling () ou Math.Floor () (ou Math.Round avec MidpointRounding qui répond à nos exigences)

int i1 = Convert.ToInt32( Math.Ceiling(d) );
int i2 = (int) Math.Ceiling(d);
Jarek Kardas
la source
44
La conversion en un nombre entier ne arrondit pas, elle le coupe simplement (en fait toujours en arrondissant). Cela a donc tout son sens.
Max Schmeling
57
@Max: oui, mais pourquoi convertit-il?
Stefan Steinegger
18
@Stefan Steinegger Si tout ce qu'il a fait a été lancé, il n'y aurait aucune raison en premier lieu, n'est-ce pas? Notez également que le nom de la classe est Convertir pas Cast.
bug-a-lot
3
En VB: CInt () arrondit. Fix () tronque. M'a brûlé une fois ( blog.wassupy.com/2006/01/i-can-believe-it-not-truncating.html )
Michael Haren
74

Ils auraient dû faire de 0 un entier même en cas de surcharge de la fonction enum.

Je connaissais la raison d'être de l'équipe de base C # pour mapper le 0 à l'énumération, mais tout de même, ce n'est pas aussi orthogonal qu'il devrait l'être. Exemple de Npgsql .

Exemple de test:

namespace Craft
{
    enum Symbol { Alpha = 1, Beta = 2, Gamma = 3, Delta = 4 };


   class Mate
    {
        static void Main(string[] args)
        {

            JustTest(Symbol.Alpha); // enum
            JustTest(0); // why enum
            JustTest((int)0); // why still enum

            int i = 0;

            JustTest(Convert.ToInt32(0)); // have to use Convert.ToInt32 to convince the compiler to make the call site use the object version

            JustTest(i); // it's ok from down here and below
            JustTest(1);
            JustTest("string");
            JustTest(Guid.NewGuid());
            JustTest(new DataTable());

            Console.ReadLine();
        }

        static void JustTest(Symbol a)
        {
            Console.WriteLine("Enum");
        }

        static void JustTest(object o)
        {
            Console.WriteLine("Object");
        }
    }
}
Michael Buen
la source
18
Wow c'est un nouveau pour moi. Également bizarre comment ConverTo.ToIn32 () fonctionne, mais la conversion en (int) 0 ne fonctionne pas. Et tout autre nombre> 0 fonctionne. (Par "travaux", j'entends appeler la surcharge de l'objet.)
Lucas
Il existe une règle d'analyse de code recommandée pour appliquer les bonnes pratiques concernant ce comportement: msdn.microsoft.com/en-us/library/ms182149%28VS.80%29.aspx Ce lien contient également une belle description du fonctionnement du mappage 0 .
Chris Clark
1
@Chris Clark: j'ai essayé de mettre None = 0 sur le symbole enum. le compilateur choisit toujours enum pour 0 et même (int) 0
Michael Buen
2
OMI, ils auraient dû introduire un mot-clé nonequi peut être utilisé converti en n'importe quelle énumération, et faire de 0 toujours un int et non implicitement convertible en une énumération.
CodesInChaos
5
ConverTo.ToIn32 () fonctionne car son résultat n'est pas une constante de temps de compilation. Et seule la constante de temps de compilation 0 est convertible en énumération. Dans les versions antérieures de .net, même seul le littéral 0aurait dû être convertible en enum. Voir le blog d'Eric Lippert: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspx
CodesInChaos
67

C'est l'un des plus inhabituels que j'ai vus jusqu'à présent (à part ceux ici bien sûr!):

public class Turtle<T> where T : Turtle<T>
{
}

Il vous permet de le déclarer mais n'a aucune utilité réelle, car il vous demandera toujours d'envelopper la classe que vous remplissez au centre avec une autre tortue.

[blague] Je suppose que ce sont des tortues tout le long ... [/ blague]

RCIX
la source
34
Vous pouvez cependant créer des instances:class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Marc Gravell
24
En effet. C'est le modèle que les énumérations Java utilisent à bon escient. Je l'utilise également dans les tampons de protocole.
Jon Skeet
6
RCIX, oh oui c'est ça.
Joshua
8
J'ai beaucoup utilisé ce modèle dans des trucs génériques de fantaisie. Il permet des choses comme un clone correctement tapé ou la création d'instances de lui-même.
Lucero
20
C'est le «modèle de modèle curieusement récurrent» en.wikipedia.org/wiki/Curiously_recurring_template_pattern
porges
65

En voici un que je n'ai découvert que récemment ...

interface IFoo
{
   string Message {get;}
}
...
IFoo obj = new IFoo("abc");
Console.WriteLine(obj.Message);

Les regards au- dessus fou au premier coup d' œil, mais est en réalité juridique .Aucun, vraiment (même si je l' ai manqué un élément clé, mais ce n'est pas quelque chose comme aki « ajouter une classe appelée IFoo» ou « ajouter un usingalias à un point IFooà un classe").

Voyez si vous pouvez comprendre pourquoi, alors: Qui a dit que vous ne pouvez pas instancier une interface?

Marc Gravell
la source
1
+1 pour "utiliser l'alias" - je ne savais pas que vous pouviez faire ça !
David
pirater le compilateur pour COM Interop :-)
Ion Todirel
Enfoiré! Vous auriez pu au moins dire "dans certaines circonstances" ... Mon compilateur réfute!
MA Hanin
56

Quand un booléen n'est-il ni vrai ni faux?

Bill a découvert que vous pouvez pirater un booléen de sorte que si A est vrai et B est vrai, (A et B) est faux.

Booléens piratés

Jonathan Allen
la source
134
Quand c'est FILE_NOT_FOUND, bien sûr!
Greg
12
C'est intéressant car cela signifie, mathématiquement parlant, qu'aucune déclaration en C # n'est prouvable. Oups.
Simon Johnson
20
Un jour, j'écrirai un programme qui dépend de ce comportement, et les démons de l'enfer le plus sombre me prépareront un accueil. Bwahahahahaha!
Jeffrey L Whitledge
18
Cet exemple utilise des opérateurs au niveau du bit et non logiques. Comment est-ce surprenant?
Josh Lee
6
Eh bien, il pirate la disposition de la structure, bien sûr, vous obtiendrez des résultats étranges, ce n'est pas si surprenant ou inattendu!
Ion Todirel
47

J'arrive un peu tard pour la fête, mais j'ai trois quatre cinq:

  1. Si vous interrogez InvokeRequired sur un contrôle qui n'a pas été chargé / affiché, il dira false - et explosera dans votre visage si vous essayez de le changer à partir d'un autre thread ( la solution est de faire référence à cela. Manipulez le créateur du contrôle).

  2. Un autre qui m'a fait trébucher, c'est que, étant donné un montage avec:

    enum MyEnum
    {
        Red,
        Blue,
    }

    si vous calculez MyEnum.Red.ToString () dans un autre assembly, et entre temps, quelqu'un a recompilé votre énumération pour:

    enum MyEnum
    {
        Black,
        Red,
        Blue,
    }

    à l'exécution, vous obtiendrez "Black".

  3. J'avais un assemblage partagé avec quelques constantes pratiques. Mon prédécesseur avait laissé une charge de propriétés get-look d'apparence laide, je pensais que je me débarrasserais de l'encombrement et utiliserais simplement const public. J'ai été plus qu'un peu surpris lorsque VS les a compilés à leurs valeurs, et non à des références.

  4. Si vous implémentez une nouvelle méthode d'une interface à partir d'un autre assembly, mais que vous reconstruisez en référençant l'ancienne version de cet assembly, vous obtenez une TypeLoadException (pas d'implémentation de 'NewMethod'), même si vous l' avez implémentée (voir ici ).

  5. Dictionnaire <,>: "L'ordre dans lequel les articles sont retournés n'est pas défini". C'est horrible , car cela peut parfois vous mordre, mais travailler les autres, et si vous venez de supposer aveuglément que Dictionary va jouer bien ("pourquoi ne le ferait-il pas? Je pensais, List fait"), vous devez vraiment ayez le nez dedans avant de commencer enfin à remettre en question votre hypothèse.

Benjol
la source
6
# 2 est un exemple intéressant. Les énumérations sont des mappages de compilateur à des valeurs intégrales. Ainsi, même si vous ne leur avez pas explicitement assigné de valeurs, le compilateur l'a fait, ce qui entraîne MyEnum.Red = 0 et MyEnum.Blue = 1. Lorsque vous avez ajouté Black, vous avez redéfini la valeur 0 pour mapper de Red à Black. Je soupçonne que le problème se serait également manifesté dans d'autres usages, comme la sérialisation.
LBushkin
3
+1 pour Invoke requis. Chez nous, nous préférons assigner explicitement des valeurs à des énumérations comme Red = 1, Blue = 2 pour que de nouvelles puissent être insérées avant ou après, elles auront toujours la même valeur. Cela est particulièrement nécessaire si vous enregistrez des valeurs dans des bases de données.
TheVillageIdiot
53
Je ne suis pas d'accord que le n ° 5 est un "cas de bord". Le dictionnaire ne doit pas avoir d'ordre défini en fonction du moment où vous insérez des valeurs. Si vous souhaitez un ordre défini, utilisez une liste, ou utilisez une clé qui peut être triée d'une manière qui vous est utile, ou utilisez une structure de données entièrement différente.
Wedge
21
@Wedge, comme SortedDictionary peut-être?
Allon Guralnek
4
# 3 se produit parce que les constantes sont insérées en tant que littéraux partout où elles sont utilisées (en C #, au moins). Votre prédécesseur l'a peut-être déjà remarqué, c'est pourquoi ils ont utilisé la propriété get-only. Cependant, une variable en lecture seule (par opposition à une constante) fonctionnerait tout aussi bien.
Remoun
33

VB.NET, nullables et l'opérateur ternaire:

Dim i As Integer? = If(True, Nothing, 5)

Cela m'a pris un certain temps pour déboguer, car je m'attendais ià contenir Nothing.

Que dois-je vraiment contenir? 0.

C'est un comportement surprenant mais en fait "correct": Nothingdans VB.NET n'est pas exactement le même que nulldans CLR: Nothingpeut signifier nullou default(T)pour un type de valeur T, selon le contexte. Dans le cas ci-dessus, Ifdéduit Integercomme le type commun de, Nothinget 5donc, dans ce cas, Nothingsignifie 0.

Heinzi
la source
Assez intéressant, je n'ai pas trouvé cette réponse, j'ai donc dû créer une question . Eh bien, qui savait que la réponse se trouve dans ce fil?
GSerg
28

J'ai trouvé une deuxième valise de coin vraiment étrange qui bat la première de loin.

La méthode String.Equals (String, String, StringComparison) n'est pas sans effet secondaire.

Je travaillais sur un bloc de code qui avait ceci sur une ligne à part en haut d'une fonction:

stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);

La suppression de cette ligne entraîne un débordement de pile ailleurs dans le programme.

Le code s'est avéré installer un gestionnaire pour ce qui était essentiellement un événement BeforeAssemblyLoad et essayer de le faire

if (assemblyfilename.EndsWith("someparticular.dll", StringComparison.InvariantCultureIgnoreCase))
{
    assemblyfilename = "someparticular_modified.dll";
}

Maintenant, je ne devrais pas avoir à vous le dire. L'utilisation d'une culture qui n'a pas été utilisée auparavant dans une comparaison de chaînes provoque une charge d'assembly. InvariantCulture ne fait pas exception à cette règle.

Joshua
la source
Je suppose que "charger un assemblage" est un effet secondaire, puisque vous pouvez l'observer avec BeforeAssemblyLoad!
Jacob Krall
2
Sensationnel. C'est un coup parfait dans la jambe du mainteneur. Je suppose que l'écriture d'un gestionnaire BeforeAssemblyLoad peut conduire à de nombreuses surprises.
wigy
20

Voici un exemple de la façon dont vous pouvez créer une structure qui provoque le message d'erreur "Vous avez tenté de lire ou d'écrire de la mémoire protégée. Cela indique souvent qu'une autre mémoire est corrompue". La différence entre le succès et l'échec est très subtile.

Le test unitaire suivant illustre le problème.

Voyez si vous pouvez déterminer ce qui n'a pas fonctionné.

    [Test]
    public void Test()
    {
        var bar = new MyClass
        {
            Foo = 500
        };
        bar.Foo += 500;

        Assert.That(bar.Foo.Value.Amount, Is.EqualTo(1000));
    }

    private class MyClass
    {
        public MyStruct? Foo { get; set; }
    }

    private struct MyStruct
    {
        public decimal Amount { get; private set; }

        public MyStruct(decimal amount) : this()
        {
            Amount = amount;
        }

        public static MyStruct operator +(MyStruct x, MyStruct y)
        {
            return new MyStruct(x.Amount + y.Amount);
        }

        public static MyStruct operator +(MyStruct x, decimal y)
        {
            return new MyStruct(x.Amount + y);
        }

        public static implicit operator MyStruct(int value)
        {
            return new MyStruct(value);
        }

        public static implicit operator MyStruct(decimal value)
        {
            return new MyStruct(value);
        }
    }
cbp
la source
Ma tête me fait mal ... Pourquoi ça ne marche pas?
jasonh
2
Hm j'ai écrit ça il y a quelques mois, mais je ne me souviens pas pourquoi c'est exactement arrivé.
cbp
10
Ressemble à un bogue du compilateur; les += 500appels: ldc.i4 500(pousse 500 comme un Int32), puis call valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)- donc il traite ensuite comme un decimal(96 bits) sans aucune conversion. Si vous l'utilisez += 500M, c'est bien. Il semble simplement que le compilateur pense qu'il peut le faire dans un sens (probablement à cause de l'opérateur int implicite) et décide ensuite de le faire d'une autre manière.
Marc Gravell
1
Désolé pour le double post, voici une explication plus nuancée. J'ajouterai ceci, j'ai été mordu par ça et ça ça craint, même si je comprends pourquoi ça arrive. Pour moi, c'est une limitation malheureuse du struct / valuetype. bytes.com/topic/net/answers/…
Bennett Dill
2
@Ben obtient des erreurs de compilation ou la modification n'affectant pas la structure d'origine est très bien. Une violation d'accès est une bête bien différente. Le runtime ne doit jamais le lancer si vous écrivez simplement du code managé pur et sûr.
CodesInChaos
18

C # prend en charge les conversions entre les tableaux et les listes tant que les tableaux ne sont pas multidimensionnels et qu'il existe une relation d'héritage entre les types et que les types sont des types de référence

object[] oArray = new string[] { "one", "two", "three" };
string[] sArray = (string[])oArray;

// Also works for IList (and IEnumerable, ICollection)
IList<string> sList = (IList<string>)oArray;
IList<object> oList = new string[] { "one", "two", "three" };

Notez que cela ne fonctionne pas:

object[] oArray2 = new int[] { 1, 2, 3 }; // Error: Cannot implicitly convert type 'int[]' to 'object[]'
int[] iArray = (int[])oArray2;            // Error: Cannot convert type 'object[]' to 'int[]'
Peter van der Heijden
la source
11
L'exemple IList <T> n'est qu'un transtypage, car la chaîne [] implémente déjà ICloneable, IList, ICollection, IEnumerable, IList <string>, ICollection <string> et IEnumerable <string>.
Lucas
15

C'est le plus étrange que j'ai rencontré par accident:

public class DummyObject
{
    public override string ToString()
    {
        return null;
    }
}

Utilisé comme suit:

DummyObject obj = new DummyObject();
Console.WriteLine("The text: " + obj.GetType() + " is " + obj);

Jettera un NullReferenceException. Il s'avère que les ajouts multiples sont compilés par le compilateur C # pour un appel à String.Concat(object[]). Avant .NET 4, il y a un bogue dans cette surcharge de Concat où l'objet est vérifié pour null, mais pas le résultat de ToString ():

object obj2 = args[i];
string text = (obj2 != null) ? obj2.ToString() : string.Empty;
// if obj2 is non-null, but obj2.ToString() returns null, then text==null
int length = text.Length;

Il s'agit d'un bogue d'ECMA-334 §14.7.4:

L'opérateur binaire + effectue la concaténation de chaînes lorsqu'un ou les deux opérandes sont de type string. Si un opérande de concaténation de chaînes est null, une chaîne vide est substituée. Sinon, tout opérande non chaîne est converti en sa représentation chaîne en appelant la ToStringméthode virtuelle héritée de type object. Si ToStringretourne null, une chaîne vide est substituée.

Sam Harwell
la source
3
Hmm, mais je peux imaginer que ce défaut .ToStringne devrait jamais retourner null, mais string.Empty. Néanmoins et erreur dans le cadre.
Dykam
12

Intéressant - quand j'ai regardé pour la première fois, j'ai supposé que c'était quelque chose que le compilateur C # vérifiait, mais même si vous émettez l'IL directement pour supprimer toute chance d'interférence, cela se produit toujours, ce qui signifie que c'est vraiment le newobjcode op qui fait le vérification.

var method = new DynamicMethod("Test", null, null);
var il = method.GetILGenerator();

il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Newarr, typeof(char));
il.Emit(OpCodes.Newobj, typeof(string).GetConstructor(new[] { typeof(char[]) }));

il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Newarr, typeof(char));
il.Emit(OpCodes.Newobj, typeof(string).GetConstructor(new[] { typeof(char[]) }));

il.Emit(OpCodes.Call, typeof(object).GetMethod("ReferenceEquals"));
il.Emit(OpCodes.Box, typeof(bool));
il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] { typeof(object) }));

il.Emit(OpCodes.Ret);

method.Invoke(null, null);

Cela équivaut également à truesi vous vérifiez string.Emptyce qui signifie que cet op-code doit avoir un comportement spécial pour interner des chaînes vides.

Greg Beech
la source
ne pas être un aleck intelligent ou quoi que ce soit, mais avez-vous entendu parler du réflecteur ? c'est assez pratique dans ce genre de cas;
RCIX
3
Vous n'êtes pas intelligent; vous manquez le point - je voulais générer une IL spécifique pour ce cas. Et de toute façon, étant donné que Reflection.Emit est trivial pour ce type de scénario, c'est probablement aussi rapide que d'écrire un programme en C # puis d'ouvrir le réflecteur, de trouver le binaire, de trouver la méthode, etc ... Et je n'ai même pas à quitter l'IDE pour le faire.
Greg Beech
10
Public Class Item
   Public ID As Guid
   Public Text As String

   Public Sub New(ByVal id As Guid, ByVal name As String)
      Me.ID = id
      Me.Text = name
   End Sub
End Class

Public Sub Load(sender As Object, e As EventArgs) Handles Me.Load
   Dim box As New ComboBox
   Me.Controls.Add(box)          'Sorry I forgot this line the first time.'
   Dim h As IntPtr = box.Handle  'Im not sure you need this but you might.'
   Try
      box.Items.Add(New Item(Guid.Empty, Nothing))
   Catch ex As Exception
      MsgBox(ex.ToString())
   End Try
End Sub

La sortie est "Tentative de lecture de la mémoire protégée. Ceci indique que l'autre mémoire est corrompue."

Joshua
la source
1
Intéressant! Cela ressemble à un bogue du compilateur, cependant; J'ai porté en C # et cela fonctionne très bien. Cela dit, il y a beaucoup de problèmes avec les exceptions lancées dans Load, et cela se comporte différemment avec / sans débogueur - vous pouvez intercepter avec un débogueur, mais pas sans (dans certains cas).
Marc Gravell
Désolé, j'ai oublié, vous devez ajouter la zone de liste déroulante au formulaire avant de le faire.
Joshua
Est-ce à voir avec l'initialisation du dialogue en utilisant un SEH comme une sorte d'horrible mécanisme de communication interne? Je me souviens vaguement de quelque chose comme ça dans Win32.
Daniel Earwicker
1
C'est le même problème cbp ci-dessus. Le type de valeur retourné est une copie, par conséquent, toute référence à des propriétés découlant de ladite copie est dirigée vers un terrain de bits ... bytes.com/topic/net/answers/…
Bennett Dill
1
Nan. Il n'y a pas de structure ici. Je l'ai en fait débogué. Il ajoute un NULL à la collection d'éléments de liste de la zone de liste déroulante native, provoquant un crash retardé.
Joshua
10

PropertyInfo.SetValue () peut affecter des entiers à des énumérations, des entiers à des octets nullables, des énumérations à des énumérations nullables, mais pas des entiers à des énumérations nullables.

enumProperty.SetValue(obj, 1, null); //works
nullableIntProperty.SetValue(obj, 1, null); //works
nullableEnumProperty.SetValue(obj, MyEnum.Foo, null); //works
nullableEnumProperty.SetValue(obj, 1, null); // throws an exception !!!

Description complète ici

Anders Ivner
la source
10

Et si vous avez une classe générique qui a des méthodes qui peuvent être rendues ambiguës en fonction des arguments de type? J'ai rencontré cette situation récemment en écrivant un dictionnaire bidirectionnel. Je voulais écrire des Get()méthodes symétriques qui retourneraient l'opposé de tout argument passé. Quelque chose comme ça:

class TwoWayRelationship<T1, T2>
{
    public T2 Get(T1 key) { /* ... */ }
    public T1 Get(T2 key) { /* ... */ }
}

Tout va bien si vous créez une instance où T1et T2sont de types différents:

var r1 = new TwoWayRelationship<int, string>();
r1.Get(1);
r1.Get("a");

Mais si T1et T2sont les mêmes (et probablement si l'un était une sous-classe d'un autre), c'est une erreur de compilation:

var r2 = new TwoWayRelationship<int, int>();
r2.Get(1);  // "The call is ambiguous..."

Fait intéressant, toutes les autres méthodes dans le deuxième cas sont encore utilisables; ce ne sont que des appels à la méthode désormais ambiguë qui provoque une erreur de compilation. Cas intéressant, quoique peu probable et obscur.

tclem
la source
Les opposants à la surcharge de méthode vont adorer celui-ci ^^.
Christian Klauser
1
Je ne sais pas, cela a un sens total pour moi.
Scott Whitlock
10

C # Accessibilité Puzzler


La classe dérivée suivante accède à un champ privé à partir de sa classe de base et le compilateur regarde silencieusement de l'autre côté:

public class Derived : Base
{
    public int BrokenAccess()
    {
        return base.m_basePrivateField;
    }
}

Le domaine est en effet privé:

private int m_basePrivateField = 0;

Voulez-vous deviner comment nous pouvons compiler un tel code?

.

.

.

.

.

.

.

Répondre


L'astuce consiste à déclarer en Derivedtant que classe interne de Base:

public class Base
{
    private int m_basePrivateField = 0;

    public class Derived : Base
    {
        public int BrokenAccess()
        {
            return base.m_basePrivateField;
        }
    }
}

Les classes internes ont un accès complet aux membres de la classe externe. Dans ce cas, la classe interne dérive également de la classe externe. Cela nous permet de "casser" l'encapsulation des membres privés.

Omer Mor
la source
C'est en fait bien documenté; msdn.microsoft.com/en-us/library/ms173120%28VS.80%29.aspx . Il peut parfois être utile, surtout si la classe externe est statique.
Oui - bien sûr, c'est documenté. Cependant, très peu de gens ont résolu ce casse-tête, alors j'ai pensé que c'était un morceau de trivia cool.
Omer Mor
2
On dirait que vous auriez une très forte possibilité de débordement de pile en faisant hériter une classe interne de son propriétaire ...
Jamie Treworgy
Un autre cas similaire (et parfaitement correct) est qu'un objet peut accéder à un membre privé d'un autre objet du même type:class A { private int _i; public void foo(A other) { int res = other._i; } }
Olivier Jacot-Descombes
10

Je viens de trouver une jolie petite chose aujourd'hui:

public class Base
{
   public virtual void Initialize(dynamic stuff) { 
   //...
   }
}
public class Derived:Base
{
   public override void Initialize(dynamic stuff) {
   base.Initialize(stuff);
   //...
   }
}

Cela génère une erreur de compilation.

L'appel à la méthode 'Initialize' doit être distribué dynamiquement, mais ne peut pas l'être car il fait partie d'une expression d'accès de base. Pensez à transtyper les arguments dynamiques ou à supprimer l'accès de base.

Si j'écris base.Initialize (en tant qu'objet); cela fonctionne parfaitement, mais cela semble être un "mot magique" ici, car il fait exactement la même chose, tout est toujours reçu comme dynamique ...

TDaver
la source
8

Dans une API que nous utilisons, les méthodes qui renvoient un objet de domaine peuvent renvoyer un "objet nul" spécial. Dans l'implémentation de ceci, l'opérateur de comparaison et la Equals()méthode sont remplacés pour retourner trues'ils sont comparés ànull .

Un utilisateur de cette API peut donc avoir du code comme celui-ci:

return test != null ? test : GetDefault();

ou peut-être un peu plus verbeux, comme ceci:

if (test == null)
    return GetDefault();
return test;

GetDefault()est une méthode renvoyant une valeur par défaut que nous voulons utiliser à la place null. La surprise m'a frappé lorsque j'utilisais ReSharper et que je vous recommande de réécrire l'une ou l'autre de ces options comme suit:

return test ?? GetDefault();

Si l'objet de test est un objet nul renvoyé par l'API au lieu d'un objet approprié null, le comportement du code a maintenant changé, car l'opérateur de coalescence null vérifie réellement null, sans exécuter operator=ou Equals().

Tor Livar
la source
1
pas vraiment une affaire de coin ac #, mais cher seigneur qui a pensé à ça?!?
Ray Booysen
Ce code n'est-il pas uniquement en utilisant des types nullables? D'où ReSharper recommandant le "??" utilisation. Comme Ray l'a dit, je n'aurais pas pensé que c'était une affaire de coin; ou ai-je tort?
Tony
1
Oui, les types sont nullables - et il y a un NullObject en plus. Si c'est un cas d'angle, je ne sais pas, mais au moins c'est un cas où 'if (a! = Null) retourne a; retourner b; ' n'est pas la même chose que «retourner un ?? b '. Je suis absolument d'accord que c'est un problème avec la conception du framework / API - surcharger == null pour retourner vrai sur un objet n'est certainement pas une bonne idée!
Tor Livar
8

Considérez ce cas étrange:

public interface MyInterface {
  void Method();
}
public class Base {
  public void Method() { }
}
public class Derived : Base, MyInterface { }

Si Baseet Derivedsont déclarés dans le même assembly, le compilateur rendra Base::Methodvirtuel et scellé (dans le CIL), même siBase n'implémente pas l'interface.

Si Baseet Derivedsont dans des assemblys différents, lors de la compilation de l' Derivedassembly, le compilateur ne changera pas l'autre assembly, donc il introduira un membre dans Derivedce sera une implémentation explicite car MyInterface::Methodil déléguera simplement l'appel à Base::Method.

Le compilateur doit le faire pour prendre en charge la répartition polymorphe en ce qui concerne l'interface, c'est-à-dire qu'il doit rendre cette méthode virtuelle.

Jordão
la source
Cela semble en effet étrange. Devra enquêter plus tard :)
Jon Skeet
@ Jon Skeet: J'ai trouvé cela en recherchant des stratégies de mise en œuvre pour les rôles en C # . Ce serait génial d'avoir votre avis à ce sujet!
Jordão
7

Ce qui suit pourrait être des connaissances générales qui me manquaient, mais hein. Il y a quelque temps, nous avions un cas de bogue qui comprenait des propriétés virtuelles. Résumant un peu le contexte, considérez le code suivant et appliquez le point d'arrêt à la zone spécifiée:

class Program
{
    static void Main(string[] args)
    {
        Derived d = new Derived();
        d.Property = "AWESOME";
    }
}

class Base
{
    string _baseProp;
    public virtual string Property 
    { 
        get 
        {
            return "BASE_" + _baseProp;
        }
        set
        {
            _baseProp = value;
            //do work with the base property which might 
            //not be exposed to derived types
            //here
            Console.Out.WriteLine("_baseProp is BASE_" + value.ToString());
        }
    }
}

class Derived : Base
{
    string _prop;
    public override string Property 
    {
        get { return _prop; }
        set 
        { 
            _prop = value; 
            base.Property = value;
        } //<- put a breakpoint here then mouse over BaseProperty, 
          //   and then mouse over the base.Property call inside it.
    }

    public string BaseProperty { get { return base.Property; } private set { } }
}

Dans le Derivedcontexte de l' objet, vous pouvez obtenir le même comportement lors de l'ajout en base.Propertytant que montre ou de la saisie base.Propertydans la montre rapide.

Cela m'a pris un peu de temps pour réaliser ce qui se passait. Au final, j'ai été éclairé par la Quickwatch. Lorsque vous accédez à la Quickwatch et explorez l' Derivedobjet d (ou à partir du contexte de l'objet this) et sélectionnez le champ base, le champ d'édition en haut de la Quickwatch affiche la distribution suivante:

((TestProject1.Base)(d))

Ce qui signifie que si la base est remplacée en tant que telle, l'appel serait

public string BaseProperty { get { return ((TestProject1.Base)(d)).Property; } private set { } }

pour les montres, Quickwatch et les info-bulles de débogage de la souris, et il serait alors logique qu'il s'affiche "AWESOME"au lieu de "BASE_AWESOME"considérer le polymorphisme. Je ne sais toujours pas pourquoi cela le transformerait en distribution, une hypothèse est que cela callpourrait ne pas être disponible dans le contexte de ces modules, et seulementcallvirt .

Quoi qu'il en soit, cela ne change évidemment rien en termes de fonctionnalités, cela Derived.BasePropertyreviendra toujours vraiment "BASE_AWESOME", et donc ce n'était pas la racine de notre bogue au travail, simplement un composant déroutant. J'ai toutefois trouvé intéressant de voir comment cela pouvait induire les développeurs en erreur qui ne le Basesauraient pas lors de leurs sessions de débogage, en particulier s'il n'est pas exposé dans votre projet mais plutôt référencé comme une DLL tierce, ce qui fait que les développeurs disent simplement:

"Oi, wait..what? Omg que DLL est comme, .. faisant quelque chose de drôle"

Dynami Le Savard
la source
Ce n'est rien de spécial, c'est juste la façon dont les substitutions fonctionnent.
configurateur
7

Celui-ci est assez difficile à surpasser. Je suis tombé dessus alors que j'essayais de construire une implémentation RealProxy qui prend vraiment en charge Begin / EndInvoke (merci MS pour avoir rendu cela impossible à faire sans horribles hacks). Cet exemple est essentiellement un bogue dans le CLR, le chemin de code non géré pour BeginInvoke ne valide pas que le message de retour de RealProxy.PrivateInvoke (et mon remplacement Invoke) renvoie une instance d'un IAsyncResult. Une fois qu'il est retourné, le CLR devient incroyablement confus et perd toute idée de ce qui se passe, comme le montrent les tests en bas.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Proxies;
using System.Reflection;
using System.Runtime.Remoting.Messaging;

namespace BrokenProxy
{
    class NotAnIAsyncResult
    {
        public string SomeProperty { get; set; }
    }

    class BrokenProxy : RealProxy
    {
        private void HackFlags()
        {
            var flagsField = typeof(RealProxy).GetField("_flags", BindingFlags.NonPublic | BindingFlags.Instance);
            int val = (int)flagsField.GetValue(this);
            val |= 1; // 1 = RemotingProxy, check out System.Runtime.Remoting.Proxies.RealProxyFlags
            flagsField.SetValue(this, val);
        }

        public BrokenProxy(Type t)
            : base(t)
        {
            HackFlags();
        }

        public override IMessage Invoke(IMessage msg)
        {
            var naiar = new NotAnIAsyncResult();
            naiar.SomeProperty = "o noes";
            return new ReturnMessage(naiar, null, 0, null, (IMethodCallMessage)msg);
        }
    }

    interface IRandomInterface
    {
        int DoSomething();
    }

    class Program
    {
        static void Main(string[] args)
        {
            BrokenProxy bp = new BrokenProxy(typeof(IRandomInterface));
            var instance = (IRandomInterface)bp.GetTransparentProxy();
            Func<int> doSomethingDelegate = instance.DoSomething;
            IAsyncResult notAnIAsyncResult = doSomethingDelegate.BeginInvoke(null, null);

            var interfaces = notAnIAsyncResult.GetType().GetInterfaces();
            Console.WriteLine(!interfaces.Any() ? "No interfaces on notAnIAsyncResult" : "Interfaces");
            Console.WriteLine(notAnIAsyncResult is IAsyncResult); // Should be false, is it?!
            Console.WriteLine(((NotAnIAsyncResult)notAnIAsyncResult).SomeProperty);
            Console.WriteLine(((IAsyncResult)notAnIAsyncResult).IsCompleted); // No way this works.
        }
    }
}

Production:

No interfaces on notAnIAsyncResult
True
o noes

Unhandled Exception: System.EntryPointNotFoundException: Entry point was not found.
   at System.IAsyncResult.get_IsCompleted()
   at BrokenProxy.Program.Main(String[] args) 
Steve
la source
6

Je ne sais pas si vous diriez que c'est une bizarrerie de Windows Vista / 7 ou une bizarrerie .Net mais cela m'a fait me gratter la tête pendant un certain temps.

string filename = @"c:\program files\my folder\test.txt";
System.IO.File.WriteAllText(filename, "Hello world.");
bool exists = System.IO.File.Exists(filename); // returns true;
string text = System.IO.File.ReadAllText(filename); // Returns "Hello world."

Sous Windows Vista / 7, le fichier sera réellement écrit dans C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt

Spencer Ruport
la source
2
Il s'agit en effet d'une amélioration de la sécurité de Vista (pas 7, afaik). Mais ce qui est cool, c'est que vous pouvez lire et ouvrir le fichier avec le chemin des fichiers programme, alors que si vous y regardez avec l'explorateur, il n'y a rien. Celui-ci m'a pris presque une journée de travail @ un client avant de finalement le découvrir.
Henri
C'est certainement une chose Windows 7 aussi. C'est ce que j'utilisais quand je suis tombé dessus. Je comprends le raisonnement derrière cela, mais c'était toujours frustrant de comprendre.
Spencer Ruport
Dans Vista / Win 7 (WinXP techniquement aussi), les applications doivent écrire dans un dossier AppData dans le dossier des utilisateurs, en tant que données utilisateur techniquement. Les applications ne devraient jamais écrire dans les fichiers de programme / windows / system32 / etc, sauf si elles ont des privilèges d'administrateur, et ces privilèges ne devraient être là que pour dire mettre à niveau le programme / le désinstaller / installer une nouvelle fonctionnalité. MAIS! N'écrivez toujours pas dans system32 / windows / etc :) Si vous avez exécuté ce code ci-dessus en tant qu'administrateur (clic droit> exécuter en tant qu'administrateur), il devrait théoriquement écrire dans le dossier d'application des fichiers programme.
Steve Syfuhs
Sonne comme la virtualisation - crispybit.spaces.live.com/blog/cns!1B71C2122AD43308!134.entry
Colin Newell
6

Avez-vous déjà pensé que le compilateur C # pourrait générer un CIL non valide? Exécutez ceci et vous obtiendrez TypeLoadException:

interface I<T> {
  T M(T p);
}
abstract class A<T> : I<T> {
  public abstract T M(T p);
}
abstract class B<T> : A<T>, I<int> {
  public override T M(T p) { return p; }
  public int M(int p) { return p * 2; }
}
class C : B<int> { }

class Program {
  static void Main(string[] args) {
    Console.WriteLine(new C().M(42));
  }
}

Je ne sais pas cependant comment cela se passe dans le compilateur C # 4.0.

EDIT : c'est la sortie de mon système:

C:\Temp>type Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1 {

  interface I<T> {
    T M(T p);
  }
  abstract class A<T> : I<T> {
    public abstract T M(T p);
  }
  abstract class B<T> : A<T>, I<int> {
    public override T M(T p) { return p; }
    public int M(int p) { return p * 2; }
  }
  class C : B<int> { }

  class Program {
    static void Main(string[] args) {
      Console.WriteLine(new C().M(11));
    }
  }

}
C:\Temp>csc Program.cs
Microsoft (R) Visual C# 2008 Compiler version 3.5.30729.1
for Microsoft (R) .NET Framework version 3.5
Copyright (C) Microsoft Corporation. All rights reserved.


C:\Temp>Program

Unhandled Exception: System.TypeLoadException: Could not load type 'ConsoleAppli
cation1.C' from assembly 'Program, Version=0.0.0.0, Culture=neutral, PublicKeyTo
ken=null'.
   at ConsoleApplication1.Program.Main(String[] args)

C:\Temp>peverify Program.exe

Microsoft (R) .NET Framework PE Verifier.  Version  3.5.30729.1
Copyright (c) Microsoft Corporation.  All rights reserved.

[token  0x02000005] Type load failed.
[IL]: Error: [C:\Temp\Program.exe : ConsoleApplication1.Program::Main][offset 0x
00000001] Unable to resolve token.
2 Error(s) Verifying Program.exe

C:\Temp>ver

Microsoft Windows XP [Version 5.1.2600]
Jordão
la source
Fonctionne pour moi avec le compilateur C # 3.5 et le compilateur C # 4 ...
Jon Skeet
Dans mon système, cela ne fonctionne pas. Je vais coller la sortie dans la question.
Jordão
Cela a échoué pour moi dans .NET 3.5 (je n'ai pas le temps de tester 4.0). Et je peux reproduire le problème avec le code VB.NET.
Mark Hurd
3

Il y a quelque chose de vraiment excitant dans C #, la façon dont il gère les fermetures.

Au lieu de copier les valeurs de la variable de pile dans la variable libre de fermeture, il fait que la magie du préprocesseur enveloppe toutes les occurrences de la variable dans un objet et la déplace ainsi hors de la pile - directement dans le tas! :)

Je suppose que cela rend le langage C # encore plus fonctionnel (ou lambda-complet hein)) que ML lui-même (qui utilise la copie de valeur de pile AFAIK). F # a également cette fonctionnalité, comme C #.

Cela m'apporte beaucoup de plaisir, merci les gars MS!

Ce n'est pas un cas étrange ou un coin ... mais quelque chose de vraiment inattendu à partir d'un langage de machine virtuelle basé sur la pile :)

Bubba88
la source
3

D'une question que j'ai posée il n'y a pas longtemps:

L'opérateur conditionnel ne peut pas transtyper implicitement?

Donné:

Bool aBoolValue;

aBoolValueest attribué Vrai ou Faux;

Les éléments suivants ne seront pas compilés:

Byte aByteValue = aBoolValue ? 1 : 0;

Mais cela:

Int anIntValue = aBoolValue ? 1 : 0;

La réponse fournie est également très bonne.

MPelletier
la source
bien que je sois ve not test it Isûr que cela fonctionnera: Byte aByteValue = aBoolValue? (Octet) 1: (octet) 0; Ou: Byte aByteValue = (Byte) (aBoolValue? 1: 0);
Alex Pacurar
2
Oui, Alex, ça marcherait. La clé réside dans le casting implicite. 1 : 0seul sera implicitement converti en entier, pas en octet.
MPelletier
2

La portée en c # est parfois vraiment bizarre. Permettez-moi de vous donner un exemple:

if (true)
{
   OleDbCommand command = SQLServer.CreateCommand();
}

OleDbCommand command = SQLServer.CreateCommand();

Cela ne parvient pas à compiler, car la commande est redéclarée? Il y a des conjectures intéressées quant à pourquoi cela fonctionne de cette façon dans ce fil sur stackoverflow et dans mon blog .

Anders Rune Jensen
la source
34
Je ne considère pas cela comme particulièrement bizarre. Ce que vous appelez «code parfaitement correct» dans votre blog est parfaitement incorrect selon les spécifications de la langue. Il peut être correct dans un langage imaginaire que vous aimeriez que C # soit, mais la spécification du langage est assez claire qu'en C #, il n'est pas valide.
Jon Skeet
7
Et bien c'est valable en C / C ++. Et comme c'est C # j'aurais aimé que ça marche encore. Ce qui me dérange le plus, c'est qu'il n'y a aucune raison pour que le compilateur fasse cela. Ce n'est pas difficile de faire un cadrage imbriqué. Je suppose que tout se résume à l'élément de moindre surprise. Cela signifie que la spécification peut dire ceci et cela, mais cela ne m'aide pas beaucoup si c'est complètement illogique qu'elle se comporte de cette façon.
Anders Rune Jensen le
6
C #! = C / C ++. Souhaitez-vous également utiliser cout << "Hello World!" << endl; au lieu de Console.WriteLine ("Hello World!") ;? De plus, ce n'est pas illogique, il suffit de lire les spécifications.
Kredns
9
Je parle de règles de cadrage qui font partie du cœur du langage. Vous parlez de la bibliothèque standard. Mais il est maintenant clair pour moi que je devrais simplement lire la minuscule spécification du langage c # avant de commencer à programmer dedans.
Anders Rune Jensen
6
Eric Lippert a en fait récemment publié les raisons pour lesquelles C # est conçu comme ça: blogs.msdn.com/ericlippert/archive/2009/11/02/… . Le résumé est parce qu'il est moins probable que les changements auront des conséquences imprévues.
Helephant