Was ist der seltsamste Eckfall, den Sie in C # oder .NET gesehen haben? [geschlossen]


322

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?


64
Getestet auf Mono 2.0 rc; gibt True zurück
Marc Gravell

10
Beide Zeichenfolgen sind am Ende Zeichenfolgen. Leer und es scheint, dass das Framework nur einen Verweis darauf enthält
Adrian Zanescu

34
Es ist eine Sache zur Erhaltung des Gedächtnisses. Suchen Sie in der MSDN-Dokumentation nach der statischen Methode string.Intern. Die CLR verwaltet einen Zeichenfolgenpool. Aus diesem Grund werden Zeichenfolgen mit identischem Inhalt als Verweise auf denselben Speicher, dh Objekt, angezeigt.
John Leidegren

12
@ John: String-Internierung erfolgt nur automatisch für Literale . Das ist hier nicht der Fall. @ DanielSwe: Internierung ist nicht erforderlich, um Strings unveränderlich zu machen. Die Tatsache, dass es möglich ist, ist eine schöne Folge der Unveränderlichkeit, aber normales Internieren findet hier sowieso nicht statt.
Jon Skeet

3
Das Implementierungsdetail, das dieses Verhalten verursacht, wird hier erklärt: blog.liranchen.com/2010/08/brain-teasing-with-strings.html
Liran

Antworten:


394

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 ...)

    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());
    }

Also, was war T ...

Antwort: beliebig Nullable<T>- wie z int?. 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 aufruft


Update: Die Handlung wird dicker ... Ayende Rahien warf eine ähnliche Herausforderung auf seinen Blog , aber mit einem 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?");
}

Aber es kann besiegt werden! Verwenden der gleichen Indirektion, die auch für Remoting verwendet wird. Warnung - das Folgende ist rein böse :

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

Wenn dies new()geschehen ist , wird der Aufruf an den Proxy ( MyFunnyProxyAttribute) umgeleitet , der zurückkehrt null. Jetzt geh und wasche deine Augen!


9
Warum kann Nullable <T> .GetType () nicht definiert werden? Sollte das Ergebnis nicht typeof sein (Nullable <T>)?
Drew Noakes

69
Drew: Das Problem ist, dass GetType () nicht virtuell ist und daher nicht überschrieben wird. Dies bedeutet, dass der Wert für den Methodenaufruf eingerahmt ist. Die Box wird zu einer Nullreferenz, daher die NRE.
Jon Skeet

10
@ Draw; Darüber hinaus gibt es spezielle Boxregeln für Nullable <T>. Dies bedeutet, dass leere Nullable <T> -Boxen auf Null gesetzt werden und keine Box, die leere Nullable <T> enthält (und Null-Un-Boxen auf Null Nullable <T) >)
Marc Gravell

29
Sehr sehr cool. Auf eine unkühle Art und Weise. ;-)
Konrad Rudolph

6
Konstruktor-Einschränkung, 10.1.5 in der C # 3.0-
Sprachspezifikation

216

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

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

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.


22
Es kam mir auch komisch vor. Zumindest bis ich eine große Liste von Zahlen umrundet und deren Summe berechnet hatte. Sie erkennen dann, dass Sie, wenn Sie einfach aufrunden, möglicherweise einen großen Unterschied zur Summe der nicht gerundeten Zahlen haben. Sehr schlecht, wenn Sie finanzielle Berechnungen durchführen!
Tsvetomir Tsonev

255
Falls die Leute es nicht wussten, können Sie Folgendes tun: Math.Round (x, MidpointRounding.AwayFromZero); Rundungsschema ändern.
ICR

26
Aus den Dokumenten: Das Verhalten dieser Methode folgt dem IEEE-Standard 754, Abschnitt 4. Diese Art der Rundung wird manchmal als Rundung auf die nächste oder Bankrundung bezeichnet. Es minimiert Rundungsfehler, die sich aus der konsistenten Rundung eines Mittelpunkts in eine einzelne Richtung ergeben.
ICR

