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?
Odpowiedzi:
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 ...)
Czym więc był T ...
Odpowiedź: dowolna
Nullable<T>
- taka jakint?
. 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 ;-pAktualizacja: fabuła gęstnieje ... Ayende Rahien rzucił podobne wyzwanie na swoim blogu , ale z
where T : class, new()
: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 :
W tym miejscu
new()
wywołanie jest przekierowywane do proxy (MyFunnyProxyAttribute
), które zwracanull
. A teraz idź i umyj oczy!źródło
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
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.
źródło
int(fVal + 0.5)
tak często widzę nawet w językach, które mają wbudowaną funkcję zaokrąglania.Co zrobi ta funkcja, jeśli zostanie wywołana jako
Rec(0)
(nie w debuggerze)?Odpowiedź:
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.
źródło
++
totalnie mnie wyrzuciło. Nie możesz zadzwonićRec(i + 1)
jak normalna osoba?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?
Łatwym oszustwem może być:
Ale prawdziwe rozwiązanie jest następujące:
Jest więc mało znany fakt, że typy wartości (struktury) mogą ponownie przypisać swoją
this
zmienną.źródło
//this = new Teaser();
:-)public Foo(int bar){this = new Foo(); specialVar = bar;}
. To nie jest wydajne i nie jest tak naprawdę uzasadnione (specialVar
przypisywane 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()
)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:
czy i1 == i2 ?
Okazuje się, że i1! = I2. Z powodu różnych zasad zaokrąglania w operatorze Konwertuj i rzutuj rzeczywiste wartości to:
Zawsze lepiej jest wywoływać Math.Ceiling () lub Math.Floor () (lub Math.Round z MidpointRounding, który spełnia nasze wymagania)
źródło
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:
źródło
none
którego można użyć do konwersji na dowolny wyliczenie, i ustawić 0 zawsze na liczbę całkowitą i niejawnie zamienialną na wyliczenie.0
mógł być konwertowany na enum. Zobacz blog Erica Lipperta: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspxJest to jeden z najbardziej niezwykłych, jakie do tej pory widziałem (oprócz tych tutaj oczywiście!):
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]
źródło
class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Oto jeden, o którym dowiedziałem się niedawno ...
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 „dodajusing
alias, aby wskazaćIFoo
na klasa").Sprawdź, czy możesz dowiedzieć się, dlaczego: Kto mówi, że nie możesz utworzyć interfejsu?
źródło
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
źródło
Przyjeżdżam trochę na imprezę, ale mam
trzyczterypięć: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).
Kolejnym, który mnie zaskoczył, jest zestaw z:
jeśli obliczysz MyEnum.Red.ToString () w innym zestawie, a pomiędzy czasami ktoś ponownie skompiluje twoje wyliczenie do:
w czasie wykonywania pojawi się „Czarny”.
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ń.
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 ).
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.
źródło
VB.NET, nullables i operator trójskładnikowy:
Debugowanie zajęło mi trochę czasu, ponieważ spodziewałem
i
się zawieraćNothing
.Co tak naprawdę zawiera?
0
.Jest to zaskakujące, ale w rzeczywistości „poprawne” zachowanie:
Nothing
w VB.NET nie jest dokładnie takie samo jaknull
w CLR:Nothing
może oznaczaćnull
lubdefault(T)
dla typu wartościT
, w zależności od kontekstu. W powyższym przypadku,If
wyprowadzaInteger
jak wspólna typuNothing
a5
, dlatego w tym przypadku,Nothing
środki0
.źródło
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:
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ć
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.
źródło
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.
źródło
+= 500
połączenia:ldc.i4 500
(500 popycha za Int32), a następniecall valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)
- tak, że następnie traktuje jako Adecimal
(96 bitów), bez konwersji. Jeśli+= 500M
go 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.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
Pamiętaj, że to nie działa:
źródło
To najdziwniejszy przypadek, jaki spotkałem przypadkiem:
Używany w następujący sposób:
Rzuci
NullReferenceException
. Okazuje się, że wiele dodatków jest kompilowanych przez kompilator C # do wywołaniaString.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 ():Jest to błąd ECMA-334 § 14.7.4:
źródło
.ToString
tak naprawdę nigdy nie powinna zwracać wartości null, ale ciąg znaków. Niemniej jednak i błąd w ramach.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
newobj
kod operacyjny, który robi kontrola.Jest to również równoznaczne,
true
jeśli sprawdzisz, zstring.Empty
którymi środkami ten kod operacyjny musi mieć specjalne zachowanie, aby internować puste łańcuchy.źródło
Dane wyjściowe to „Próba odczytu chronionej pamięci. Wskazuje to, że inna pamięć jest uszkodzona”.
źródło
PropertyInfo.SetValue () może przypisywać liczby całkowite do wyliczeń, ints do nullable ints, enum do nullable enum, ale nie ints do nullable enum.
Pełny opis tutaj
źródło
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:Wszystko jest dobrze, jeśli utworzysz instancję gdzie
T1
iT2
są różne typy:Ale jeśli
T1
iT2
są takie same (i prawdopodobnie jeśli jedna była podklasą innej), jest to błąd kompilatora: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.
źródło
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ę:
Pole jest rzeczywiście prywatne:
Chcesz zgadnąć, jak możemy skompilować taki kod?
.
.
.
.
.
.
.
Odpowiedź
Sztuką jest zadeklarowanie
Derived
jako wewnętrznej klasyBase
: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.
źródło
class A { private int _i; public void foo(A other) { int res = other._i; } }
Właśnie dziś znalazłem coś miłego:
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 ...
źródło
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ć,true
jeśli zostanie porównanenull
.Użytkownik tego interfejsu API może mieć taki kod:
lub może bardziej szczegółowy, taki jak ten:
gdzie
GetDefault()
jest metoda zwracająca wartość domyślną, której chcemy użyć zamiastnull
. Niespodzianka uderzyła mnie, gdy korzystałem z ReSharper i zgodnie z zaleceniem przepisałem jedno z poniższych: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 sprawdzanull
, czy nie działaoperator=
lubEquals()
.źródło
Rozważ ten dziwny przypadek:
Jeśli
Base
iDerived
są zadeklarowane w tym samym zestawie, kompilator utworzyBase::Method
wirtualny i zapieczętowany (w CIL), nawet jeśliBase
nie implementuje interfejsu.Jeśli
Base
iDerived
znajdują się w różnychDerived
zestawach , podczas kompilowania zestawu kompilator nie zmieni drugiego zestawu, więc wprowadzi element członkowski,Derived
który będzie jawną implementacjąMyInterface::Method
, która po prostu przekaże wywołanieBase::Method
.Kompilator musi to zrobić, aby obsługiwać wysyłanie polimorficzne w odniesieniu do interfejsu, tzn. Musi uczynić tę metodę wirtualną.
źródło
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:
W
Derived
kontekście obiektowym takie samo zachowanie można uzyskać podczas dodawaniabase.Property
jako zegarka lub pisaniabase.Property
w 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
Derived
obiekt d (lub z kontekstu obiektuthis
) ibase
wybierasz pole , pole edycyjne na górze Quickwatch wyświetla następującą obsadę:Co oznacza, że jeśli podstawa zostanie zastąpiona jako taka, połączenie będzie
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, żecall
może nie być dostępna z kontekstu tych modułów i tylkocallvirt
.W każdym razie, to oczywiście niczego nie zmienia pod względem funkcjonalności,
Derived.BaseProperty
nadal 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śliBase
nie jest ujawniony w projekcie, ale jest nazywany zewnętrzną biblioteką DLL, co powoduje, że deweloperzy mówią:źródło
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.
Wynik:
źródło
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.
W systemie Windows Vista / 7 plik zostanie faktycznie zapisany
C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt
źródło
Czy kiedykolwiek myślałeś, że kompilator C # może wygenerować nieprawidłowy plik CIL? Uruchom to, a otrzymasz
TypeLoadException
:Nie wiem jednak, jak sobie radzi w kompilatorze C # 4.0.
EDYCJA : to jest wyjście z mojego systemu:
źródło
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 :)
źródło
Z pytania, które zadałem niedawno:
Operator warunkowy nie może rzucić niejawnie?
Dany:
Gdzie
aBoolValue
jest przypisane Prawda lub Fałsz;Następujące elementy nie zostaną skompilowane:
Ale to:
Podana odpowiedź jest również całkiem dobra.
źródło
ve not test it I
pewien, że to zadziała: Byte aByteValue = aBoolValue? (Bajt) 1: (bajt) 0; Lub: Byte aByteValue = (Byte) (aBoolValue? 1: 0);1 : 0
sam będzie domyślnie rzutował na int, a nie Byte.Określanie zakresu w języku c # jest czasem naprawdę dziwaczne. Pozwól mi podać jeden przykład:
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 .
źródło