Jaki jest najdziwniejszy przypadek narożny, jaki widziałeś w C # lub .NET? [Zamknięte]


322

Zbieram kilka przypadków narożnych i łamigłówki i zawsze chciałbym usłyszeć więcej. Strona tak naprawdę zawiera tylko bity i boby w języku C #, ale uważam też, że podstawowe rzeczy .NET też są interesujące. Na przykład oto taki, którego nie ma na stronie, ale który wydaje mi się niesamowity:

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

Spodziewałbym się, że wypisze Fałsz - w końcu „nowy” (z typem referencyjnym) zawsze tworzy nowy obiekt, prawda? Specyfikacje zarówno dla C #, jak i CLI wskazują, że powinno. Cóż, nie w tym konkretnym przypadku. Wyświetla wartość True i działał na każdej wersji frameworka, z którą ją testowałem. (Wprawdzie nie próbowałem tego na Mono ...)

Żeby było jasne, to tylko przykład tego, czego szukam - nie szukałem szczególnie dyskusji / wyjaśnienia tej dziwności. (Nie jest to to samo, co normalne internowanie łańcucha; w szczególności internowanie łańcucha zwykle nie występuje, gdy wywoływany jest konstruktor.) Naprawdę prosiłem o podobne dziwne zachowanie.

Czy są tam jakieś inne klejnoty?


64
Testowany na Mono 2.0 rc; zwraca True
Marc Gravell

10
oba ciągi są ciągami. Pusty i wygląda na to, że framework zachowuje tylko jedno odniesienie do tego
Adrian Zanescu

34
To kwestia zachowania pamięci. Wyszukaj w dokumentacji MSDN ciąg metody statycznej. CLR utrzymuje pulę ciągów. Dlatego ciągi o identycznej treści pojawiają się jako odniesienia do tej samej pamięci, tj. Obiektu.
John Leidegren

12
@John: Internowanie ciągów zachodzi automatycznie tylko w przypadku literałów . W tym przypadku tak nie jest. @DanielSwe: Internowanie nie jest wymagane, aby ciągi były niezmienne. Fakt, że jest to możliwe, jest niezłą konsekwencją niezmienności, ale i tak nie dzieje się tutaj normalne internowanie.
Jon Skeet

3
Szczegół implementacji, który powoduje takie zachowanie, wyjaśniono tutaj: blog.liranchen.com/2010/08/brain-teasing-with-strings.html
Liran

Odpowiedzi:


394

Wydaje mi się, że już ci to pokazywałem, ale podoba mi się tutaj zabawa - trzeba było trochę debugowania, aby wyśledzić! (oryginalny kod był oczywiście bardziej złożony i subtelny ...)

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

Czym więc był T ...

Odpowiedź: dowolna Nullable<T>- taka jak int?. Wszystkie metody są zastępowane, z wyjątkiem GetType (), które nie mogą być; więc jest rzutowany (w ramce) na obiekt (a zatem na null), aby wywołać object.GetType () ... który wywołuje null ;-p


Aktualizacja: fabuła gęstnieje ... Ayende Rahien rzucił podobne wyzwanie na swoim blogu , ale z 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?");
}

Ale można go pokonać! Korzystanie z tej samej pośredniczości, z której korzystają rzeczy takie jak zdalne sterowanie; ostrzeżenie - następujące jest czyste zło :

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

W tym miejscu new()wywołanie jest przekierowywane do proxy ( MyFunnyProxyAttribute), które zwraca null. A teraz idź i umyj oczy!


9
Dlaczego nie można zdefiniować Nullable <T> .GetType ()? Czy wynik nie powinien być typeof (Nullable <T>)?
Drew Noakes,

69
Drew: problem polega na tym, że GetType () nie jest wirtualny, więc nie jest zastępowany - co oznacza, że ​​wartość jest zapakowana w wywołanie metody. Pole staje się odwołaniem zerowym, stąd NRE.
Jon Skeet,

10
@Rysował; dodatkowo istnieją specjalne reguły boksu dla Nullable <T>, co oznacza, że ​​puste pole Nullable <T> ma wartość null, a nie pole zawierające puste Nullable <T> (i null un-box do pustego Nullable <T >)
Marc Gravell

29
Bardzo bardzo fajne. W pewien niechlujny sposób. ;-)
Konrad Rudolph

6
Ograniczenie dla konstruktorów, 10.1.5 w specyfikacji językowej C # 3.0
Marc Gravell

216

Zaokrąglanie bankowców.

Ten nie jest tak bardzo błędem lub usterką kompilatora, ale z pewnością dziwnym narożnym przypadkiem ...

.NET Framework wykorzystuje schemat lub zaokrąglanie znane jako Zaokrąglanie bankiera.

W zaokrąglaniu bankierów liczby 0,5 są zaokrąglane do najbliższej liczby parzystej, więc

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

Może to prowadzić do nieoczekiwanych błędów w obliczeniach finansowych opartych na bardziej znanym zaokrąglaniu Round-Half-Up.

Dotyczy to również Visual Basic.


22
To też wydawało mi się dziwne. To jest przynajmniej do czasu, aż będę miał dużą listę liczb i obliczyć ich sumę. Wtedy zdajesz sobie sprawę, że jeśli po prostu zaokrąglisz w górę, skończysz z potencjalnie ogromną różnicą w stosunku do sumy nie zaokrąglonych liczb. Bardzo źle, jeśli wykonujesz obliczenia finansowe!
Tsvetomir Tsonev