8
Ich frage mich, ob ich int(fVal + 0.5)deshalb auch in Sprachen mit integrierter Rundungsfunktion so oft sehe .
Ben Blank

32
Ironischerweise arbeitete ich bei einer Bank einmal und die anderen Programmierer begann darüber Ausflippen und dachte , dass Rundung im Rahmen gebrochen war
dan

176

Was macht diese Funktion, wenn sie als aufgerufen wird Rec(0)(nicht unter dem Debugger)?

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

Antworten:

  • Bei 32-Bit-JIT sollte dies zu einer StackOverflowException führen
  • Bei 64-Bit-JIT sollten alle Zahlen in int.MaxValue gedruckt werden

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.


10
Muss im Release-Modus kompiliert werden, funktioniert aber definitiv auf x64 =)
Neil Williams

3
Möglicherweise lohnt es sich, Ihre Antwort zu aktualisieren, wenn VS 2010 herauskommt, da alle aktuellen JITs dann die TCO im Release-Modus
ausführen

3
Ich habe gerade VS2010 Beta 1 unter 32-Bit-WinXP ausprobiert. Erhalten Sie immer noch eine StackOverflowException.
Squillman

130
+1 für die StackOverflowException
Calvinlough

7
Das ++hat mich total umgehauen. Kannst du nicht Rec(i + 1)wie ein normaler Mensch anrufen ?
Konfigurator

111

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?

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

Ein einfacher Cheat könnte sein:

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

Aber die wirkliche Lösung ist folgende:

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

Es ist also eine wenig bekannte Tatsache, dass Werttypen (Strukturen) ihre thisVariable neu zuweisen können .


3
C ++ - Klassen können das auch ... wie ich kürzlich herausgefunden habe, nur um angeschrien zu werden, weil ich tatsächlich versucht habe, es für eine Optimierung zu verwenden: p
mpen

1
Ich habe tatsächlich In-Place New verwendet.
Ich

70
Dies ist auch ein Cheat : //this = new Teaser();:-)
AndrewJacksonZA

17
:-) Ich würde diese Cheats in meinem Produktionscode vorziehen, als diesen Greuel der Neuzuweisung ...
Omer Mor

2
Von CLR über C #: Der Grund dafür ist, dass Sie den parameterlosen Konstruktor einer Struktur in einem anderen Konstruktor aufrufen können. Wenn Sie nur einen Wert einer Struktur initialisieren möchten und die anderen Werte Null / Null (Standard) sein sollen, können Sie schreiben public Foo(int bar){this = new Foo(); specialVar = bar;}. Dies ist nicht effizient und nicht wirklich gerechtfertigt ( specialVarwird 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 sollten public Foo(int bar) : this())
kizzx2

100

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:

double d = 13.6;

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

ist i1 == i2 ?

Es stellt sich heraus, dass i1! = I2. Aufgrund unterschiedlicher Rundungsrichtlinien in Convert und Cast Operator sind die tatsächlichen Werte:

i1 == 14
i2 == 13

Es ist immer besser, Math.Ceiling () oder Math.Floor () (oder Math.Round mit MidpointRounding, das unseren Anforderungen entspricht) aufzurufen.

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

44
Das Umwandeln in eine Ganzzahl rundet nicht, sondern schneidet sie nur ab (effektiv immer abrunden). Das macht also durchaus Sinn.
Max Schmeling

57
@ Max: Ja, aber warum rundet Convert?
Stefan Steinegger

18
@Stefan Steinegger Wenn alles, was es tat, besetzt wäre, gäbe es überhaupt keinen Grund dafür, oder? Beachten Sie auch, dass der Klassenname "Nicht konvertieren" lautet.
Bug-a-Lot

3
In VB: CInt () Runden. Fix () wird abgeschnitten. Hat mich einmal verbrannt ( blog.wassupy.com/2006/01/i-can-believe-it-not-truncating.html )
Michael Haren

