Qual è il caso d'angolo più strano che hai visto in C # o .NET? [chiuso]


322

Colleziono alcuni astucci d'angolo e rompicapo e vorrei sempre saperne di più. La pagina copre davvero solo bit e bob del linguaggio C #, ma trovo anche interessanti le cose di .NET. Ad esempio, eccone uno che non è nella pagina, ma che trovo incredibile:

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

Mi aspetto che stampare False - dopo tutto, "new" (con un tipo di riferimento) crea sempre un nuovo oggetto, non è vero? Le specifiche per C # e CLI indicano che dovrebbe. Bene, non in questo caso particolare. Stampa True e lo ha fatto su ogni versione del framework con cui l'ho provato. (Non l'ho provato su Mono, certo ...)

Giusto per essere chiari, questo è solo un esempio del tipo di cosa che sto cercando - non ero particolarmente alla ricerca di discussioni / spiegazioni di questa stranezza. (Non è lo stesso del normale internamento di stringhe; in particolare, l'internamento di stringhe normalmente non si verifica quando viene chiamato un costruttore.) Stavo davvero chiedendo un comportamento strano simile.

Altre gemme in agguato là fuori?


64
Testato su Mono 2.0 rc; restituisce True
Marc Gravell

10
entrambe le stringhe finiscono per essere string.Empty e sembra che il framework mantenga solo un riferimento a quello
Adrian Zanescu

34
È una cosa di conservazione della memoria. Cerca la documentazione MSDN per la stringa del metodo statico.Intern. Il CLR mantiene un pool di stringhe. Ecco perché stringhe con contenuto identico vengono visualizzate come riferimenti alla stessa memoria, ovvero all'oggetto.
John Leidegren,

12
@John: l'internazionalizzazione delle stringhe avviene automaticamente solo per i letterali . Questo non è il caso qui. @DanielSwe: il interning non è richiesto per rendere immutabili le stringhe. Il fatto che sia possibile è un bel corollario di immutabilità, ma il normale internato non sta avvenendo qui.
Jon Skeet,

3
Il dettaglio dell'implementazione che causa questo comportamento è spiegato qui: blog.liranchen.com/2010/08/brain-teasing-with-strings.html
Liran

Risposte:


394

Penso di averti mostrato questo prima, ma mi piace il divertimento qui - ci sono voluti alcuni debug per rintracciare! (il codice originale era ovviamente più complesso e sottile ...)

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

Quindi cosa era T ...

Risposta: qualsiasi Nullable<T>- come ad esempio int?. Tutti i metodi vengono sovrascritti, tranne GetType () che non può essere; quindi viene cast (boxed) su object (e quindi su null) per chiamare object.GetType () ... che chiama null ;-p


Aggiornamento: la trama si infittisce ... Ayende Rahien ha lanciato una sfida simile sul suo blog , ma con un 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?");
}

Ma può essere sconfitto! Usando la stessa indiretta usata da cose come il telecomando; attenzione: il seguente è puro male :

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

Con questo in atto, la new()chiamata viene reindirizzata al proxy ( MyFunnyProxyAttribute), che restituisce null. Adesso vai a lavarti gli occhi!


9
Perché non è possibile definire Nullable <T> .GetType ()? Il risultato non dovrebbe essere typeof (Nullable <T>)?
Drew Noakes,

69
Drew: il problema è che GetType () non è virtuale, quindi non è sovrascritto, il che significa che il valore è inscatolato per la chiamata del metodo. La casella diventa riferimento null, quindi NRE.
Jon Skeet

10
@Ha disegnato; inoltre, esistono regole di boxe speciali per Nullable <T>, il che significa che una casella Nullable <T> vuota su null, non una casella che contiene una Nullable vuota <T> (e una non-box vuota in una Nullable vuota <T >)
Marc Gravell

29
Molto, molto bello. In un modo poco freddo. ;-)
Konrad Rudolph,


216

Arrotondamento dei banchieri.

Questo non è tanto un bug del compilatore o un malfunzionamento, ma sicuramente uno strano caso angolare ...

Il .Net Framework utilizza uno schema o un arrotondamento noto come arrotondamento del banco.

Nell'arrotondamento dei banchieri gli 0,5 numeri vengono arrotondati al numero pari più vicino, quindi

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

Ciò può comportare alcuni bug imprevisti nei calcoli finanziari basati sul più noto arrotondamento arrotondato per metà.

Questo vale anche per Visual Basic.


22
Mi è sembrato strano anche a me. Questo è, almeno, fino a quando ho avuto intorno a un grande elenco di numeri e calcolare la loro somma. Ti rendi quindi conto che se arrotondi semplicemente, finirai con una differenza potenzialmente enorme dalla somma dei numeri non arrotondati. Molto male se stai facendo calcoli finanziari!
Tsvetomir Tsonev,