255
Jeśli ludzie nie wiedzą, możesz zrobić: Math.Round (x, MidpointRounding.AwayFromZero); Aby zmienić schemat zaokrąglania.
ICR

26
Z dokumentacji: Zachowanie tej metody jest zgodne ze standardem IEEE 754, sekcja 4. Ten rodzaj zaokrąglania jest czasem nazywany zaokrąglaniem do najbliższego lub zaokrąglania bankierskiego. Minimalizuje błędy zaokrąglania wynikające z konsekwentnego zaokrąglania wartości punktu środkowego w jednym kierunku.
ICR

8
Zastanawiam się, czy właśnie dlatego int(fVal + 0.5)tak często widzę nawet w językach, które mają wbudowaną funkcję zaokrąglania.
Ben Blank

32
Jak na ironię, kiedyś pracowałem w banku, a inni programiści zaczęli o tym mówić , sądząc, że zaokrąglenie zostało przerwane w ramach
dan

176

Co zrobi ta funkcja, jeśli zostanie wywołana jako Rec(0)(nie w debuggerze)?

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

Odpowiedź:

  • W 32-bitowym JIT powinno to spowodować wyjątek StackOverflowException
  • W 64-bitowym JIT powinien wypisać wszystkie liczby na int.MaxValue

Wynika to z faktu, że 64-bitowy kompilator JIT stosuje optymalizację wywołania ogona , podczas gdy 32-bitowy JIT nie.

Niestety nie mam pod ręką 64-bitowej maszyny, aby to sprawdzić, ale metoda spełnia wszystkie warunki optymalizacji wywołania ogona. Jeśli ktoś ma taki, chciałbym sprawdzić, czy to prawda.


10
Musi zostać skompilowany w trybie wydania, ale na pewno działa na x64 =)
Neil Williams

3
być może warto zaktualizować odpowiedź, gdy pojawi się VS 2010, ponieważ wszystkie obecne zespoły JIT
wykonają

3
Właśnie wypróbowałem VS2010 Beta 1 na 32-bitowym WinXP. Nadal otrzymuj StackOverflowException.
squillman

130
+1 za StackOverflowException
calvinlough

7
Że ++totalnie mnie wyrzuciło. Nie możesz zadzwonić Rec(i + 1)jak normalna osoba?
konfigurator

111

Przypisz to!


To jest to, o które lubię pytać na imprezach (prawdopodobnie dlatego już mnie nie zaproszono):

Czy potrafisz skompilować następujący fragment kodu?

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

Łatwym oszustwem może być:

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

Ale prawdziwe rozwiązanie jest następujące:

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

Jest więc mało znany fakt, że typy wartości (struktury) mogą ponownie przypisać swoją thiszmienną.


3
Klasy C ++ też mogą to zrobić ... jak niedawno odkryłem, tylko po to, by zostać wykrzyczanym za faktyczne wykorzystanie go do optymalizacji: p
mpen

1
Właściwie użyłem nowego w miejscu. Chciałem tylko efektywnego sposobu na aktualizację wszystkich pól :)
mpen

70
To także oszustwo: //this = new Teaser();:-)
AndrewJacksonZA

17
:-) Wolę te kody w moim kodzie produkcyjnym, niż tę obrzydliwość zmiany przypisania ...
Omer Mor

2
Z CLR przez C #: Powodem, dla którego to zrobili, jest to, że można wywołać konstruktora bez parametrów parametru struct w innym konstruktorze. Jeśli chcesz zainicjować tylko jedną wartość struktury i chcesz, aby pozostałe wartości były równe zero / null (domyślnie), możesz napisać public Foo(int bar){this = new Foo(); specialVar = bar;}. To nie jest wydajne i nie jest tak naprawdę uzasadnione ( specialVarprzypisywane dwukrotnie), ale po prostu FYI. (To jest powód podany w książce, nie wiem, dlaczego nie powinniśmy po prostu to robić public Foo(int bar) : this())
kizzx2 30.01.2011

100

Kilka lat temu, pracując nad programem lojalnościowym, mieliśmy problem z ilością punktów przyznawanych klientom. Problem dotyczył rzutowania / konwersji podwójnej na int.

W kodzie poniżej:

double d = 13.6;

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

czy i1 == i2 ?

Okazuje się, że i1! = I2. Z powodu różnych zasad zaokrąglania w operatorze Konwertuj i rzutuj rzeczywiste wartości to:

i1 == 14
i2 == 13

Zawsze lepiej jest wywoływać Math.Ceiling () lub Math.Floor () (lub Math.Round z MidpointRounding, który spełnia nasze wymagania)

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

44
Rzucanie na liczbę całkowitą nie zaokrągla, tylko ją kroi (zawsze zaokrąglając w dół). To ma więc sens.
Max Schmeling

57
@Max: tak, ale dlaczego Convert round?
Stefan Steinegger

18
@Stefan Steinegger Gdyby wszystko, co zostało zrobione, zostałoby obsadzone, po pierwsze, nie byłoby żadnego powodu, prawda? Zauważ też, że nazwa klasy to Convert not Cast.
bug-a-lot