74

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:

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");
        }
    }
}

18
Wow, das ist neu für mich. Auch seltsam, wie ConverTo.ToIn32 () funktioniert, Casting auf (int) 0 jedoch nicht. Und jede andere Zahl> 0 funktioniert. (Mit "funktioniert" meine ich das Objekt Überladung nennen.)
Lucas

Es gibt eine empfohlene Code-Analyseregel, um bewährte Methoden für dieses Verhalten durchzusetzen: msdn.microsoft.com/en-us/library/ms182149%28VS.80%29.aspx Dieser Link enthält auch eine nette Beschreibung der Funktionsweise der 0-Zuordnung .
Chris Clark

1
@ Chris Clark: Ich habe versucht, None = 0 auf enum Symbol zu setzen.
Michael Buen

2
IMO sollten sie ein Schlüsselwort eingeführt haben, das in eine nonebeliebige Aufzählung konvertiert werden kann, und 0 immer zu einem int machen und nicht implizit in eine Aufzählung konvertierbar machen.
CodesInChaos

5
ConverTo.ToIn32 () funktioniert, weil das Ergebnis keine Kompilierungszeitkonstante ist. Und nur die Kompilierzeitkonstante 0 kann in eine Aufzählung konvertiert werden. In früheren Versionen von .net sollte sogar nur das Literal 0in enum konvertierbar sein. Siehe Eric Lipperts Blog: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspx
CodesInChaos

67

Dies ist eines der ungewöhnlichsten, die ich bisher gesehen habe (abgesehen von den hier natürlich!):

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

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]


34
Sie können jedoch Instanzen erstellen:class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Marc Gravell

24
Tatsächlich. Dies ist das Muster, das Java-Enums mit großer Wirkung verwenden. Ich benutze es auch in Protokollpuffern.
Jon Skeet

6
RCIX, oh ja, das ist es.
Joshua

8
Ich habe dieses Muster ziemlich oft in ausgefallenen Generika verwendet. Es ermöglicht Dinge wie einen korrekt eingegebenen Klon oder das Erstellen von Instanzen von sich selbst.
Lucero

20
Dies ist das 'merkwürdig wiederkehrende Vorlagenmuster' en.wikipedia.org/wiki/Curiously_recurring_template_pattern
porges

65

Hier ist eine, von der ich erst kürzlich erfahren habe ...

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

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 "einen usingAlias hinzufügen , um IFooauf a zu zeigen" Klasse").

Sehen Sie, ob Sie herausfinden können, warum, dann: Wer sagt, dass Sie eine Schnittstelle nicht instanziieren können?


1
+1 für „unter Verwendung von Alias“ - ich wusste , dass du nie tun konnte , dass !
David

Hack im Compiler für COM Interop :-)
Ion Todirel

Du Bastard! Sie hätten zumindest "unter bestimmten Umständen" sagen können ... Mein Compiler widerlegt!
MA Hanin

56

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


134
Wenn es natürlich FILE_NOT_FOUND ist!
Greg

12
Dies ist interessant, weil es mathematisch gesehen bedeutet, dass keine Aussage in C # beweisbar ist. Hoppla.
Simon Johnson

20
Eines Tages werde ich ein Programm schreiben, das von diesem Verhalten abhängt, und die Dämonen der dunkelsten Hölle werden mich willkommen heißen. Bwahahahahaha!
Jeffrey L Whitledge

18
In diesem Beispiel werden bitweise und keine logischen Operatoren verwendet. Wie ist das überraschend?
Josh Lee

6
Nun, er hackt das Layout der Struktur, natürlich werden Sie seltsame Ergebnisse erzielen, das ist nicht so überraschend oder unerwartet!
Ion Todirel

47