255
Nel caso in cui le persone non lo sapessero, puoi fare: Math.Round (x, MidpointRounding.AwayFromZero); Per modificare lo schema di arrotondamento.
ICR

26
Dai documenti: Il comportamento di questo metodo segue lo standard IEEE 754, sezione 4. Questo tipo di arrotondamento viene talvolta chiamato arrotondamento al più vicino o arrotondamento del banchiere. Riduce al minimo gli errori di arrotondamento risultanti dall'arrotondamento costante di un valore del punto medio in una sola direzione.
ICR

8
Mi chiedo se questo è il motivo per cui vedo int(fVal + 0.5)così spesso anche nelle lingue che hanno una funzione di arrotondamento integrata.
Ben Blank,

32
Ironia della sorte, una volta ho lavorato in una banca e gli altri programmatori hanno iniziato a capirlo, pensando che l'arrotondamento fosse interrotto nel framework
dan

176

Cosa farà questa funzione se chiamata come Rec(0)(non sotto il debugger)?

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

Risposta:

  • Su JIT a 32 bit dovrebbe risultare in StackOverflowException
  • Su JIT a 64 bit dovrebbe stampare tutti i numeri su int.MaxValue

Questo perché il compilatore JIT a 64 bit applica l'ottimizzazione delle chiamate di coda , mentre il JIT a 32 bit no.

Sfortunatamente non ho a disposizione una macchina a 64 bit per verificarlo, ma il metodo soddisfa tutte le condizioni per l'ottimizzazione delle chiamate in coda. Se qualcuno ne ha uno sarei interessato a vedere se è vero.


10
Deve essere compilato in modalità di rilascio, ma sicuramente funziona su x64 =)
Neil Williams,

3
potrebbe valere la pena aggiornare la tua risposta quando uscirà VS 2010 poiché tutti i JIT attuali eseguiranno il TCO in modalità Release
ShuggyCoUk,

3
Ho appena provato su VS2010 Beta 1 su WinXP a 32 bit. Ottieni ancora una StackOverflowException.
Squillman,

130
+1 per StackOverflowException
calvinlough

7
Che ++mi ha completamente buttato via. Non puoi chiamare Rec(i + 1)come una persona normale?
configuratore

111

Assegna questo!


Questo è quello che mi piace chiedere alle feste (che è probabilmente il motivo per cui non mi invitano più):

Puoi compilare il seguente pezzo di codice?

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

Un trucco semplice potrebbe essere:

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

Ma la vera soluzione è questa:

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

Quindi è risaputo che i tipi di valore (strutture) possono riassegnare la loro thisvariabile.


3
Anche le classi C ++ possono farlo ... come ho scoperto un po 'di recente, solo per essere urlato per aver effettivamente cercato di usarlo per un'ottimizzazione: p
mpen

1
Stavo usando sul posto nuovo in realtà. Volevo solo un modo efficiente per aggiornare tutti i campi :)
mpen

70
Anche questo è un trucco: //this = new Teaser();:-)
AndrewJacksonZA

17
:-) Preferirei quei trucchi nel mio codice di produzione, rispetto a questo abominio di riassegnazione ...
Omer Mor

2
Da CLR via C #: il motivo per cui è stato fatto è perché puoi chiamare il costruttore senza parametri di una struttura in un altro costruttore. Se si desidera inizializzare solo un valore di una struttura e si desidera che gli altri valori siano zero / null (impostazione predefinita), è possibile scrivere public Foo(int bar){this = new Foo(); specialVar = bar;}. Questo non è efficiente e non è davvero giustificato ( specialVarviene assegnato due volte), ma solo FYI. (Questo è il motivo indicato nel libro, non so perché non dovremmo semplicemente farlo public Foo(int bar) : this())
kizzx2

100

Pochi anni fa, quando lavoravamo al programma di lealtà, avevamo un problema con la quantità di punti assegnati ai clienti. Il problema era legato al casting / conversione del doppio in int.

Nel codice seguente:

double d = 13.6;

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

fa i1 == i2 ?

Si scopre che i1! = I2. A causa delle diverse politiche di arrotondamento nell'operatore Converti e cast, i valori effettivi sono:

i1 == 14
i2 == 13

È sempre meglio chiamare Math.Ceiling () o Math.Floor () (o Math.Round con MidpointRounding che soddisfa i nostri requisiti)

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

44
Il cast su un numero intero non viene arrotondato, ma lo interrompe (effettivamente sempre arrotondando per difetto). Quindi questo ha perfettamente senso.
Max Schmeling,

57
@Max: sì, ma perché Convert round?
Stefan Steinegger,

18
@Stefan Steinegger Se tutto quello che ha fatto è stato lanciato, non ci sarebbe motivo di farlo in primo luogo, vero? Si noti inoltre che il nome della classe è Converti non trasmesso.
bug-a-lot