3
W VB: rundy CInt (). Fix () obcina. Spalił mnie raz ( blog.wassupy.com/2006/01/i-can-believe-it-not-truncating.html )
Michael Haren

74

Powinny one uczynić 0 liczbą całkowitą, nawet gdy występuje przeciążenie funkcji wyliczeniowej.

Znałem uzasadnienie zespołu C # dla odwzorowania 0 na wyliczanie, ale nadal nie jest tak ortogonalne, jak powinno być. Przykład z Npgsql .

Przykład testowy:

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, to dla mnie nowe. Dziwnie też działa ConverTo.ToIn32 (), ale rzutowanie na (int) 0 nie. I każda inna liczba> 0 działa. (Przez „działa” mam na myśli wywołanie przeciążenia obiektu.)
Lucas

Istnieje zalecana reguła analizy kodu w celu egzekwowania dobrych praktyk dotyczących tego zachowania: msdn.microsoft.com/en-us/library/ms182149%28VS.80%29.aspx Ten link zawiera również ładny opis działania odwzorowania 0 .
Chris Clark,

1
@Chris Clark: Próbowałem umieścić None = 0 na symbolu wyliczenia. wciąż kompilator wybiera wyliczenie dla 0, a nawet (int) 0
Michael Buen

2
IMO powinni wprowadzić słowo kluczowe, nonektórego można użyć do konwersji na dowolny wyliczenie, i ustawić 0 zawsze na liczbę całkowitą i niejawnie zamienialną na wyliczenie.
CodesInChaos

5
ConverTo.ToIn32 () działa, ponieważ jego wynik nie jest stałą czasu kompilacji. I tylko stała kompilacji 0 jest konwertowalna na wyliczenie. We wcześniejszych wersjach .net nawet literał 0mógł być konwertowany na enum. Zobacz blog Erica Lipperta: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspx
CodesInChaos

67

Jest to jeden z najbardziej niezwykłych, jakie do tej pory widziałem (oprócz tych tutaj oczywiście!):

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

Pozwala ci to zadeklarować, ale nie ma większego sensu, ponieważ zawsze poprosi cię o zawinięcie dowolnej klasy w centrum innym Żółwiem.

[żart] Myślę, że to żółwie do samego końca ... [/ żart]


34
Możesz jednak tworzyć instancje:class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Marc Gravell

24
W rzeczy samej. Jest to wzorzec, który wykorzystuje wyliczenia Java z doskonałym skutkiem. Używam go również w buforach protokołów.
Jon Skeet

6
RCIX, och tak to jest.
Jozuego

8
Użyłem tego wzoru dość często w fantazyjnych rodzajach. Pozwala na takie rzeczy jak poprawnie wpisany klon lub tworzenie własnych instancji.
Lucero,

20
To jest „ciekawie powtarzający się wzorzec szablonu” en.wikipedia.org/wiki/Curiously_recurring_template_pattern
wtorki

65

Oto jeden, o którym dowiedziałem się niedawno ...

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

Powyższe na pierwszy rzut oka wygląda na szalone, ale w rzeczywistości jest legalne. Nie, naprawdę (chociaż przegapiłem kluczową część, ale nie jest to nic hackującego, jak „dodaj klasę o nazwie IFoo” lub „dodaj usingalias, aby wskazać IFoona klasa").

Sprawdź, czy możesz dowiedzieć się, dlaczego: Kto mówi, że nie możesz utworzyć interfejsu?


1
+1 dla „używając pseudonimu” - nigdy nie wiedział, że może zrobić to !
David,

włamać się do kompilatora dla COM Interop :-)
Ion Todirel

Ty draniu! Mogłeś przynajmniej powiedzieć „w pewnych okolicznościach” ... Mój kompilator się nie zgadza!
MA Hanin,

56

Kiedy Boolean nie jest ani prawdziwy, ani fałszywy?

Bill odkrył, że możesz zhakować wartość logiczną, aby jeśli A to prawda, a B to prawda, (A i B) to fałsz.

Zhakowane booleany


134
Oczywiście kiedy jest FILE_NOT_FOUND!
Greg

12
Jest to interesujące, ponieważ oznacza, matematycznie, że żadne stwierdzenie w języku C # nie jest możliwe do udowodnienia. Ups
Simon Johnson

20
Pewnego dnia napiszę program, który zależy od tego zachowania, a demony z najciemniejszego piekła przygotują dla mnie powitanie. Bwahahahahaha!
Jeffrey L Whitledge

18
W tym przykładzie użyto operatorów bitowych, a nie logicznych. Jak to jest zaskakujące?
Josh Lee

6
Hakuje układ struktury, oczywiście otrzymasz dziwne wyniki, to nie jest takie zaskakujące ani nieoczekiwane!
Ion Todirel

47

