Ich sammle ein paar Eckfälle und Denksportaufgaben und würde immer gerne mehr hören. Die Seite behandelt nur wirklich C # -Sprachen, aber ich finde auch .NET-Kernsachen interessant. Zum Beispiel hier eine, die nicht auf der Seite ist, die ich aber unglaublich finde:
string x = new string(new char[0]);
string y = new string(new char[0]);
Console.WriteLine(object.ReferenceEquals(x, y));
Ich würde erwarten, dass False gedruckt wird - schließlich erstellt "new" (mit einem Referenztyp) immer ein neues Objekt, nicht wahr? Die Spezifikationen für C # und die CLI geben an, dass dies der Fall sein sollte. Nun, nicht in diesem speziellen Fall. Es gibt True aus und hat es auf jeder Version des Frameworks ausgeführt, mit dem ich es getestet habe. (Ich habe es bei Mono zugegebenermaßen nicht ausprobiert ...)
Um ganz klar zu sein, dies ist nur ein Beispiel für die Art von Dingen, nach denen ich suche - ich habe nicht besonders nach Diskussionen / Erklärungen für diese Kuriosität gesucht. (Es ist nicht dasselbe wie normales String-Interning. Insbesondere findet String-Interning normalerweise nicht statt, wenn ein Konstruktor aufgerufen wird.) Ich habe wirklich nach einem ähnlichen merkwürdigen Verhalten gefragt.
Gibt es noch andere Edelsteine, die da draußen lauern?
Antworten:
Ich glaube, ich habe dir das schon einmal gezeigt, aber ich mag den Spaß hier - es hat einiges an Debugging gekostet, um es aufzuspüren! (Der ursprüngliche Code war offensichtlich komplexer und subtiler ...)
Also, was war T ...
Antwort: beliebig
Nullable<T>
- wie zint?
. Alle Methoden werden überschrieben, mit Ausnahme von GetType (), das nicht sein kann. Daher wird es in ein Objekt (und damit in null) umgewandelt (boxed), um object.GetType () ... aufzurufen, das null ;-p aufruftUpdate: Die Handlung wird dicker ... Ayende Rahien warf eine ähnliche Herausforderung auf seinen Blog , aber mit einem
where T : class, new()
:Aber es kann besiegt werden! Verwenden der gleichen Indirektion, die auch für Remoting verwendet wird. Warnung - das Folgende ist rein böse :
Wenn dies
new()
geschehen ist , wird der Aufruf an den Proxy (MyFunnyProxyAttribute
) umgeleitet , der zurückkehrtnull
. Jetzt geh und wasche deine Augen!quelle
Bankrundung.
Dies ist nicht so sehr ein Compiler-Fehler oder eine Fehlfunktion, aber sicherlich ein seltsamer Eckfall ...
Das .Net-Framework verwendet ein Schema oder eine Rundung, die als Banker-Rundung bezeichnet wird.
Bei der Bankrundung werden die 0,5-Zahlen auf die nächste gerade Zahl gerundet
Dies kann zu unerwarteten Fehlern bei Finanzberechnungen führen, die auf der bekannteren Round-Half-Up-Rundung basieren.
Dies gilt auch für Visual Basic.
quelle
int(fVal + 0.5)
deshalb auch in Sprachen mit integrierter Rundungsfunktion so oft sehe .Was macht diese Funktion, wenn sie als aufgerufen wird
Rec(0)
(nicht unter dem Debugger)?Antworten:
Dies liegt daran, dass der 64-Bit-JIT-Compiler die Tail-Call-Optimierung anwendet , während dies beim 32-Bit-JIT nicht der Fall ist.
Leider habe ich keinen 64-Bit-Computer zur Hand, um dies zu überprüfen, aber die Methode erfüllt alle Bedingungen für die Tail-Call-Optimierung. Wenn jemand einen hat, würde mich interessieren, ob er wahr ist.
quelle
++
hat mich total umgehauen. Kannst du nichtRec(i + 1)
wie ein normaler Mensch anrufen ?Weisen Sie dies zu!
Dies ist eine, die ich gerne auf Partys frage (weshalb ich wahrscheinlich nicht mehr eingeladen werde):
Können Sie den folgenden Code kompilieren lassen?
Ein einfacher Cheat könnte sein:
Aber die wirkliche Lösung ist folgende:
Es ist also eine wenig bekannte Tatsache, dass Werttypen (Strukturen) ihre
this
Variable neu zuweisen können .quelle
//this = new Teaser();
:-)public Foo(int bar){this = new Foo(); specialVar = bar;}
. Dies ist nicht effizient und nicht wirklich gerechtfertigt (specialVar
wird zweimal zugewiesen), sondern nur zu Ihrer Information. (Das ist der Grund, der in dem Buch angegeben ist, ich weiß nicht, warum wir es nicht einfach tun solltenpublic Foo(int bar) : this()
)Vor einigen Jahren hatten wir bei der Arbeit am Loyalitätsprogramm ein Problem mit der Anzahl der Punkte, die Kunden gegeben wurden. Das Problem bezog sich auf das Casting / Konvertieren von Double in Int.
Im folgenden Code:
ist i1 == i2 ?
Es stellt sich heraus, dass i1! = I2. Aufgrund unterschiedlicher Rundungsrichtlinien in Convert und Cast Operator sind die tatsächlichen Werte:
Es ist immer besser, Math.Ceiling () oder Math.Floor () (oder Math.Round mit MidpointRounding, das unseren Anforderungen entspricht) aufzurufen.
quelle
Sie sollten 0 zu einer Ganzzahl gemacht haben, selbst wenn eine Überladung der Enum-Funktion vorliegt.
Ich kannte die Gründe des C # -Kern-Teams für die Zuordnung von 0 zu Enum, aber es ist nicht so orthogonal, wie es sein sollte. Beispiel aus Npgsql .
Testbeispiel:
quelle
none
beliebige Aufzählung konvertiert werden kann, und 0 immer zu einem int machen und nicht implizit in eine Aufzählung konvertierbar machen.0
in enum konvertierbar sein. Siehe Eric Lipperts Blog: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspxDies ist eines der ungewöhnlichsten, die ich bisher gesehen habe (abgesehen von den hier natürlich!):
Sie können es deklarieren, haben aber keinen wirklichen Nutzen, da Sie immer aufgefordert werden, die Klasse, die Sie in die Mitte stecken, mit einer anderen Schildkröte zu umwickeln.
[Witz] Ich denke, es sind Schildkröten ganz unten ... [/ Witz]
quelle
class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Hier ist eine, von der ich erst kürzlich erfahren habe ...
Das Obige sieht auf den ersten Blick verrückt aus, ist aber tatsächlich legal. Nein, wirklich (obwohl ich einen wichtigen Teil verpasst habe, aber es ist nichts Hackiges wie "eine Klasse namens
IFoo
" hinzufügen oder "einenusing
Alias hinzufügen , umIFoo
auf a zu zeigen" Klasse").Sehen Sie, ob Sie herausfinden können, warum, dann: Wer sagt, dass Sie eine Schnittstelle nicht instanziieren können?
quelle
Wann ist ein Boolescher Wert weder wahr noch falsch?
Bill entdeckte, dass Sie einen Booleschen Wert hacken können, sodass (A und B) falsch ist, wenn A wahr und B wahr ist.
Gehackte Boolesche
quelle
Ich komme etwas spät zur Party, aber ich habe
drei,vier,fünf:Wenn Sie InvokeRequired für ein Steuerelement abfragen, das nicht geladen / angezeigt wurde, wird false angezeigt - und es wird Ihnen ins Gesicht gesprengt, wenn Sie versuchen, es von einem anderen Thread aus zu ändern ( die Lösung besteht darin, darauf zu verweisen Steuerung).
Eine andere, die mich stolperte, ist die, die bei einer Versammlung mit:
Wenn Sie MyEnum.Red.ToString () in einer anderen Assembly berechnen und zwischenzeitlich jemand Ihre Aufzählung neu kompiliert hat, um:
Zur Laufzeit erhalten Sie "Schwarz".
Ich hatte eine gemeinsame Assembly mit einigen praktischen Konstanten. Mein Vorgänger hatte eine Menge hässlich aussehender Nur-Get-Eigenschaften hinterlassen. Ich dachte, ich würde die Unordnung beseitigen und einfach die öffentliche Konstante verwenden. Ich war mehr als ein wenig überrascht, als VS sie nach ihren Werten und nicht nach Referenzen zusammenstellte.
Wenn Sie eine neue Methode einer Schnittstelle aus einer anderen Assembly implementieren, aber unter Bezugnahme auf die alte Version dieser Assembly neu erstellen, erhalten Sie eine TypeLoadException (keine Implementierung von 'NewMethod'), obwohl Sie sie implementiert haben (siehe hier ).
Wörterbuch <,>: "Die Reihenfolge, in der die Artikel zurückgegeben werden, ist undefiniert". Das ist schrecklich , weil es dich manchmal beißen kann, aber andere arbeiten lässt, und wenn du nur blind angenommen hast, dass Dictionary gut spielen wird ("warum sollte es nicht? Ich dachte, List tut es"), musst du es wirklich Haben Sie Ihre Nase drin, bevor Sie endlich anfangen, Ihre Annahme in Frage zu stellen.
quelle
VB.NET, nullables und der ternäre Operator:
Das hat mich einige Zeit zu debuggen, da ich erwartet hatte
i
zu enthaltenNothing
.Was enthält ich wirklich?
0
.Dies ist ein überraschendes, aber tatsächlich "korrektes" Verhalten:
Nothing
In VB.NET ist es nicht genau das gleiche wienull
in CLR:Nothing
Kann je nach Kontext entweder bedeutennull
oderdefault(T)
für einen WerttypT
. Im obigen Fall bedeutetIf
FolgerungenInteger
als die übliche Art vonNothing
und bedeutet5
in diesem Fall .Nothing
0
quelle
Ich fand einen zweiten wirklich seltsamen Eckfall, der meinen ersten bei weitem übertrifft.
Die String.Equals-Methode (String, String, StringComparison) ist nicht nebenwirkungsfrei.
Ich habe an einem Codeblock gearbeitet, der dies in einer eigenen Zeile oben in einer Funktion hatte:
Das Entfernen dieser Zeile führt zu einem Stapelüberlauf an einer anderen Stelle im Programm.
Es stellte sich heraus, dass der Code einen Handler für ein BeforeAssemblyLoad-Ereignis installierte und dies versuchte
Inzwischen sollte ich es dir nicht sagen müssen. Die Verwendung einer Kultur, die zuvor noch nicht für einen Zeichenfolgenvergleich verwendet wurde, führt zu einer Baugruppenlast. InvariantCulture ist keine Ausnahme.
quelle
Hier ist ein Beispiel, wie Sie eine Struktur erstellen können, die die Fehlermeldung "Versuch, geschützten Speicher zu lesen oder zu schreiben. Dies ist häufig ein Hinweis darauf, dass anderer Speicher beschädigt ist" verursacht. Der Unterschied zwischen Erfolg und Misserfolg ist sehr subtil.
Der folgende Komponententest zeigt das Problem.
Sehen Sie, ob Sie herausfinden können, was schief gelaufen ist.
quelle
+= 500
Aufrufe:ldc.i4 500
(drückt 500 als Int32), danncall valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)
- so wird es dann alsdecimal
(96-Bit) ohne Konvertierung behandelt. Wenn Sie es verwenden+= 500M
, wird es richtig. Es sieht einfach so aus, als ob der Compiler denkt , dass er es auf eine Weise tun kann (vermutlich aufgrund des impliziten int-Operators) und dann beschließt, es auf eine andere Weise zu tun.C # unterstützt Konvertierungen zwischen Arrays und Listen, solange die Arrays nicht mehrdimensional sind und eine Vererbungsbeziehung zwischen den Typen besteht und die Typen Referenztypen sind
Beachten Sie, dass dies nicht funktioniert:
quelle
Dies ist das seltsamste, dem ich zufällig begegnet bin:
Wird wie folgt verwendet:
Wird ein werfen
NullReferenceException
. Es stellt sich heraus, dass die mehreren Ergänzungen vom C # -Compiler zu einem Aufruf von kompiliert werdenString.Concat(object[])
. Vor .NET 4 gibt es einen Fehler in genau dieser Überladung von Concat, bei dem das Objekt auf Null überprüft wird, jedoch nicht auf ToString ():Dies ist ein Fehler von ECMA-334 §14.7.4:
quelle
.ToString
eigentlich niemals null zurückgeben sollte, sondern string.Empty. Trotzdem und Fehler im Rahmen.Interessant - als ich mir das zum ersten Mal ansah, nahm ich an, dass es etwas war, nach dem der C # -Compiler gesucht hat, aber selbst wenn Sie die IL direkt ausgeben, um die Möglichkeit von Interferenzen auszuschließen, geschieht dies immer noch, was bedeutet, dass es wirklich der
newobj
Op-Code ist, der das tut Überprüfung.Dies entspricht auch der
true
Überprüfungstring.Empty
, anhand derer dieser Op-Code ein spezielles Verhalten aufweisen muss, um leere Zeichenfolgen zu internieren.quelle
Die Ausgabe lautet "Es wurde versucht, den geschützten Speicher zu lesen. Dies ist ein Hinweis darauf, dass der andere Speicher beschädigt ist."
quelle
PropertyInfo.SetValue () kann Aufzählungen Ints zuweisen, Ints zu nullbaren Ints, Aufzählungen zu nullbaren Aufzählungen, aber keine Ints zu nullbaren Aufzählungen.
Vollständige Beschreibung hier
quelle
Was ist, wenn Sie eine generische Klasse mit Methoden haben, die je nach Typargumenten mehrdeutig werden können? Ich bin kürzlich auf diese Situation gestoßen, als ich ein Zwei-Wege-Wörterbuch geschrieben habe. Ich wollte symmetrische
Get()
Methoden schreiben , die das Gegenteil von jedem Argument zurückgeben, das übergeben wurde. Etwas wie das:Alles ist gut gut , wenn man eine Instanz machen , wo
T1
undT2
gibt verschiedene Arten:Aber wenn
T1
und gleichT2
sind (und wahrscheinlich, wenn eine eine Unterklasse einer anderen war), ist es ein Compilerfehler:Interessanterweise sind alle anderen Methoden im zweiten Fall noch verwendbar; Es werden nur die jetzt mehrdeutigen Methoden aufgerufen, die einen Compilerfehler verursachen. Interessanter Fall, wenn auch etwas unwahrscheinlich und dunkel.
quelle
C # Accessibility Puzzler
Die folgende abgeleitete Klasse greift von ihrer Basisklasse aus auf ein privates Feld zu , und der Compiler schaut still auf die andere Seite:
Das Feld ist in der Tat privat:
Möchten Sie wissen, wie wir solchen Code kompilieren können?
.
.
.
.
.
.
.
Antworten
Der Trick besteht darin,
Derived
als innere Klasse zu deklarierenBase
:Innere Klassen erhalten vollen Zugriff auf die äußeren Klassenmitglieder. In diesem Fall leitet sich die innere Klasse auch von der äußeren Klasse ab. Dies ermöglicht es uns, die Kapselung privater Mitglieder zu "brechen".
quelle
class A { private int _i; public void foo(A other) { int res = other._i; } }
Habe heute gerade ein schönes kleines Ding gefunden:
Dies löst einen Kompilierungsfehler aus.
Der Aufruf der Methode 'Initialize' muss dynamisch ausgelöst werden, kann jedoch nicht erfolgen, da er Teil eines Basiszugriffsausdrucks ist. Ziehen Sie in Betracht, die dynamischen Argumente umzuwandeln oder den Basiszugriff zu eliminieren.
Wenn ich base.Initialize schreibe (Zeug als Objekt); es funktioniert perfekt, aber dies scheint hier ein "Zauberwort" zu sein, da es genau das gleiche tut, wird immer noch alles als dynamisch empfangen ...
quelle
In einer von uns verwendeten API geben Methoden, die ein Domänenobjekt zurückgeben, möglicherweise ein spezielles "Nullobjekt" zurück. Bei der Implementierung werden der Vergleichsoperator und die
Equals()
Methode überschrieben, um zurückzugeben,true
wenn sie mit verglichen werdennull
.Ein Benutzer dieser API verfügt möglicherweise über folgenden Code:
oder vielleicht etwas ausführlicher wie folgt:
Dabei
GetDefault()
gibt es eine Methode, die einen Standardwert zurückgibt, den wir anstelle von verwenden möchtennull
. Die Überraschung traf mich, als ich ReSharper verwendete und der Empfehlung folgte, eines davon wie folgt umzuschreiben:Wenn es sich bei dem Testobjekt um ein von der API zurückgegebenes Nullobjekt handelt
null
, hat sich das Verhalten des Codes jetzt geändert, da der Null-Koaleszenzoperator tatsächlich prüftnull
, nicht ausgeführt wirdoperator=
oderEquals()
.quelle
Betrachten Sie diesen seltsamen Fall:
Wenn
Base
undDerived
in derselben Assembly deklariert sind, wird der CompilerBase::Method
virtuell und versiegelt (in der CIL), obwohlBase
die Schnittstelle nicht implementiert wird.Wenn
Base
undDerived
sich in verschiedenen Assemblys befinden,Derived
ändert der Compiler beim Kompilieren der Assembly nicht die andere Assembly, sodass ein Mitglied eingeführtDerived
wird, das eine explizite Implementierung darstellt, fürMyInterface::Method
die nur der Aufruf delegiert wirdBase::Method
.Der Compiler muss dies tun, um den polymorphen Versand in Bezug auf die Schnittstelle zu unterstützen, dh er muss diese Methode virtuell machen.
quelle
Das Folgende könnte allgemeines Wissen sein, das mir einfach fehlte, aber eh. Vor einiger Zeit hatten wir einen Fehlerfall, der virtuelle Eigenschaften enthielt. Wenn Sie den Kontext ein wenig abstrahieren, berücksichtigen Sie den folgenden Code und wenden Sie den Haltepunkt auf den angegebenen Bereich an:
Im
Derived
Objektkontext können Sie dasselbe Verhalten beim Hinzufügenbase.Property
als Uhr oder beim Eingebenbase.Property
in die Schnelluhr feststellen.Ich brauchte einige Zeit, um zu erkennen, was los war. Am Ende wurde ich von der Quickwatch erleuchtet. Wenn Sie in die Quickwatch gehen und das
Derived
Objekt d (oder aus dem Kontext des Objektsthis
) erkunden und das Feld auswählenbase
, zeigt das Bearbeitungsfeld oben auf der Quickwatch die folgende Besetzung an:Das heißt, wenn die Basis als solche ersetzt wird, wäre der Anruf
für die Uhren, Quickwatch und die Debugging-Mouse-Over-Tooltips, und es wäre dann sinnvoll, sie anzuzeigen,
"AWESOME"
anstatt den"BASE_AWESOME"
Polymorphismus zu berücksichtigen. Ich bin mir immer noch nicht sicher, warum es sich in eine Besetzung verwandeln würde. Eine Hypothese ist, dass diescall
möglicherweise nicht und nur im Kontext dieser Module verfügbar istcallvirt
.Wie auch immer, das ändert offensichtlich nichts an der Funktionalität,
Derived.BaseProperty
wird immer noch wirklich zurückkehren"BASE_AWESOME"
, und daher war dies nicht die Wurzel unseres Fehlers bei der Arbeit, sondern lediglich eine verwirrende Komponente. Ich fand es jedoch interessant, wie es Entwickler irreführen könnte, die sich dieser Tatsache während ihrer Debug-Sitzungen nicht bewusst wären, insbesondere wenn sieBase
nicht in Ihrem Projekt verfügbar gemacht werden, sondern als DLL eines Drittanbieters bezeichnet werden, was dazu führt, dass Entwickler nur sagen:quelle
Das ist ziemlich schwer zu toppen. Ich bin darauf gestoßen, als ich versucht habe, eine RealProxy-Implementierung zu erstellen, die Begin / EndInvoke wirklich unterstützt (danke MS, dass dies ohne schreckliche Hacks unmöglich ist). Dieses Beispiel ist im Grunde ein Fehler in der CLR. Der nicht verwaltete Codepfad für BeginInvoke überprüft nicht, ob die Rückmeldung von RealProxy.PrivateInvoke (und meine Invoke-Überschreibung) eine Instanz eines IAsyncResult zurückgibt. Sobald es zurückgegeben wird, wird die CLR unglaublich verwirrt und verliert jede Vorstellung davon, was los ist, wie die Tests unten zeigen.
Ausgabe:
quelle
Ich bin mir nicht sicher, ob Sie sagen würden, dass dies eine Windows Vista / 7-Kuriosität oder eine .Net-Kuriosität ist, aber ich habe mir eine Weile am Kopf gekratzt.
In Windows Vista / 7 wird die Datei tatsächlich beschrieben
C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt
quelle
Haben Sie jemals gedacht, dass der C # -Compiler ungültige CIL generieren könnte? Führen Sie dies aus und Sie erhalten eine
TypeLoadException
:Ich weiß allerdings nicht, wie es im C # 4.0-Compiler abschneidet.
EDIT : Dies ist die Ausgabe von meinem System:
quelle
C # hat etwas wirklich Aufregendes, wie es mit Verschlüssen umgeht.
Anstatt die Werte der Stapelvariablen in die Variable ohne Abschluss zu kopieren, führt diese Präprozessor-Magie alle Vorkommen der Variablen in ein Objekt ein und verschiebt sie so aus dem Stapel - direkt auf den Heap! :) :)
Ich denke, das macht C # noch funktional vollständiger (oder lambda-vollständiger huh) als ML selbst (das das Kopieren von AFAIK mit Stapelwerten verwendet). F # hat diese Funktion ebenso wie C #.
Das freut mich sehr, danke MS Jungs!
Es ist zwar keine Kuriosität oder ein Eckfall ... aber etwas wirklich Unerwartetes von einer stapelbasierten VM-Sprache :)
quelle
Aus einer Frage, die ich vor kurzem gestellt habe:
Bedingter Operator kann nicht implizit umwandeln?
Gegeben:
Wo
aBoolValue
ist entweder wahr oder falsch zugewiesen;Folgendes wird nicht kompiliert:
Aber das würde:
Die Antwort ist auch ziemlich gut.
quelle
ve not test it I
sicher bin , dass dies funktionieren wird: Byte aByteValue = aBoolValue? (Byte) 1: (Byte) 0; Oder: Byte aByteValue = (Byte) (aBoolValue? 1: 0);1 : 0
allein wird implizit in int umgewandelt, nicht in Byte.Das Scoping in c # ist manchmal wirklich bizarr. Lassen Sie mich Ihnen ein Beispiel geben:
Dies kann nicht kompiliert werden, da der Befehl neu deklariert wird. In diesem Thread zu Stackoverflow und in meinem Blog gibt es einige interessante Vermutungen, warum dies so funktioniert .
quelle