3
In VB: round CInt (). Fix () tronca. Mi ha bruciato una volta ( blog.wassupy.com/2006/01/i-can-believe-it-not-truncating.html )
Michael Haren,

74

Avrebbero dovuto rendere 0 un numero intero anche quando c'è un sovraccarico della funzione enum.

Conoscevo la logica del team di base C # per mappare 0 su enum, ma non è così ortogonale come dovrebbe essere. Esempio da Npgsql .

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

18
Wow, questo è nuovo per me. Strano anche il modo in cui ConverTo.ToIn32 () funziona ma il cast su (int) 0 no. E qualsiasi altro numero> 0 funziona. (Per "opere" intendo chiamare il sovraccarico dell'oggetto.)
Lucas,

Esiste una regola di analisi del codice consigliata per applicare le buone pratiche relative a questo comportamento: msdn.microsoft.com/en-us/library/ms182149%28VS.80%29.aspx Questo link contiene anche una bella descrizione del funzionamento della mappatura 0 .
Chris Clark,

1
@Chris Clark: ho provato a mettere None = 0 sul simbolo enum. comunque il compilatore sceglie enum per 0 e even (int) 0
Michael Buen il

2
IMO avrebbero dovuto introdurre una parola chiave noneche può essere utilizzata convertita in qualsiasi enum e rendere 0 sempre un int e non convertibile implicitamente in un enum.
CodesInChaos

5
ConverTo.ToIn32 () funziona perché il suo risultato non è una costante compiletime. E solo la costante compiletime 0 è convertibile in un enum. Nelle versioni precedenti di .net anche solo il letterale 0avrebbe dovuto essere convertibile in enum. Vedi il blog di Eric Lippert: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspx
CodesInChaos

67

Questo è uno dei più insoliti che abbia mai visto finora (a parte quelli ovviamente qui!):

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

Ti consente di dichiararlo ma non ha alcun reale utilizzo, dal momento che ti chiederà sempre di avvolgere qualsiasi altra classe al centro con un'altra Tartaruga.

[scherzo] Immagino che siano le tartarughe fino in fondo ... [/ scherzo]


34
È possibile creare istanze, tuttavia:class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Marc Gravell

24
Infatti. Questo è il modello che gli enumeratori Java usano con grande efficacia. Lo uso anche nei protocol buffer.
Jon Skeet,

6
RCIX, oh sì, lo è.
Giosuè,

8
Ho usato abbastanza questo modello in fantasiose cose di generici. Permette cose come un clone digitato correttamente o la creazione di istanze di se stesso.
Lucero,

20
Questo è il 'modello di modello curiosamente ricorrente' en.wikipedia.org/wiki/Curiously_recurring_template_pattern
porges

65

Eccone uno che ho scoperto solo di recente ...

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

Quanto sopra sembra folle a prima vista, ma in realtà è legale. No, davvero (anche se ho perso una parte fondamentale, ma non è niente di strano come "aggiungi una classe chiamata IFoo" o "aggiungi un usingalias per indicare IFooun classe").

Vedi se riesci a capire perché, quindi: chi dice che non puoi istanziare un'interfaccia?


1
+1 per "utilizzando alias" - Non ho mai saputo che si potrebbe fare che !
David,

hack nel compilatore per COM Interop :-)
Ion Todirel

Bastardo! Avresti potuto almeno dire "in determinate circostanze" ... Il mio compilatore smentisce!
MA Hanin,

56

Quando un booleano non è né vero né falso?

Bill ha scoperto che puoi hackerare un booleano in modo che se A è vero e B è vero, (A e B) è falso.

Booleani hackerati


134
Quando è FILE_NOT_FOUND, ovviamente!
Greg,

12
Questo è interessante perché significa, matematicamente, che nessuna affermazione in C # è provabile. Ooops.
Simon Johnson,

20
Un giorno scriverò un programma che dipende da questo comportamento, e i demoni dell'inferno più oscuro prepareranno un benvenuto per me. Bwahahahahaha!
Jeffrey L Whitledge,

18
Questo esempio utilizza operatori bit per bit, non logici. Com'è sorprendente?
Josh Lee,

6
Bene, modifica il layout della struttura, ovviamente otterrai strani risultati, non è sorprendente o inaspettato!
Ion Todirel,

47