Przyjeżdżam trochę na imprezę, ale mam trzy cztery pięć:

  1. Jeśli sondujesz InvokeRequired na formancie, który nie został załadowany / pokazany, powie „false” - i wysadzi ci się w twarz, jeśli spróbujesz go zmienić z innego wątku ( rozwiązaniem jest odwołanie się do tego. Uchwyt w twórcy kontrola).

  2. Kolejnym, który mnie zaskoczył, jest zestaw z:

    enum MyEnum
    {
        Red,
        Blue,
    }

    jeśli obliczysz MyEnum.Red.ToString () w innym zestawie, a pomiędzy czasami ktoś ponownie skompiluje twoje wyliczenie do:

    enum MyEnum
    {
        Black,
        Red,
        Blue,
    }

    w czasie wykonywania pojawi się „Czarny”.

  3. Miałem wspólny zestaw z kilkoma przydatnymi stałymi. Mój poprzednik zostawił mnóstwo brzydko wyglądających właściwości „tylko get”, myślałem, że pozbędę się bałaganu i po prostu użyję publicznego const. Byłem bardziej niż trochę zaskoczony, gdy VS skompilował je według ich wartości, a nie odniesień.

  4. Jeśli wdrożenie nowej metody interfejsu od innego zespołu, ale odbudować przedstawieniu starą wersję, że zespół, masz TypeLoadException (brak wdrożenia „NewMethod”), nawet jeśli zostały wdrożone go (patrz tutaj ).

  5. Słownik <,>: „Kolejność zwracania elementów jest niezdefiniowana”. To jest okropne , ponieważ może cię czasem gryźć, ale pracuj nad innymi, a jeśli po prostu na ślepo zakładasz, że Słownik będzie dobrze grał („dlaczego nie? Myślałem, że Lista”), naprawdę musisz miej w nim nos, zanim w końcu zaczniesz kwestionować swoje przypuszczenia.


6
# 2 jest interesującym przykładem. Wyliczenia są odwzorowaniami kompilatora na wartości całkowite. Tak więc, mimo że nie przypisałeś im wyraźnie tych wartości, kompilator tak zrobił, w wyniku czego MyEnum.Red = 0 i MyEnum.Blue = 1. Po dodaniu czerni zmieniłeś wartość 0 na mapowanie z czerwonej na czarną. Podejrzewam, że problem pojawiłby się także w innych zastosowaniach, takich jak serializacja.
LBushkin

3
Wymagane +1 dla Invoke. Naszym wolimy jawnie przypisywać wartości do wyliczeń, takich jak Czerwony = 1, Niebieski = 2, aby nowy mógł zostać wstawiony przed lub po, zawsze będzie miał tę samą wartość. Jest to szczególnie konieczne, jeśli zapisujesz wartości w bazach danych.
TheVillageIdiot

53
Nie zgadzam się, że nr 5 to „przypadek na krawędzi”. Słownik nie powinien mieć określonej kolejności opartej na wstawianiu wartości. Jeśli chcesz zdefiniować kolejność, użyj Listy lub klucza, który można posortować w sposób, który jest dla Ciebie przydatny, lub użyj zupełnie innej struktury danych.
Wedge

21
@ Wedge, może jak SortedDictionary?
Allon Guralnek

4
# 3 dzieje się, ponieważ stałe są wstawiane jako literały wszędzie, gdzie są używane (przynajmniej w języku C #). Twój poprzednik mógł już to zauważyć i dlatego użył właściwości get-only. Jednak zmienna tylko do odczytu (w przeciwieństwie do stałej) działałaby równie dobrze.
Remoun

33

VB.NET, nullables i operator trójskładnikowy:

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

Debugowanie zajęło mi trochę czasu, ponieważ spodziewałem isię zawierać Nothing.

Co tak naprawdę zawiera? 0.

Jest to zaskakujące, ale w rzeczywistości „poprawne” zachowanie: Nothingw VB.NET nie jest dokładnie takie samo jak nullw CLR: Nothingmoże oznaczać nulllub default(T)dla typu wartości T, w zależności od kontekstu. W powyższym przypadku, Ifwyprowadza Integerjak wspólna typu Nothinga 5, dlatego w tym przypadku, Nothingśrodki 0.


Co ciekawe, nie udało mi się znaleźć tej odpowiedzi, więc musiałem stworzyć pytanie . Cóż, kto wiedział, że odpowiedź jest w tym wątku?
GSerg

28

Znalazłem drugą naprawdę dziwną skrzynkę narożną, która pokonuje moją pierwszą długą strzał.

Metoda String.Equals (String, String, StringComparison) nie jest w rzeczywistości wolna od efektów ubocznych.

Pracowałem nad blokiem kodu, który miał to samo w wierszu u góry niektórych funkcji:

stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);

Usunięcie tej linii prowadzi do przepełnienia stosu w innym miejscu programu.

Okazało się, że kod instaluje moduł obsługi zdarzenia będącego w istocie zdarzeniem BeforeAssemblyLoad i próbuje to zrobić

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

Do tej pory nie powinienem ci mówić. Użycie kultury, która nie była wcześniej używana w porównaniu ciągów, powoduje obciążenie zestawu. InvariantCulture nie jest wyjątkiem od tego.


Myślę, że „ładowanie zestawu” jest efektem ubocznym, ponieważ można to zaobserwować za pomocą BeforeAssemblyLoad!
Jacob Krall

2
Łał. To idealny strzał w nogę opiekuna. Wydaje mi się, że napisanie modułu obsługi BeforeAssemblyLoad może prowadzić do wielu takich niespodzianek.
wigy

20

Oto przykład, w jaki sposób można utworzyć strukturę, która powoduje komunikat o błędzie „Próbowano odczytać lub zapisać chronioną pamięć. Często oznacza to uszkodzenie innej pamięci”. Różnica między sukcesem a porażką jest bardzo subtelna.