Ich komme etwas spät zur Party, aber ich habe drei, vier, fünf:

  1. 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).

  2. Eine andere, die mich stolperte, ist die, die bei einer Versammlung mit:

    enum MyEnum
    {
        Red,
        Blue,
    }

    Wenn Sie MyEnum.Red.ToString () in einer anderen Assembly berechnen und zwischenzeitlich jemand Ihre Aufzählung neu kompiliert hat, um:

    enum MyEnum
    {
        Black,
        Red,
        Blue,
    }

    Zur Laufzeit erhalten Sie "Schwarz".

  3. 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.

  4. 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 ).

  5. 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.


6
# 2 ist ein interessantes Beispiel. Aufzählungen sind Compiler-Zuordnungen zu ganzzahligen Werten. Obwohl Sie ihnen keine expliziten Werte zugewiesen haben, hat der Compiler dies getan, was zu MyEnum.Red = 0 und MyEnum.Blue = 1 führte. Wenn Sie Schwarz hinzugefügt haben, haben Sie den Wert 0 neu definiert, um ihn von Rot auf Schwarz abzubilden. Ich vermute, dass sich das Problem auch in anderen Verwendungszwecken wie der Serialisierung manifestiert hätte.
LBushkin

3
+1 für Aufruf erforderlich. Bei uns ziehen wir es vor, Aufzählungen wie Rot = 1, Blau = 2 explizit Werte zuzuweisen, damit vor oder nach dem Einfügen immer neue Werte eingefügt werden können. Dies ist besonders erforderlich, wenn Sie Werte in Datenbanken speichern.
TheVillageIdiot

53
Ich bin nicht der Meinung, dass # 5 ein "Randfall" ist. Das Wörterbuch sollte keine definierte Reihenfolge haben, die darauf basiert, wann Sie Werte einfügen. Wenn Sie eine definierte Reihenfolge wünschen, verwenden Sie eine Liste oder einen Schlüssel, der auf eine für Sie nützliche Weise sortiert werden kann, oder verwenden Sie eine völlig andere Datenstruktur.
Wedge

21
@Wedge, wie SortedDictionary vielleicht?
Allon Guralnek

4
# 3 passiert, weil Konstanten überall dort als Literale eingefügt werden, wo sie verwendet werden (zumindest in C #). Ihr Vorgänger hat es möglicherweise bereits bemerkt, weshalb er die Eigenschaft get-only verwendet hat. Eine schreibgeschützte Variable (im Gegensatz zu einer Konstante) würde jedoch genauso gut funktionieren.
Remoun

33

VB.NET, nullables und der ternäre Operator:

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

Das hat mich einige Zeit zu debuggen, da ich erwartet hatte izu enthalten Nothing.

Was enthält ich wirklich? 0.

Dies ist ein überraschendes, aber tatsächlich "korrektes" Verhalten: NothingIn VB.NET ist es nicht genau das gleiche wie nullin CLR: NothingKann je nach Kontext entweder bedeuten nulloder default(T)für einen Werttyp T. Im obigen Fall bedeutet IfFolgerungen Integerals die übliche Art von Nothingund bedeutet 5in diesem Fall .Nothing0


Interessanterweise konnte ich diese Antwort nicht finden und musste eine Frage erstellen . Nun, wer wusste, dass die Antwort in diesem Thread ist?
GSerg

28

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:

stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);

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

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

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.


Ich denke, "Laden einer Baugruppe" ist ein Nebeneffekt, da Sie dies mit BeforeAssemblyLoad beobachten können!
Jacob Krall

2
Beeindruckend. Dies ist ein perfekter Schuss in das Bein des Betreuers. Ich denke, das Schreiben eines BeforeAssemblyLoad-Handlers kann zu vielen solchen Überraschungen führen.
Perücke

20

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.

    [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);
        }
    }

Mein Kopf tut weh ... Warum funktioniert das nicht?
Jason

2
Hm, ich habe das vor ein paar Monaten geschrieben, aber ich kann mich nicht erinnern, warum genau es passiert ist.
cbp