Arrivo un po 'tardi alla festa, ma ne ho tre quattro cinque:

  1. Se esegui il polling di InvokeRequired su un controllo che non è stato caricato / mostrato, si dice falso - e ti esploderà in faccia se provi a cambiarlo da un altro thread ( la soluzione è fare riferimento a questo. Handle nel creatore del controllo).

  2. Un altro che mi ha fatto inciampare è quello dato un incontro con:

    enum MyEnum
    {
        Red,
        Blue,
    }

    se calcoli MyEnum.Red.ToString () in un altro assembly e tra una volta e l'altra qualcuno ha ricompilato il tuo enum in:

    enum MyEnum
    {
        Black,
        Red,
        Blue,
    }

    in fase di esecuzione, otterrai "Nero".

  3. Avevo un assemblaggio condiviso con alcune costanti utili dentro. Il mio predecessore aveva lasciato un sacco di proprietà get-only dall'aspetto brutto, pensavo che mi sarei liberato del disordine e avrei usato solo const pubblica. Sono stato più che un po 'sorpreso quando VS li ha compilati per i loro valori e non per i riferimenti.

  4. Se si implementa un nuovo metodo di un'interfaccia da un altro assembly, ma si ricostruisce facendo riferimento alla versione precedente di tale assembly, si ottiene TypeLoadException (nessuna implementazione di 'NewMethod'), anche se è stata implementata (vedere qui ).

  5. Dizionario <,>: "L'ordine in cui vengono restituiti gli articoli non è definito". Questo è orribile , perché a volte ti può mordere, ma funziona gli altri, e se hai semplicemente supposto ciecamente che il Dizionario suonerà bene ("perché non dovresti farlo? Pensavo, Elenco lo fa"), devi davvero avere il naso dentro prima di iniziare finalmente a mettere in discussione il tuo presupposto.


6
# 2 è un esempio interessante. Gli enum sono mappature del compilatore su valori integrali. Quindi, anche se non hai assegnato loro esplicitamente i valori, il compilatore lo ha fatto, risultando in MyEnum.Red = 0 e MyEnum.Blue = 1. Quando hai aggiunto Nero, hai ridefinito il valore 0 per mappare da Rosso a Nero. Ho il sospetto che il problema si sarebbe manifestato anche in altri usi, come la serializzazione.
LBushkin,

3
+1 per Invoke richiesto. Alla nostra preferiamo assegnare esplicitamente valori a enum come Red = 1, Blue = 2 in modo che uno nuovo possa essere inserito prima o dopo che risulterà sempre nello stesso valore. È particolarmente necessario se si stanno salvando valori nei database.
TheVillageIdiot

53
Non sono d'accordo sul fatto che # 5 sia un "caso limite". Il dizionario non dovrebbe avere un ordine definito basato sull'inserimento di valori. Se si desidera un ordine definito, utilizzare un Elenco o utilizzare una chiave che può essere ordinata in un modo utile per l'utente oppure utilizzare una struttura di dati completamente diversa.
Wed

21
@Wedge, come forse SortedDictionary?
Allon Guralnek,

4
# 3 accade perché le costanti vengono inserite come letterali ovunque vengano utilizzate (almeno in C #). Il tuo predecessore potrebbe averlo già notato, motivo per cui hanno usato la proprietà get-only. Tuttavia, una variabile di sola lettura (al contrario di una const) funzionerebbe altrettanto bene.
Remoun,

33

VB.NET, nullables e l'operatore ternario:

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

Questo ha richiesto del tempo per il debug, poiché mi aspettavo idi contenereNothing .

Cosa contiene veramente? 0.

Ciò è sorprendente ma in realtà comportamento "corretto": Nothingin VB.NET non è esattamente lo stesso nulldi CLR: Nothingpuò significare nullo default(T)per un tipo di valore T, a seconda del contesto. Nel caso di cui sopra, Ifdeduce Integercome tipo comune Nothinge 5, quindi, in questo caso, Nothingmezzi 0.


Abbastanza interessante, non sono riuscito a trovare questa risposta, quindi ho dovuto creare una domanda . Bene, chi sapeva che la risposta è in questa discussione?
GSerg,

28

Ho trovato un secondo caso davvero strano d'angolo che batte il mio primo da un colpo lungo.

Il metodo String.Equals (String, String, StringComparison) non è in realtà privo di effetti collaterali.

Stavo lavorando su un blocco di codice che aveva questo su una riga da solo nella parte superiore di alcune funzioni:

stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);

La rimozione di quella linea porta a uno overflow dello stack da qualche altra parte nel programma.

Si è scoperto che il codice stava installando un gestore per quello che era essenzialmente un evento BeforeAssemblyLoad e che cercava di fare

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

Ormai non avrei dovuto dirtelo. L'uso di una cultura che non è mai stata utilizzata in precedenza in un confronto di stringhe provoca un caricamento dell'assembly. InvariantCulture non fa eccezione.


Immagino che "caricare un assieme" sia un effetto collaterale, dato che puoi osservarlo con BeforeAssemblyLoad!
Jacob Krall,

2
Wow. Questo è un colpo perfetto nella gamba del manutentore. Immagino che scrivere un gestore BeforeAssemblyLoad possa portare a molte sorprese del genere.
parrucca

20