Poniższy test jednostkowy pokazuje problem.

Sprawdź, czy możesz dowiedzieć się, co poszło nie tak.

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

Boli mnie głowa ... Dlaczego to nie działa?
jasonh

2
Hm, napisałem to kilka miesięcy temu, ale nie pamiętam, dlaczego dokładnie tak się stało.
cbp

10
Wygląda jak błąd kompilatora; na += 500połączenia: ldc.i4 500(500 popycha za Int32), a następnie call valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)- tak, że następnie traktuje jako A decimal(96 bitów), bez konwersji. Jeśli += 500Mgo użyjesz , zrobi to dobrze. Po prostu wygląda na to, że kompilator myśli, że może to zrobić w jeden sposób (prawdopodobnie z powodu niejawnego operatora int), a następnie decyduje się to zrobić w inny sposób.
Marc Gravell

1
Przepraszamy za podwójny post, oto bardziej szczegółowe wyjaśnienie. Dodam to, trochę mnie to wciągnęło i to jest do bani, chociaż rozumiem, dlaczego tak się dzieje. Dla mnie jest to niefortunne ograniczenie typu struktury / wartości. bytes.com/topic/net/answers/…
Bennett Dill

2
@Ben uzyskanie błędów kompilatora lub modyfikacja nie wpływająca na oryginalną strukturę jest w porządku. Naruszenie dostępu to zupełnie inna bestia. Środowisko wykonawcze nigdy nie powinno go wyrzucać, jeśli piszesz tylko bezpieczny, czysty kod zarządzany.
CodesInChaos

18

C # obsługuje konwersje między tablicami i listami, o ile tablice nie są wielowymiarowe i istnieje zależność dziedziczenia między typami, a typy są typami referencyjnymi

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

Pamiętaj, że to nie działa:

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
Przykład IList <T> jest tylko rzutowaniem, ponieważ string [] już implementuje ICloneable, IList, ICollection, IEnumerable, IList <string>, ICollection <string> i IEnumerable <string>.
Lucas

15

To najdziwniejszy przypadek, jaki spotkałem przypadkiem:

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

Używany w następujący sposób:

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

Rzuci NullReferenceException. Okazuje się, że wiele dodatków jest kompilowanych przez kompilator C # do wywołania String.Concat(object[]). Przed wersją .NET 4 występuje błąd w tym przeciążeniu Concat, w którym obiekt jest sprawdzany pod kątem wartości zerowej, ale nie jest wynikiem 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;

Jest to błąd ECMA-334 § 14.7.4:

Operator binarny + wykonuje konkatenację łańcuchów, gdy jeden lub oba operandy są typu string. Jeśli operandem konkatenacji łańcucha jest null, pusty łańcuch jest zastępowany. W przeciwnym razie dowolny argument nieciągowy zostanie przekonwertowany na reprezentację ciągu przez wywołanie ToStringmetody wirtualnej odziedziczonej po typie object. Jeśli ToStringzwraca null, zastępowany jest pusty ciąg.


3
Hmm, ale mogę sobie wyobrazić tę usterkę, ponieważ .ToStringtak naprawdę nigdy nie powinna zwracać wartości null, ale ciąg znaków. Niemniej jednak i błąd w ramach.
Dykam

12

Ciekawe - kiedy po raz pierwszy spojrzałem na to, założyłem, że jest to coś, co sprawdza kompilator C #, ale nawet jeśli wyślesz IL bezpośrednio w celu usunięcia jakiejkolwiek szansy na interferencję, tak się dzieje, co oznacza, że ​​tak naprawdę jest to newobjkod operacyjny, który robi kontrola.

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

Jest to również równoznaczne, truejeśli sprawdzisz, z string.Emptyktórymi środkami ten kod operacyjny musi mieć specjalne zachowanie, aby internować puste łańcuchy.


nie być mądrym sprytem, ​​czy cokolwiek innego, ale słyszałeś o odbłyśniku ? jest całkiem przydatny w tego rodzaju przypadkach;
RCIX

3
Nie jesteś mądry; brakuje Ci punktu - chciałem wygenerować konkretną IL dla tego jednego przypadku. W każdym razie, biorąc pod uwagę, że Reflection.Emit jest trywialny dla tego typu scenariusza, prawdopodobnie jest tak szybki, jak napisanie programu w C #, a następnie otwarcie reflektora, znalezienie pliku binarnego, znalezienie metody itp. I nawet nie muszę zostaw IDE, aby to zrobić.
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

Dane wyjściowe to „Próba odczytu chronionej pamięci. Wskazuje to, że inna pamięć jest uszkodzona”.


1
Ciekawy! Brzmi jak błąd kompilatora; Przeniesiłem do C # i działa dobrze. To powiedziawszy, istnieje wiele problemów z wyjątkami zgłaszanymi w Load i działa inaczej z / bez debuggera - możesz złapać za pomocą debuggera, ale nie bez (w niektórych przypadkach).
Marc Gravell

Przepraszam, zapomniałem, musisz dodać pole kombi do formularza, zanim będzie to możliwe.
Joshua

Czy ma to związek z inicjowaniem dialogu za pomocą SEH jako pewnego rodzaju okropnego mechanizmu komunikacji wewnętrznej? Niejasno pamiętam coś takiego w Win32.
Daniel Earwicker