10
Sieht aus wie ein Compiler-Fehler; die += 500Aufrufe: ldc.i4 500(drückt 500 als Int32), dann call valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)- so wird es dann als decimal(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.
Marc Gravell

1
Entschuldigung für den doppelten Beitrag, hier ist eine ausführlichere Erklärung. Ich werde das hinzufügen, ich bin davon gebissen worden und das ist scheiße, obwohl ich verstehe, warum es passiert. Für mich ist dies eine unglückliche Einschränkung des Struktur- / Werttyps. bytes.com/topic/net/answers/…
Bennett Dill

2
@Ben Compilerfehler oder die Änderung, die die ursprüngliche Struktur nicht beeinflusst, ist in Ordnung. Eine Zugriffsverletzung ist ein ganz anderes Tier. Die Laufzeit sollte es niemals auslösen, wenn Sie nur sicheren, rein verwalteten Code schreiben.
CodesInChaos

18

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

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" };

Beachten Sie, dass dies nicht funktioniert:

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[]'

11
Das IList <T> -Beispiel ist nur eine Umwandlung, da string [] bereits ICloneable, IList, ICollection, IEnumerable, IList <string>, ICollection <string> und IEnumerable <string> implementiert.
Lucas

15

Dies ist das seltsamste, dem ich zufällig begegnet bin:

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

Wird wie folgt verwendet:

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

Wird ein werfen NullReferenceException. Es stellt sich heraus, dass die mehreren Ergänzungen vom C # -Compiler zu einem Aufruf von kompiliert werden String.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 ():

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;

Dies ist ein Fehler von ECMA-334 §14.7.4:

Der binäre + -Operator führt eine Zeichenfolgenverkettung durch, wenn einer oder beide Operanden vom Typ sind string. Wenn ein Operand für die Verkettung von Zeichenfolgen verwendet wird null, wird eine leere Zeichenfolge ersetzt. Andernfalls wird jeder Nicht-String-Operand durch Aufrufen der ToStringvom Typ geerbten virtuellen Methode in seine String-Darstellung konvertiert object. Wenn ToStringzurückgegeben wird null, wird eine leere Zeichenfolge ersetzt.


3
Hmm, aber ich kann mir diesen Fehler vorstellen, da er .ToStringeigentlich niemals null zurückgeben sollte, sondern string.Empty. Trotzdem und Fehler im Rahmen.
Dykam

12

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 newobjOp-Code ist, der das tut Überprüfung.

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);

Dies entspricht auch der trueÜberprüfung string.Empty, anhand derer dieser Op-Code ein spezielles Verhalten aufweisen muss, um leere Zeichenfolgen zu internieren.


kein kluger aleck zu sein oder so, aber hast du schon vom reflektor gehört ? es ist in solchen Fällen ziemlich praktisch;
RCIX

3
Du bist nicht schlau; Sie verpassen den Punkt - ich wollte für diesen einen Fall eine spezifische IL generieren. Angesichts der Tatsache, dass Reflection.Emit für diese Art von Szenario trivial ist, ist es wahrscheinlich so schnell wie das Schreiben eines Programms in C #, das Öffnen des Reflektors, das Finden der Binärdatei, das Finden der Methode usw. Und ich muss nicht einmal Verlassen Sie die IDE, um dies zu tun.
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

Die Ausgabe lautet "Es wurde versucht, den geschützten Speicher zu lesen. Dies ist ein Hinweis darauf, dass der andere Speicher beschädigt ist."


1
Interessant! Klingt jedoch nach einem Compiler-Fehler. Ich habe nach C # portiert und es funktioniert gut. Das heißt, es gibt viele Probleme mit Ausnahmen, die in Load ausgelöst werden, und es verhält sich mit / ohne Debugger anders - Sie können mit einem Debugger fangen, aber nicht ohne (in einigen Fällen).
Marc Gravell

Entschuldigung, ich habe vergessen, dass Sie das Kombinationsfeld zum Formular hinzufügen müssen, bevor es angezeigt wird.
Joshua