Ecco un esempio di come è possibile creare una struttura che causa il messaggio di errore "Tentativo di leggere o scrivere memoria protetta. Questa è spesso un'indicazione che l'altra memoria è corrotta". La differenza tra successo e fallimento è molto sottile.

Il seguente test unitario dimostra il problema.

Vedi se riesci a capire cosa è andato storto.

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

Mi fa male la testa ... Perché non funziona?
Jasonh,

2
Ho scritto questo qualche mese fa, ma non ricordo perché sia ​​successo esattamente.
cbp

10
Sembra un bug del compilatore; le += 500chiamate: ldc.i4 500(spinge 500 come Int32), quindi call valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)- quindi tratta come decimal(96 bit) senza alcuna conversione. Se lo usi += 500Mlo fa bene. Sembra semplicemente che il compilatore pensi di poterlo fare in un modo (presumibilmente a causa dell'operatore int implicito) e quindi decide di farlo in un altro modo.
Marc Gravell

1
Ci scusiamo per il doppio post, ecco una spiegazione più qualificata. Aggiungerò questo, sono stato morso da questo e questo fa schifo, anche se capisco perché succede. Per me questa è una sfortunata limitazione del tipo struttura / valore. bytes.com/topic/net/answers/…
Bennett Dill

2
@Ben ottenere errori del compilatore o la modifica che non influisce sulla struttura originale va bene. Una violazione di accesso è una bestia abbastanza diversa. Il runtime non dovrebbe mai essere lanciato se stai scrivendo un codice gestito puro sicuro.
CodesInChaos

18

C # supporta le conversioni tra matrici ed elenchi purché le matrici non siano multidimensionali e vi sia una relazione di ereditarietà tra i tipi e i tipi sono tipi di riferimento

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

Si noti che questo non funziona:

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
L'esempio IList <T> è solo un cast, perché string [] implementa già ICloneable, IList, ICollection, IEnumerable, IList <string>, ICollection <string> e IEnumerable <string>.
Lucas,

15

Questo è il più strano che ho incontrato per caso:

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

Usato come segue:

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

Lancerà a NullReferenceException. Si scopre che le aggiunte multiple sono compilate dal compilatore C # per una chiamata a String.Concat(object[]). Prima di .NET 4, c'è un bug proprio in quel sovraccarico di Concat in cui l'oggetto è controllato per null, ma non il risultato di 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;

Questo è un bug di ECMA-334 §14.7.4:

L'operatore binario + esegue la concatenazione di stringhe quando uno o entrambi gli operandi sono di tipo string. Se un operando di concatenazione di stringhe è null, viene sostituita una stringa vuota. Altrimenti, qualsiasi operando senza stringa viene convertito nella sua rappresentazione di stringa richiamando il ToStringmetodo virtuale ereditato dal tipo object. Se ToStringrestituisce null, viene sostituita una stringa vuota.


3
Hmm, ma posso immaginare questo errore in quanto .ToStringnon dovrebbe mai restituire null, ma string.Empty. Tuttavia ed errore nel quadro.
Dykam

12

Interessante - quando ho visto per la prima volta che ho pensato che fosse qualcosa che il compilatore C # stava verificando, ma anche se si emette direttamente l'IL per rimuovere qualsiasi possibilità di interferenza, ciò accade ancora, il che significa che è davvero il newobjcodice operativo che sta facendo il controllo.

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

Ciò equivale anche a trueverificare se string.Emptyciò significa che questo codice operativo deve avere un comportamento speciale per le stringhe vuote interne.


di non essere un genio intelligente o altro ma hai sentito parlare del riflettore ? è abbastanza utile in questo tipo di casi;
RCIX,

3
Non sei intelligente; ti manca il punto - volevo generare IL specifici per questo caso. E comunque, dato che Reflection.Emit è banale per questo tipo di scenario, probabilmente è veloce come scrivere un programma in C # quindi aprire il riflettore, trovare il binario, trovare il metodo, ecc ... E non devo nemmeno lascia l'IDE per farlo.
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

L'output è "Tentativo di leggere la memoria protetta. Questa è un'indicazione che l'altra memoria è danneggiata."


1
Interessante! Sembra un bug del compilatore, però; Ho portato su C # e funziona benissimo. Detto questo, ci sono molti problemi con le eccezioni generate in Load e si comporta in modo diverso con / senza un debugger: è possibile intercettarlo con un debugger, ma non senza (in alcuni casi).
Marc Gravell

Spiacenti, ho dimenticato, è necessario aggiungere la casella combinata al modulo prima che accada.
Giosuè,

Questo ha a che fare con l'inizializzazione del dialogo usando un SEH come una sorta di orribile meccanismo di comunicazione interna? Ricordo vagamente qualcosa del genere in Win32.
Daniel Earwicker,

1
Questo è lo stesso problema cbp sopra. Il tipo di valore che viene restituito è una copia, quindi qualsiasi riferimento a qualsiasi proprietà derivante da detta copia è diretto a bit-bucket land ... bytes.com/topic/net/answers/…
Bennett Dill

1
No. Non ci sono strutture qui. L'ho effettivamente eseguito il debug. Aggiunge un NULL alla raccolta di elementi dell'elenco della casella combinata nativa che causa un arresto ritardato.
Giosuè,

10

PropertyInfo.SetValue () può assegnare ints a enum, ints a null nulla, enum a enumerabili nulla, ma non in en nullable.

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

Descrizione completa qui


10

Cosa succede se si dispone di una classe generica che ha metodi che potrebbero essere resi ambigui a seconda degli argomenti del tipo? Mi sono imbattuto in questa situazione di recente scrivendo un dizionario a due vie. Volevo scrivere Get()metodi simmetrici che avrebbero restituito l'opposto di qualunque argomento fosse passato. Qualcosa come questo:

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

Va tutto bene se crei un'istanza in cui T1e di T2tipi diversi:

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

Ma se T1e T2sono uguali (e probabilmente se uno fosse una sottoclasse di un altro), è un errore del compilatore:

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

È interessante notare che tutti gli altri metodi nel secondo caso sono ancora utilizzabili; sono solo le chiamate al metodo ora ambiguo che causa un errore del compilatore. Caso interessante, anche se un po 'improbabile e oscuro.


Gli avversari del sovraccarico del metodo adoreranno questo ^^.
Christian Klauser,

1
Non lo so, questo ha perfettamente senso per me.
Scott Whitlock,

10

Puzzle di accessibilità C #


La seguente classe derivata sta accedendo a un campo privato dalla sua classe base e il compilatore guarda silenziosamente dall'altra parte:

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

Il campo è infatti privato:

private int m_basePrivateField = 0;

Vuoi indovinare come possiamo compilare questo codice?

.

.

.

.

.

.

.

Risposta


Il trucco è dichiarare Derivedcome una classe interna di Base:

public class Base
{
    private int m_basePrivateField = 0;

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

Alle classi interne viene dato pieno accesso ai membri della classe esterna. In questo caso, anche la classe interna deriva dalla classe esterna. Questo ci consente di "rompere" l'incapsulamento dei membri privati.


Questo in realtà è ben documentato; msdn.microsoft.com/en-us/library/ms173120%28VS.80%29.aspx . Può essere una funzione utile a volte, specialmente se la classe esterna è statica.

Sì, ovviamente è documentato. Tuttavia, pochissime persone hanno risolto questo enigma, quindi ho pensato che fosse un bel pezzo di curiosità.
Omer Mor,

2
Sembra che avresti una possibilità molto forte di un overflow dello stack avendo una classe interna eredita il suo proprietario ...
Jamie Treworgy,

Un altro caso simile (e perfettamente corretto) è che un oggetto può accedere a un membro privato di un altro oggetto dello stesso tipo:class A { private int _i; public void foo(A other) { int res = other._i; } }
Olivier Jacot-Descombes

10

Ho appena trovato una cosa carina oggi:

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

Questo genera errore di compilazione.

La chiamata al metodo "Inizializza" deve essere inviata in modo dinamico, ma non può essere perché fa parte di un'espressione di accesso di base. Prendi in considerazione la possibilità di lanciare argomenti dinamici o eliminare l'accesso di base.

Se scrivo base.Initialize (roba come oggetto); funziona perfettamente, tuttavia questa sembra essere una "parola magica" qui, dal momento che fa esattamente lo stesso, tutto è ancora ricevuto come dinamico ...


8

In un'API che stiamo utilizzando, i metodi che restituiscono un oggetto dominio potrebbero restituire uno speciale "oggetto null". Nell'implementazione di questo, l'operatore di confronto e il Equals()metodo vengono sostituiti per essere restituiti truese confrontati connull .

Quindi un utente di questa API potrebbe avere un codice come questo:

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

o forse un po 'più prolisso, in questo modo:

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

dove GetDefault()è un metodo che restituisce un valore predefinito che vogliamo usare al posto di null. La sorpresa mi ha colpito quando stavo usando ReSharper e seguendo la sua raccomandazione di riscrivere uno di questi a quanto segue:

return test ?? GetDefault();

Se l'oggetto test è un oggetto null restituito dall'API invece che corretto null, il comportamento del codice è ora cambiato, poiché l'operatore di coalescenza null verifica effettivamente null, non in esecuzione operator=o Equals().


1
non proprio un caso ac # angolo, ma caro signore che l'ha pensato?!?
Ray Booysen,

Questo codice non utilizza solo tipi nullable? Quindi ReSharper consiglia il "??" uso. Come ha detto Ray, non avrei pensato che fosse un caso angolare; o mi sbaglio?
Tony,

1
Sì, i tipi sono nullable e c'è anche un NullObject in aggiunta. Se è un caso angolare, non lo so, ma almeno è un caso in cui 'if (a! = Null) restituisce a; ritorno b; ' non è lo stesso di 'return a ?? b'. Sono assolutamente d'accordo che si tratti di un problema con il design del framework / API - overloading == null per restituire true su un oggetto non è certamente una buona idea!
Tor Livar,

8

Considera questo strano caso:

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

Se Basee Derivedsono dichiarati nello stesso assembly, il compilatore renderà Base::Methodvirtuale e sigillato (nel CIL), anche seBase non implementa l'interfaccia.

Se Basee si Derivedtrovano in assiemi diversi, durante la compilazione Deriveddell'assembly, il compilatore non modificherà l'altro assembly, quindi introdurrà un membro Derivedche sarà un'implementazione esplicita in MyInterface::Methodquanto delegherà la chiamata aBase::Method .

Il compilatore deve farlo per supportare l'invio polimorfico per quanto riguarda l'interfaccia, ovvero deve rendere virtuale quel metodo.


Sembra davvero strano. Dovrà indagare più tardi :)
Jon Skeet il

@Jon Skeet: l'ho trovato durante la ricerca di strategie di implementazione per ruoli in C # . Sarebbe bello avere il tuo feedback su quello!
Jordão,

7

Quanto segue potrebbe essere una conoscenza generale che mi mancava semplicemente, ma eh. Qualche tempo fa, abbiamo avuto un bug case che includeva proprietà virtuali. Estrarre un po 'il contesto, considerare il codice seguente e applicare il punto di interruzione all'area specificata:

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

Mentre si è nel Derivedcontesto dell'oggetto, è possibile ottenere lo stesso comportamento quando si aggiunge base.Propertycome orologio o si digitabase.Property nel quickwatch.

Mi ci è voluto un po 'di tempo per capire cosa stesse succedendo. Alla fine sono stato illuminato dal Quickwatch. Quando si accede a Quickwatch ed esplora l' Derivedoggetto d (o dal contesto dell'oggetto this) e si seleziona il campo base, il campo di modifica nella parte superiore di Quickwatch visualizza il seguente cast:

((TestProject1.Base)(d))

Ciò significa che se la base viene sostituita come tale, la chiamata sarebbe

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

per gli orologi, Quickwatch e le descrizioni dei comandi del mouse per il debug, e avrebbe quindi senso che venga visualizzato "AWESOME"invece di "BASE_AWESOME"considerare il polimorfismo. Non sono ancora sicuro del perché lo trasformerebbe in un cast, un'ipotesi è che callpotrebbe non essere disponibile dal contesto di quei moduli, e solo callvirt.

Ad ogni modo, ciò ovviamente non altera nulla in termini di funzionalità, Derived.BasePropertytornerà comunque davvero "BASE_AWESOME", e quindi questa non era la radice del nostro bug al lavoro, semplicemente un componente confuso. Tuttavia, ho trovato interessante il modo in cui potrebbe fuorviare gli sviluppatori che non sarebbero a conoscenza di questo fatto durante le loro sessioni di debug, specialmente se Basenon sono esposti nel progetto ma piuttosto indicati come una DLL di terze parti, risultando in Dev che dicono solo:

"Oi, aspetta ... cosa? Omg che DLL è come ... facendo qualcosa di divertente"


Non è niente di speciale, è solo il modo in cui le sostituzioni funzionano.
configuratore

7

Questo è piuttosto difficile da superare. Mi ci sono imbattuto mentre stavo cercando di realizzare un'implementazione di RealProxy che supporti veramente Begin / EndInvoke (grazie a MS per aver reso impossibile fare questo senza orribili hack). Questo esempio è fondamentalmente un bug nel CLR, il percorso del codice non gestito per BeginInvoke non convalida che il messaggio di ritorno da RealProxy.PrivateInvoke (e il mio override Invoke) restituisca un'istanza di un IAsyncResult. Una volta restituito, il CLR viene incredibilmente confuso e perde qualsiasi idea di cosa stia succedendo, come dimostrato dai test in fondo.

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

Produzione:

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

Non sono sicuro che diresti che questa è una stranezza di Windows Vista / 7 o una stranezza .Net ma mi ha fatto grattare la testa per un po '.

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 il file verrà effettivamente scritto C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt


2
Questo è davvero un miglioramento della sicurezza di Vista (non 7, afaik). Ma la cosa bella è che puoi leggere e aprire il file con il percorso dei file di programma, mentre se guardi lì con Explorer non c'è nulla. Questo mi ha portato quasi un giorno di lavoro a un cliente prima che finalmente lo scoprissi.
Henri

È sicuramente anche una cosa di Windows 7. Questo è quello che stavo usando quando l'ho incontrato. Capisco il ragionamento alla base, ma era ancora frustrante da capire.
Spencer Ruport

In Vista / Win 7 (anche tecnicamente winXP) le app dovrebbero scrivere in una cartella AppData nella terra della cartella Users, come i suoi dati tecnicamente dell'utente. Le applicazioni non dovrebbero mai scrivere su programfiles / windows / system32 / etc, a meno che non abbiano privilegi di amministratore, e tali privilegi dovrebbero essere lì solo per dire aggiornare il programma / disinstallarlo / installare nuove funzionalità. MA! Non scrivere ancora su system32 / windows / etc :) Se hai eseguito quel codice sopra come admin (tasto destro> esegui come admin) dovrebbe teoricamente scrivere nella cartella dell'app dei file di programma.
Steve Syfuhs


6

Hai mai pensato che il compilatore C # potesse generare CIL non valido? Esegui questo e otterrai un 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));
  }
}