1
Jest to ten sam problem powyżej. Zwracany typ wartości jest kopią, dlatego wszelkie odniesienia do jakichkolwiek właściwości wynikających z tej kopii są kierowane do krainy bit-bucket ... bytes.com/topic/net/answers/…
Bennett Dill

1
Nie. Tutaj nie ma żadnych struktur. Naprawdę to debugowałem. Dodaje NULL do zbioru pozycji listy natywnego pola kombi, powodując opóźnione zawieszenie.
Joshua

10

PropertyInfo.SetValue () może przypisywać liczby całkowite do wyliczeń, ints do nullable ints, enum do nullable enum, ale nie ints do nullable enum.

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

Pełny opis tutaj


10

Co jeśli masz ogólną klasę, która ma metody, które mogą być niejednoznaczne w zależności od argumentów typu? Niedawno wpadłem na tę sytuację, pisząc słownik dwukierunkowy. Chciałem napisać Get()metody symetryczne , które zwracałyby przeciwieństwo przekazanego argumentu. Coś takiego:

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

Wszystko jest dobrze, jeśli utworzysz instancję gdzie T1i T2są różne typy:

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

Ale jeśli T1i T2są takie same (i prawdopodobnie jeśli jedna była podklasą innej), jest to błąd kompilatora:

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

Co ciekawe, wszystkie pozostałe metody w drugim przypadku są nadal użyteczne; to tylko wywołania niejednoznacznej metody, która powoduje błąd kompilatora. Ciekawa sprawa, choć trochę nieprawdopodobna i niejasna.


Przeciwnicy przeciążenia metody pokochają ten ^^.
Christian Klauser,

1
Nie wiem, to ma dla mnie całkowity sens.
Scott Whitlock,

10

C # Accessibility Puzzler


Następująca klasa pochodna uzyskuje dostęp do pola prywatnego ze swojej klasy podstawowej, a kompilator po cichu patrzy na drugą stronę:

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

Pole jest rzeczywiście prywatne:

private int m_basePrivateField = 0;

Chcesz zgadnąć, jak możemy skompilować taki kod?

.

.

.

.

.

.

.

Odpowiedź


Sztuką jest zadeklarowanie Derivedjako wewnętrznej klasy Base:

public class Base
{
    private int m_basePrivateField = 0;

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

Klasy wewnętrzne mają pełny dostęp do członków klasy zewnętrznej. W tym przypadku klasa wewnętrzna również wywodzi się z klasy zewnętrznej. To pozwala nam „przerwać” enkapsulację członków prywatnych.


To jest właściwie dobrze udokumentowane; msdn.microsoft.com/en-us/library/ms173120%28VS.80%29.aspx . Czasami może to być przydatna funkcja, szczególnie jeśli klasa zewnętrzna jest statyczna.

Tak - oczywiście to udokumentowane. Jednak niewielu ludzi rozwiązało tę zagadkę, więc pomyślałem, że to fajna ciekawostka.
Omer Mor,

2
Wygląda na to, że miałbyś bardzo silną możliwość przepełnienia stosu, gdy klasa wewnętrzna odziedziczy swojego właściciela ...
Jamie Treworgy

Jeszcze innym podobnym (i całkowicie poprawnym) przypadkiem jest to, że obiekt może uzyskać dostęp do prywatnego członka innego obiektu tego samego typu:class A { private int _i; public void foo(A other) { int res = other._i; } }
Olivier Jacot-Descombes

10

Właśnie dziś znalazłem coś miłego:

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

To powoduje błąd kompilacji.

Wywołanie metody „Initialize” musi być dynamicznie wywoływane, ale nie może być tak, ponieważ jest częścią podstawowego wyrażenia dostępu. Rozważ rzutowanie argumentami dynamicznymi lub wyeliminowanie dostępu podstawowego.

Jeśli napiszę base.Initialize (stuff as object); działa idealnie, jednak wydaje się, że jest to tutaj „magiczne słowo”, ponieważ robi dokładnie to samo, wszystko nadal jest odbierane jako dynamiczne ...


8

W używanym przez nas interfejsie API metody zwracające obiekt domeny mogą zwracać specjalny „obiekt zerowy”. W realizacji tego operator porównania i Equals()metoda są nadpisywane, aby zwrócić, truejeśli zostanie porównanenull .

Użytkownik tego interfejsu API może mieć taki kod:

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

lub może bardziej szczegółowy, taki jak ten:

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

gdzie GetDefault()jest metoda zwracająca wartość domyślną, której chcemy użyć zamiast null. Niespodzianka uderzyła mnie, gdy korzystałem z ReSharper i zgodnie z zaleceniem przepisałem jedno z poniższych:

return test ?? GetDefault();

Jeśli obiekt testowy jest obiektem zerowym zwróconym z interfejsu API zamiast właściwego null, zachowanie kodu się teraz zmieniło, ponieważ operator koalescencji zerowej faktycznie sprawdza null, czy nie działa operator=lub Equals().


1
tak naprawdę nie jest to przypadek narożny, ale drogi panie, kto to wymyślił?!?
Ray Booysen

Czy ten kod nie używa tylko typów zerowalnych? Dlatego ReSharper poleca „??” posługiwać się. Jak powiedział Ray, nie pomyślałbym, że to przypadek narożny; czy się mylę?
Tony

1
Tak, typy są zerowalne - i dodatkowo istnieje NullObject. Jeśli jest to przypadek narożny, nie wiem, ale przynajmniej jest to przypadek, w którym „if (a! = Null) zwraca a; return b; ' to nie to samo, co „return a ?? b'. Całkowicie się zgadzam, że jest to problem z projektem frameworka / interfejsu API - przeciążenie == null, aby zwrócić wartość true dla obiektu, z pewnością nie jest dobrym pomysłem!
Tor Livar

8

Rozważ ten dziwny przypadek:

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

Jeśli Basei Derivedsą zadeklarowane w tym samym zestawie, kompilator utworzy Base::Methodwirtualny i zapieczętowany (w CIL), nawet jeśli Basenie implementuje interfejsu.

Jeśli Basei Derivedznajdują się w różnych Derivedzestawach , podczas kompilowania zestawu kompilator nie zmieni drugiego zestawu, więc wprowadzi element członkowski, Derivedktóry będzie jawną implementacją MyInterface::Method, która po prostu przekaże wywołanie Base::Method.

Kompilator musi to zrobić, aby obsługiwać wysyłanie polimorficzne w odniesieniu do interfejsu, tzn. Musi uczynić tę metodę wirtualną.


To naprawdę brzmi dziwnie. Będę musiał zbadać później :)
Jon Skeet