Hat dies mit der Dialoginitialisierung zu tun, bei der ein SEH als eine Art schrecklicher interner Kommunikationsmechanismus verwendet wird? Ich erinnere mich vage an so etwas in Win32.
Daniel Earwicker

1
Dies ist das gleiche Problem wie oben. Der zurückgegebene Wertetyp ist eine Kopie, daher werden alle Verweise auf Eigenschaften, die aus dieser Kopie stammen, an Bit-Bucket-Land weitergeleitet ... bytes.com/topic/net/answers/…
Bennett Dill

1
Nee. Hier gibt es keine Strukturen. Ich habe es tatsächlich getestet. Es fügt der Listenelement-Sammlung des nativen Kombinationsfelds NULL hinzu, was zu einem verzögerten Absturz führt.
Joshua

10

PropertyInfo.SetValue () kann Aufzählungen Ints zuweisen, Ints zu nullbaren Ints, Aufzählungen zu nullbaren Aufzählungen, aber keine Ints zu nullbaren Aufzählungen.

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 !!!

Vollständige Beschreibung hier


10

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:

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

Alles ist gut gut , wenn man eine Instanz machen , wo T1und T2gibt verschiedene Arten:

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

Aber wenn T1und gleich T2sind (und wahrscheinlich, wenn eine eine Unterklasse einer anderen war), ist es ein Compilerfehler:

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

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.


Gegner der Methodenüberladung werden diese lieben ^^.
Christian Klauser

1
Ich weiß nicht, das macht für mich total Sinn.
Scott Whitlock

10

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:

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

Das Feld ist in der Tat privat:

private int m_basePrivateField = 0;

Möchten Sie wissen, wie wir solchen Code kompilieren können?

.

.

.

.

.

.

.

Antworten


Der Trick besteht darin, Derivedals innere Klasse zu deklarieren Base:

public class Base
{
    private int m_basePrivateField = 0;

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

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".


Das ist eigentlich gut dokumentiert; msdn.microsoft.com/en-us/library/ms173120%28VS.80%29.aspx . Dies kann manchmal eine nützliche Funktion sein, insbesondere wenn die äußere Klasse statisch ist.

Ja - natürlich ist es dokumentiert. Allerdings haben nur sehr wenige Leute dieses Rätsel gelöst, daher fand ich es eine coole Kleinigkeit.
Omer Mor

2
Scheint, als hätten Sie eine sehr starke Möglichkeit eines Stapelüberlaufs, wenn eine innere Klasse ihren Besitzer erben würde ...
Jamie Treworgy

Ein weiterer ähnlicher (und vollkommen korrekter) Fall ist, dass ein Objekt auf ein privates Mitglied eines anderen Objekts des gleichen Typs zugreifen kann:class A { private int _i; public void foo(A other) { int res = other._i; } }
Olivier Jacot-Descombes

10

Habe heute gerade ein schönes kleines Ding gefunden:

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

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 ...


8

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, truewenn sie mit verglichen werden null.

Ein Benutzer dieser API verfügt möglicherweise über folgenden Code:

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

oder vielleicht etwas ausführlicher wie folgt:

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

Dabei GetDefault()gibt es eine Methode, die einen Standardwert zurückgibt, den wir anstelle von verwenden möchten null. Die Überraschung traf mich, als ich ReSharper verwendete und der Empfehlung folgte, eines davon wie folgt umzuschreiben:

return test ?? GetDefault();

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üft null, nicht ausgeführt wird operator=oder Equals().


1
Nicht wirklich ein Eckfall, aber lieber Herr, wer hat sich das ausgedacht?!?
Ray Booysen

Verwendet dieser Code nicht nur nullfähige Typen? Daher empfiehlt ReSharper das "??" verwenden. Wie Ray sagte, hätte ich das nicht für einen Eckfall gehalten; oder liege ich falsch?
Tony

1
Ja, die Typen sind nullwertfähig - und es gibt zusätzlich ein NullObject. Wenn es sich um einen Eckfall handelt, weiß ich es nicht, aber zumindest ist es ein Fall, in dem 'if (a! = Null) a zurückgibt; return b; ' ist nicht dasselbe wie 'return a ?? b '. Ich stimme absolut zu, dass es ein Problem mit dem Framework / API-Design ist - Überladen == null, um true für ein Objekt zurückzugeben, ist sicherlich keine gute Idee!
Tor Livar

8

Betrachten Sie diesen seltsamen Fall:

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

Wenn Baseund Derivedin derselben Assembly deklariert sind, wird der Compiler Base::Methodvirtuell und versiegelt (in der CIL), obwohl Basedie Schnittstelle nicht implementiert wird.

Wenn Baseund Derivedsich in verschiedenen Assemblys befinden, Derivedändert der Compiler beim Kompilieren der Assembly nicht die andere Assembly, sodass ein Mitglied eingeführt Derivedwird, das eine explizite Implementierung darstellt, für MyInterface::Methoddie nur der Aufruf delegiert wird Base::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.


Das klingt in der Tat seltsam. Muss später nachforschen :)
Jon Skeet