Non so come vada a buon fine nel compilatore C # 4.0.

EDIT : questo è l'output dal mio sistema:

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]

Funziona con me sia con il compilatore C # 3.5 che con il compilatore C # 4 ...
Jon Skeet

Nel mio sistema, non funziona. Incollerò l'output nella domanda.
Jordão,

Non è riuscito per me in .NET 3.5 (non ho tempo di testare 4.0). E posso replicare il problema con il codice VB.NET.
Mark Hurd,

3

C'è qualcosa di veramente eccitante in C #, il modo in cui gestisce le chiusure.

Invece di copiare i valori della variabile dello stack nella variabile free di chiusura, fa quella magia del preprocessore che avvolge tutte le occorrenze della variabile in un oggetto e quindi lo sposta dallo stack - direttamente nell'heap! :)

Immagino che questo renda il linguaggio C # ancora più funzionalmente completo (o lambda-complete huh)) rispetto allo stesso ML (che usa lo stack value che copia AFAIK). F # ha anche quella funzione, come fa C #.

Questo mi fa molto piacere, grazie ragazzi MS!

Non è un caso strano o angolare però ... ma qualcosa di veramente inaspettato da un linguaggio VM basato su stack :)


3

Da una domanda che non ho posto molto tempo fa:

L'operatore condizionale non può eseguire il cast implicitamente?