@Jon Skeet: Znalazłem to podczas badania strategie wdrożeniowe dla ról w C # . Byłoby wspaniale poznać Twoją opinię na ten temat!
Jordão,

7

Poniżej może być ogólna wiedza, której po prostu mi brakowało, ale eh. Jakiś czas temu mieliśmy przypadek błędu, który zawierał właściwości wirtualne. Trochę abstrakcji kontekstu, rozważ następujący kod i zastosuj punkt przerwania do określonego obszaru:

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

W Derivedkontekście obiektowym takie samo zachowanie można uzyskać podczas dodawania base.Propertyjako zegarka lub pisania base.Propertyw szybkim zegarku .

Zajęło mi trochę czasu, aby zrozumieć, co się dzieje. W końcu oświecił mnie Quickwatch. Gdy wchodzisz do Quickwatch i eksplorujesz Derivedobiekt d (lub z kontekstu obiektu this) i basewybierasz pole , pole edycyjne na górze Quickwatch wyświetla następującą obsadę:

((TestProject1.Base)(d))

Co oznacza, że ​​jeśli podstawa zostanie zastąpiona jako taka, połączenie będzie

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

dla Zegarków, Quickwatcha i etykiet debugowania kursora myszy, a wtedy byłoby sensowne, aby wyświetlał się "AWESOME"zamiast "BASE_AWESOME"rozważać polimorfizm. Nadal nie jestem pewien, dlaczego przekształciłby go w obsadę. Jedna hipoteza jest taka, że callmoże nie być dostępna z kontekstu tych modułów i tylko callvirt.

W każdym razie, to oczywiście niczego nie zmienia pod względem funkcjonalności, Derived.BasePropertynadal naprawdę powróci "BASE_AWESOME", a zatem nie było to przyczyną naszego błędu w pracy, a jedynie mylący element. Uważam jednak za interesujące, w jaki sposób może wprowadzić w błąd programistów, którzy nie byliby tego świadomi podczas sesji debugowania, szczególnie jeśli Basenie jest ujawniony w projekcie, ale jest nazywany zewnętrzną biblioteką DLL, co powoduje, że deweloperzy mówią:

„Oi, czekaj… jak to jest, że DLL jest,. Robić coś śmiesznego”


To nic specjalnego, tak po prostu zastępuje działanie.
konfigurator

7

Ten jest dość trudny do zdobycia. Natknąłem się na to, gdy próbowałem zbudować implementację RealProxy, która naprawdę obsługuje Begin / EndInvoke (dzięki MS za uczynienie tego niemożliwym bez okropnych włamań). Ten przykład jest w zasadzie błędem w CLR, niezarządzana ścieżka kodu dla BeginInvoke nie sprawdza, czy komunikat zwrotny z RealProxy.PrivateInvoke (i moje zastąpienie Invoke) zwraca instancję IAsyncResult. Po zwróceniu CLR jest niesamowicie zdezorientowany i traci jakiekolwiek pojęcie o tym, co się dzieje, co pokazują testy na dole.

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

Wynik:

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

Nie jestem pewien, czy powiedziałbyś, że to dziwactwo Windows Vista / 7 lub dziwne .Net, ale przez pewien czas drapałem się po głowie.

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

W systemie Windows Vista / 7 plik zostanie faktycznie zapisany C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt


2
To jest rzeczywiście poprawa bezpieczeństwa vista (nie 7, afaik). Ale fajną rzeczą jest to, że możesz czytać i otwierać plik za pomocą ścieżki plików programu, a jeśli spojrzysz tam w Eksploratorze, nie ma nic. Ten zajął mi prawie dzień pracy @ klienta, zanim w końcu się dowiedziałem.
Henri,

Z pewnością jest to także Windows 7. Tego właśnie używałem, kiedy na to wpadłem. Rozumiem powody, dla których to leżało, ale wciąż było frustrujące.
Spencer Ruport

