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?
Réponses:
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 ...)
Alors, quel était T ...
Réponse: tout
Nullable<T>
- commeint?
. 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 ;-pMise à jour: l'intrigue s'épaissit ... Ayende Rahien a lancé un défi similaire sur son blog , mais avec
where T : class, new()
: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 :
Avec cela en place, l'
new()
appel est redirigé vers le proxy (MyFunnyProxyAttribute
), qui revientnull
. Maintenant va te laver les yeux!la source
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
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.
la source
int(fVal + 0.5)
si souvent même dans les langues qui ont une fonction d'arrondi intégrée.Que fera cette fonction si elle est appelée as
Rec(0)
(pas sous le débogueur)?Répondre:
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.
la source
++
m'a totalement ébranlé. Tu ne peux pas appelerRec(i + 1)
comme une personne normale?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?
Une triche simple pourrait être:
Mais la vraie solution est la suivante:
C'est donc un fait peu connu que les types de valeurs (structs) peuvent réaffecter leur
this
variable.la source
//this = new Teaser();
:-)public Foo(int bar){this = new Foo(); specialVar = bar;}
. Ce n'est pas efficace et pas vraiment justifié (specialVar
est 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 fairepublic Foo(int bar) : this()
)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:
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:
Il est toujours préférable d'appeler Math.Ceiling () ou Math.Floor () (ou Math.Round avec MidpointRounding qui répond à nos exigences)
la source
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:
la source
none
qui 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.0
aurait dû être convertible en enum. Voir le blog d'Eric Lippert: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspxC'est l'un des plus inhabituels que j'ai vus jusqu'à présent (à part ceux ici bien sûr!):
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]
la source
class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
En voici un que je n'ai découvert que récemment ...
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 unusing
alias à un pointIFoo
à un classe").Voyez si vous pouvez comprendre pourquoi, alors: Qui a dit que vous ne pouvez pas instancier une interface?
la source
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
la source
J'arrive un peu tard pour la fête, mais j'ai
troisquatrecinq: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).
Un autre qui m'a fait trébucher, c'est que, étant donné un montage avec:
si vous calculez MyEnum.Red.ToString () dans un autre assembly, et entre temps, quelqu'un a recompilé votre énumération pour:
à l'exécution, vous obtiendrez "Black".
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.
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 ).
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.
la source
VB.NET, nullables et l'opérateur ternaire:
Cela m'a pris un certain temps pour déboguer, car je m'attendais
i
à contenirNothing
.Que dois-je vraiment contenir?
0
.C'est un comportement surprenant mais en fait "correct":
Nothing
dans VB.NET n'est pas exactement le même quenull
dans CLR:Nothing
peut signifiernull
oudefault(T)
pour un type de valeurT
, selon le contexte. Dans le cas ci-dessus,If
déduitInteger
comme le type commun de,Nothing
et5
donc, dans ce cas,Nothing
signifie0
.la source
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:
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
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.
la source
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é.
la source
+= 500
appels:ldc.i4 500
(pousse 500 comme un Int32), puiscall valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)
- donc il traite ensuite comme undecimal
(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.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
Notez que cela ne fonctionne pas:
la source
C'est le plus étrange que j'ai rencontré par accident:
Utilisé comme suit:
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 ():Il s'agit d'un bogue d'ECMA-334 §14.7.4:
la source
.ToString
ne devrait jamais retourner null, mais string.Empty. Néanmoins et erreur dans le cadre.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
newobj
code op qui fait le vérification.Cela équivaut également à
true
si vous vérifiezstring.Empty
ce qui signifie que cet op-code doit avoir un comportement spécial pour interner des chaînes vides.la source
La sortie est "Tentative de lecture de la mémoire protégée. Ceci indique que l'autre mémoire est corrompue."
la source
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.
Description complète ici
la source
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:Tout va bien si vous créez une instance où
T1
etT2
sont de types différents:Mais si
T1
etT2
sont les mêmes (et probablement si l'un était une sous-classe d'un autre), c'est une erreur de compilation: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.
la source
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é:
Le domaine est en effet privé:
Voulez-vous deviner comment nous pouvons compiler un tel code?
.
.
.
.
.
.
.
Répondre
L'astuce consiste à déclarer en
Derived
tant que classe interne deBase
: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.
la source
class A { private int _i; public void foo(A other) { int res = other._i; } }
Je viens de trouver une jolie petite chose aujourd'hui:
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 ...
la source
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 retournertrue
s'ils sont comparés ànull
.Un utilisateur de cette API peut donc avoir du code comme celui-ci:
ou peut-être un peu plus verbeux, comme ceci:
où
GetDefault()
est une méthode renvoyant une valeur par défaut que nous voulons utiliser à la placenull
. 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: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éellementnull
, sans exécuteroperator=
ouEquals()
.la source
Considérez ce cas étrange:
Si
Base
etDerived
sont déclarés dans le même assembly, le compilateur rendraBase::Method
virtuel et scellé (dans le CIL), même siBase
n'implémente pas l'interface.Si
Base
etDerived
sont dans des assemblys différents, lors de la compilation de l'Derived
assembly, le compilateur ne changera pas l'autre assembly, donc il introduira un membre dansDerived
ce sera une implémentation explicite carMyInterface::Method
il 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.
la source
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:
Dans le
Derived
contexte de l' objet, vous pouvez obtenir le même comportement lors de l'ajout enbase.Property
tant que montre ou de la saisiebase.Property
dans 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'
Derived
objet d (ou à partir du contexte de l'objetthis
) et sélectionnez le champbase
, le champ d'édition en haut de la Quickwatch affiche la distribution suivante:Ce qui signifie que si la base est remplacée en tant que telle, l'appel serait
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 celacall
pourrait 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.BaseProperty
reviendra 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 leBase
sauraient 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:la source
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.
Production:
la source
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.
Sous Windows Vista / 7, le fichier sera réellement écrit dans
C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt
la source
Avez-vous déjà pensé que le compilateur C # pourrait générer un CIL non valide? Exécutez ceci et vous obtiendrez
TypeLoadException
:Je ne sais pas cependant comment cela se passe dans le compilateur C # 4.0.
EDIT : c'est la sortie de mon système:
la source
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 :)
la source
D'une question que j'ai posée il n'y a pas longtemps:
L'opérateur conditionnel ne peut pas transtyper implicitement?
Donné:
Où
aBoolValue
est attribué Vrai ou Faux;Les éléments suivants ne seront pas compilés:
Mais cela:
La réponse fournie est également très bonne.
la source
ve not test it I
sûr que cela fonctionnera: Byte aByteValue = aBoolValue? (Octet) 1: (octet) 0; Ou: Byte aByteValue = (Byte) (aBoolValue? 1: 0);1 : 0
seul sera implicitement converti en entier, pas en octet.La portée en c # est parfois vraiment bizarre. Permettez-moi de vous donner un exemple:
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 .
la source