@ Jon Skeet: Ich habe dies gefunden, als ich Implementierungsstrategien für Rollen in C # recherchiert habe . Wäre toll, wenn Sie Ihr Feedback dazu bekommen würden!
Jordão

7

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:

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 { } }
}

Im DerivedObjektkontext können Sie dasselbe Verhalten beim Hinzufügen base.Propertyals Uhr oder beim Eingeben base.Propertyin 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 DerivedObjekt d (oder aus dem Kontext des Objekts this) erkunden und das Feld auswählen base, zeigt das Bearbeitungsfeld oben auf der Quickwatch die folgende Besetzung an:

((TestProject1.Base)(d))

Das heißt, wenn die Basis als solche ersetzt wird, wäre der Anruf

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

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 dies callmöglicherweise nicht und nur im Kontext dieser Module verfügbar ist callvirt.

Wie auch immer, das ändert offensichtlich nichts an der Funktionalität, Derived.BasePropertywird 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 sie Basenicht in Ihrem Projekt verfügbar gemacht werden, sondern als DLL eines Drittanbieters bezeichnet werden, was dazu führt, dass Entwickler nur sagen:

"Oi, warte ... was? Omg, wie DLL ist, ... etwas Lustiges zu tun"


Das ist nichts Besonderes, so überschreibt nur die Arbeit.
Konfigurator

7

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.

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.
        }
    }
}

Ausgabe:

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) 

6

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.

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."

In Windows Vista / 7 wird die Datei tatsächlich beschrieben C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt


2
Dies ist in der Tat eine Vista (nicht 7, afaik) Sicherheitsverbesserung. Aber das Coole ist, dass Sie die Datei mit dem Programmdateipfad lesen und öffnen können, während es nichts gibt, wenn Sie dort mit dem Explorer suchen. Dieser hat mich fast einen Tag Arbeit bei einem Kunden gekostet, bevor ich es endlich herausgefunden habe.
Henri

Es ist definitiv auch eine Windows 7-Sache. Das habe ich benutzt, als ich darauf gestoßen bin. Ich verstehe die Gründe dafür, aber es war immer noch frustrierend, es herauszufinden.
Spencer Ruport

In Vista / Win 7 (technisch auch winXP) sollten Apps als technische Benutzerdaten in einen AppData-Ordner im Benutzerordnerland schreiben. Anwendungen sollten niemals in Programmdateien / Windows / System32 / usw. schreiben, es sei denn, sie haben Administratorrechte, und diese Rechte sollten nur dazu da sein, das Programm zu aktualisieren / es zu deinstallieren / eine neue Funktion zu installieren. ABER! Schreiben Sie immer noch nicht in system32 / windows / etc :) Wenn Sie diesen Code oben als admin ausgeführt haben (Rechtsklick> als admin ausführen), sollte er theoretisch in den App-Ordner der Programmdateien schreiben.
Steve Syfuhs