W systemie Vista / Win 7 (również technicznie winXP) aplikacje powinny zapisywać w folderze AppData w folderze Users-folder, jako technicznie dane użytkownika. Aplikacje nigdy nie powinny pisać do plików programów / windows / system32 / etc, chyba że mają uprawnienia administratora, a te przywileje powinny być tylko po to, aby powiedzieć, że uaktualniają program / odinstalowują go / instalują nową funkcję. ALE! Nadal nie pisz do system32 / windows / etc :) Jeśli uruchomiłeś powyższy kod jako administrator (kliknij prawym przyciskiem myszy> uruchom jako administrator), powinien teoretycznie zapisać w folderze aplikacji plików programu.
Steve Syfuhs


6

Czy kiedykolwiek myślałeś, że kompilator C # może wygenerować nieprawidłowy plik CIL? Uruchom to, a otrzymasz 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));
  }
}

Nie wiem jednak, jak sobie radzi w kompilatorze C # 4.0.

EDYCJA : to jest wyjście z mojego systemu:

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]

Działa dla mnie zarówno z kompilatorem C # 3.5, jak i kompilatorem C # 4 ...
Jon Skeet

W moim systemie to nie działa. Wkleję wynik w pytaniu.
Jordão

Nie udało mi się to w .NET 3.5 (nie mam czasu na testowanie 4.0). Mogę zreplikować problem z kodem VB.NET.
Mark Hurd

3

Jest coś naprawdę ekscytującego w C #, w sposobie, w jaki obsługuje zamknięcia.

Zamiast kopiować wartości zmiennych stosu do zmiennej wolnej od zamykania, magia preprocesora owija wszystkie wystąpienia zmiennej w obiekt i tym samym przenosi go ze stosu - prosto na stos! :)

Wydaje mi się, że dzięki temu język C # jest jeszcze bardziej funkcjonalnie kompletny (lub lambda-complete huh)) niż sam ML (który używa AFAIK do kopiowania wartości stosu). F # ma również tę funkcję, podobnie jak C #.

To sprawia mi wiele radości, dziękuję wam stwardnienie rozsiane!

Nie jest to jednak dziwactwo ani narożnik ... ale coś naprawdę nieoczekiwanego z języka VM opartego na stosie :)


3

Z pytania, które zadałem niedawno:

Operator warunkowy nie może rzucić niejawnie?

Dany:

Bool aBoolValue;

Gdzie aBoolValue jest przypisane Prawda lub Fałsz;

Następujące elementy nie zostaną skompilowane:

Byte aByteValue = aBoolValue ? 1 : 0;

Ale to:

Int anIntValue = aBoolValue ? 1 : 0;

Podana odpowiedź jest również całkiem dobra.


chociaż jestem ve not test it Ipewien, że to zadziała: Byte aByteValue = aBoolValue? (Bajt) 1: (bajt) 0; Lub: Byte aByteValue = (Byte) (aBoolValue? 1: 0);
Alex Pacurar

2
Tak, Alex, to by zadziałało. Kluczem jest ukryty rzut. 1 : 0sam będzie domyślnie rzutował na int, a nie Byte.
MPelletier

2

Określanie zakresu w języku c # jest czasem naprawdę dziwaczne. Pozwól mi podać jeden przykład:

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

OleDbCommand command = SQLServer.CreateCommand();

Kompilacja nie powiedzie się, ponieważ polecenie zostało ponownie zapisane? Istnieją pewne domysły, dlaczego tak działa w tym wątku na stackoverflow i na moim blogu .


34
Nie uważam tego za szczególnie dziwaczne. To, co na swoim blogu nazywasz „idealnie poprawnym kodem”, jest całkowicie niepoprawne zgodnie ze specyfikacją języka. Może być poprawny w jakimś wyimaginowanym języku, w którym chciałbyś, aby C #, ale specyfikacja języka jest dość jasna, że ​​w C # jest niepoprawna.
Jon Skeet

7
Jest to poprawne w C / C ++. A ponieważ jest to C #, chciałbym, żeby nadal działał. Najbardziej boli mnie to, że kompilator nie ma powodu, aby to robić. Zagnieżdżanie zakresu nie jest trudne. Myślę, że wszystko sprowadza się do elementu najmniejszego zaskoczenia. Oznacza to, że może być tak, że specyfikacja mówi to i tamto, ale to naprawdę nie pomaga mi bardzo, jeśli jest całkowicie nielogiczne, że tak się zachowuje.
Anders Rune Jensen

6
C #! = C / C ++. Czy chcesz również użyć cout << „Hello World!” << endl; zamiast Console.WriteLine („Hello World!”) ;? Również nie jest to nielogiczne, wystarczy przeczytać specyfikację.
Kredns

9
Mówię o regułach określania zakresu, które są częścią rdzenia języka. Mówisz o standardowej bibliotece. Ale teraz jest dla mnie jasne, że powinienem po prostu przeczytać małą specyfikację języka c #, zanim zacznę w nim programować.
Anders Rune Jensen

6
Eric Lippert opublikował ostatnio powody, dla których C # został tak zaprojektowany: blogs.msdn.com/ericlippert/archive/2009/11/02/ ... Podsumowanie wynika z tego, że rzadziej zmiany będą miały niezamierzone konsekwencje.
Helephant
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.