Dato:

Bool aBoolValue;

Dove aBoolValueè assegnato Vero o Falso;

Non verrà compilato quanto segue:

Byte aByteValue = aBoolValue ? 1 : 0;

Ma questo avrebbe:

Int anIntValue = aBoolValue ? 1 : 0;

Anche la risposta fornita è abbastanza buona.


anche se sono ve not test it Isicuro che funzionerà: Byte aByteValue = aBoolValue? (Byte) 1: (Byte) 0; Oppure: Byte aByteValue = (Byte) (aBoolValue? 1: 0);
Alex Pacurar,

2
Sì, Alex, funzionerebbe. La chiave sta nel casting implicito. 1 : 0solo lancerà implicitamente int, non Byte.
MPelletier,

2

Lo scoping in c # a volte è davvero bizzarro. Lascia che ti faccia un esempio:

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

OleDbCommand command = SQLServer.CreateCommand();

Questo non riesce a compilare, perché il comando è redeclared? Ci sono alcune ipotesi interessanti sul perché funzioni in questo modo su StackOverflow e nel mio blog .


34
Non lo considero particolarmente bizzarro. Quello che chiami "codice perfettamente corretto" nel tuo blog è perfettamente errato in base alle specifiche della lingua. Può essere corretto in una lingua immaginaria faresti come C # per essere, ma le specifiche linguaggio è del tutto evidente che in C # è valido.
Jon Skeet,

7
Bene è valido in C / C ++. E dato che è C # mi sarebbe piaciuto che funzionasse ancora. Ciò che mi dà più fastidio è che il compilatore non ha motivo di farlo. Non è difficile fare scoping nidificati. Immagino che tutto si riduca all'elemento di minima sorpresa. Significa che può essere che le specifiche dicono questo e quello, ma ciò non mi aiuta molto se è completamente illogico che si comporti in quel modo.
Anders Rune Jensen,

6
C #! = C / C ++. Ti piacerebbe anche usare Cout << "Hello World!" << endl; invece di Console.WriteLine ("Hello World!") ;? Inoltre non è illogico, basta leggere le specifiche.
Kredns,

9
Sto parlando di regole di scoping che fanno parte del nucleo della lingua. Stai parlando della libreria standard. Ma ora mi è chiaro che dovrei semplicemente leggere le minuscole specifiche del linguaggio c # prima di iniziare a programmare in esso.
Anders Rune Jensen,

6
Eric Lippert ha pubblicato i motivi per cui C # è stato progettato in questo modo di recente: blogs.msdn.com/ericlippert/archive/2009/11/02/… . Il riassunto è perché è meno probabile che le modifiche abbiano conseguenze indesiderate.
Helephant,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.