6

Haben Sie jemals gedacht, dass der C # -Compiler ungültige CIL generieren könnte? Führen Sie dies aus und Sie erhalten eine 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));
  }
}

Ich weiß allerdings nicht, wie es im C # 4.0-Compiler abschneidet.

EDIT : Dies ist die Ausgabe von meinem System:

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]

Funktioniert für mich sowohl mit dem C # 3.5-Compiler als auch mit dem C # 4-Compiler ...
Jon Skeet

In meinem System funktioniert es nicht. Ich werde die Ausgabe in die Frage einfügen.
Jordão

In .NET 3.5 ist dies für mich fehlgeschlagen (ich habe keine Zeit, 4.0 zu testen). Und ich kann das Problem mit VB.NET-Code replizieren.
Mark Hurd

3

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 :)


3

Aus einer Frage, die ich vor kurzem gestellt habe:

Bedingter Operator kann nicht implizit umwandeln?

Gegeben:

Bool aBoolValue;

Wo aBoolValueist entweder wahr oder falsch zugewiesen;

Folgendes wird nicht kompiliert:

Byte aByteValue = aBoolValue ? 1 : 0;

Aber das würde:

Int anIntValue = aBoolValue ? 1 : 0;

Die Antwort ist auch ziemlich gut.


obwohl ich ve not test it Isicher bin , dass dies funktionieren wird: Byte aByteValue = aBoolValue? (Byte) 1: (Byte) 0; Oder: Byte aByteValue = (Byte) (aBoolValue? 1: 0);
Alex Pacurar

2
Ja, Alex, das würde funktionieren. Der Schlüssel liegt im impliziten Casting. 1 : 0allein wird implizit in int umgewandelt, nicht in Byte.
MPelletier

2

Das Scoping in c # ist manchmal wirklich bizarr. Lassen Sie mich Ihnen ein Beispiel geben:

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

OleDbCommand command = SQLServer.CreateCommand();

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 .


34
Ich halte das nicht für besonders bizarr. Was Sie in Ihrem Blog als "vollkommen korrekter Code" bezeichnen, ist gemäß der Sprachspezifikation vollkommen falsch . Es mag Sie würden in einer imaginären Sprache korrekt sein wie C # zu sein, aber die Sprache spec ist ganz klar , dass in C # invalid es ist.
Jon Skeet

7
Nun, es ist gültig in C / C ++. Und da es C # ist, hätte ich mir gewünscht, dass es immer noch funktioniert. Was mich am meisten stört, ist, dass es für den Compiler keinen Grund gibt, dies zu tun. Es ist nicht schwer, verschachteltes Scoping durchzuführen. Ich denke, es kommt alles auf das Element der geringsten Überraschung an. Das heißt, es kann sein, dass die Spezifikation dies und das sagt, aber das hilft mir nicht sehr, wenn es völlig unlogisch ist, dass es sich so verhält.
Anders Rune Jensen

6
C #! = C / C ++. Möchten Sie auch cout << "Hello World!" << endl; anstelle von Console.WriteLine ("Hallo Welt!");? Auch ist es nicht unlogisch, lesen Sie einfach die Spezifikation.
Kredns

9
Ich spreche von Scoping-Regeln, die Teil des Kerns der Sprache sind. Sie sprechen von der Standardbibliothek. Aber jetzt ist mir klar, dass ich einfach die winzige Spezifikation der c # -Sprache lesen sollte, bevor ich anfange, darin zu programmieren.
Anders Rune Jensen

6
Eric Lippert hat kürzlich die Gründe veröffentlicht, warum C # so gestaltet wurde: blogs.msdn.com/ericlippert/archive/2009/11/02/… . Die Zusammenfassung ist, weil es weniger wahrscheinlich ist, dass Änderungen unbeabsichtigte Konsequenzen haben.
Helephant
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.