Я собираю несколько угловых случаев и тизеров мозга и всегда хотел бы услышать больше. Страница действительно охватывает только биты и бобы языка C #, но я также нахожу основные вещи .NET тоже интересными. Например, вот то, чего нет на странице, но которое я считаю невероятным:
string x = new string(new char[0]);
string y = new string(new char[0]);
Console.WriteLine(object.ReferenceEquals(x, y));
Я ожидаю, что для печати False - в конце концов, «new» (со ссылочным типом) всегда создает новый объект, не так ли? Спецификации как для C #, так и для CLI указывают, что так и должно быть. Ну, не в этом конкретном случае. Он печатает True, и сделал это на каждой версии фреймворка, с которым я его тестировал. (Я не пробовал это на Моно, по общему признанию ...)
Просто чтобы прояснить, это всего лишь пример того, что я ищу - я не особенно искал обсуждения / объяснения этой странности. (Это не то же самое, что обычное интернирование строк; в частности, интернирование строк обычно не происходит при вызове конструктора.) Я действительно просил подобное странное поведение.
Есть еще какие-нибудь драгоценные камни?
Ответы:
Я думаю, что показывал вам это раньше, но мне здесь нравится веселье - это заняло некоторую отладку, чтобы выследить! (оригинальный код был явно более сложным и тонким ...)
Так, что было T ...
Ответ: любой
Nullable<T>
- такой какint?
. Все методы переопределены, кроме GetType (), который не может быть; поэтому он приводится (в штучной упаковке) к объекту (и, следовательно, к нулю) для вызова object.GetType () ... который вызывает null ;-pОбновление: сюжет утолщается ... Ayende Rahien бросил аналогичный вызов в своем блоге , но с
where T : class, new()
:Но это можно победить! Использование той же косвенности, которую используют такие вещи, как удаленное взаимодействие; предупреждение - следующее является чистым злом :
После этого
new()
вызов перенаправляется в прокси (MyFunnyProxyAttribute
), который возвращаетсяnull
. А теперь иди и помой глаза!источник
Банковское округление.
Это не столько ошибка компилятора или сбой, но, конечно, странный случай ...
.Net Framework использует схему или округление, известное как округление банкира.
В округлении банкиров числа 0,5 округляются до ближайшего четного числа, поэтому
Это может привести к некоторым неожиданным ошибкам в финансовых расчетах, основанных на более известном округлении до половины.
Это также верно для Visual Basic.
источник
int(fVal + 0.5)
так часто вижу даже в языках, которые имеют встроенную функцию округления.Что будет делать эта функция, если она вызывается как
Rec(0)
(не в отладчике)?Ответ:
Это связано с тем, что 64-разрядный JIT-компилятор применяет оптимизацию хвостового вызова , а 32-разрядный JIT - нет.
К сожалению, у меня нет 64-битной машины, чтобы проверить это, но метод отвечает всем условиям для оптимизации хвостового вызова. Если у кого-то есть такой, мне было бы интересно узнать, правда ли это.
источник
++
там меня полностью скинуло. Разве ты не можешь позвонить,Rec(i + 1)
как нормальный человек?Назначьте это!
Это то, что я люблю спрашивать на вечеринках (возможно, поэтому меня больше не приглашают):
Можете ли вы сделать следующий кусок кода скомпилировать?
Простой чит может быть:
Но реальное решение заключается в следующем:
Так что малоизвестно, что типы значений (структуры) могут переназначать свои
this
переменные.источник
//this = new Teaser();
:-)public Foo(int bar){this = new Foo(); specialVar = bar;}
. Это неэффективно и не совсем оправдано (specialVar
назначается дважды), но просто к сведению. (Это причина, приведенная в книге, я не знаю, почему мы не должны просто делать этоpublic Foo(int bar) : this()
)Несколько лет назад, когда мы работали над программой лояльности, у нас возникла проблема с количеством баллов, начисляемых клиентам. Проблема была связана с приведением / преобразованием double в int.
В коде ниже:
i1 == i2 ?
Оказывается, что i1! = I2. Из-за различных политик округления в операторе Convert и cast действительные значения:
Всегда лучше вызывать Math.Ceiling () или Math.Floor () (или Math.Round с MidpointRounding, который отвечает нашим требованиям).
источник
Они должны были сделать 0 целым числом, даже если перегружена функция enum.
Я знал обоснование команды ядра C # для отображения 0 в enum, но все же оно не так ортогонально, как должно быть. Пример из Npgsql .
Тестовый пример:
источник
none
которое можно использовать для преобразования в любое перечисление, и сделать 0 всегда int и неявно конвертируемым в перечисление.0
должен был быть преобразован в enum. См. Блог Эрика Липперта: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspxЭто одна из самых необычных вещей, которые я когда-либо видел (кроме тех, что здесь, конечно!):
Он позволяет вам объявить это, но не имеет реального использования, так как он всегда будет просить вас обернуть любой класс, который вы наберете в центре, другой черепахой.
[шутка] Я предполагаю, что это черепахи полностью вниз ... [/ шутка]
источник
class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Вот один, о котором я только недавно узнал ...
Вышеприведенное выглядит на первый взгляд сумасшедшим, но на самом деле это законно. Нет, правда (хотя я пропустил ключевую часть, но это не что-то хакерское, вроде «добавить класс с именем
IFoo
» или «добавитьusing
псевдоним в точку»).IFoo
на класс").Посмотрите, сможете ли вы выяснить почему: кто сказал, что вы не можете создать интерфейс?
источник
Когда логическое значение не является ни истинным, ни ложным?
Билл обнаружил, что вы можете взломать логическое значение, так что если A - это True, а B - True, (A и B) - False.
Взломанные логические выражения
источник
Я немного опаздываю на вечеринку, но у меня есть
тричетырепять:Если вы опрашиваете InvokeRequired для элемента управления, который не был загружен / показан, он скажет «ложь» - и взорвется вам в лицо, если вы попытаетесь изменить его из другого потока ( решение состоит в том, чтобы сослаться на это. Обращайтесь к создателю контроль).
Еще одна вещь, которая сбила меня с толку - это сборка с:
если вы вычисляете MyEnum.Red.ToString () в другой сборке, и между тем кто-то перекомпилировал ваше перечисление в:
во время выполнения вы получите «черный».
У меня была общая сборка с несколькими удобными константами. Мой предшественник оставил массу уродливых свойств, доступных только для get, я думал, что избавлюсь от беспорядка и просто использую public const. Я был более чем удивлен, когда VS скомпилировал их с их значениями, а не ссылками.
Если вы реализуете новый метод интерфейса из другой сборки, но перестраиваете, ссылаясь на старую версию этой сборки, вы получаете исключение TypeLoadException (без реализации NewMethod), даже если вы его реализовали (см. Здесь ).
Словарь <,>: «Порядок возврата элементов не определен». Это ужасно , потому что это может иногда кусать вас, но работать на других, и если вы просто слепо предполагали, что Словарь будет играть хорошо («почему бы и нет? Я подумал, Список делает»), вам действительно нужно держи свой нос перед тем, как наконец начнешь сомневаться в своем предположении.
источник
VB.NET, обнуляемый и троичный оператор:
Это заняло у меня некоторое время для отладки, так как я ожидал
i
содержатьNothing
.Что я на самом деле содержит?
0
,Это удивительно, но на самом деле «правильное» поведение:
Nothing
в VB.NET это не совсем то же самое, чтоnull
в CLR:Nothing
может означатьnull
илиdefault(T)
тип значенияT
, в зависимости от контекста. В приведенном выше случаеIf
выводитсяInteger
как общий типNothing
и5
, значит, в данном случае,Nothing
означает0
.источник
Я обнаружил второй действительно странный угловой корпус, который намного превосходит мой первый.
Метод String.Equals (String, String, StringComparison) фактически не имеет побочных эффектов.
Я работал над блоком кода, который содержал это в отдельной строке вверху какой-то функции:
Удаление этой строки приводит к переполнению стека в другом месте программы.
Код оказался установкой обработчика для того, что по сути было событием BeforeAssemblyLoad, и попыткой сделать
К настоящему времени я не должен был говорить тебе. Использование культуры, которая раньше не использовалась при сравнении строк, приводит к загрузке сборки. InvariantCulture не является исключением из этого.
источник
Вот пример того, как вы можете создать структуру, которая вызывает сообщение об ошибке «Попытка чтения или записи защищенной памяти. Это часто указывает на то, что другая память повреждена». Разница между успехом и провалом очень тонкая.
Следующий модульный тест демонстрирует проблему.
Посмотрим, сможете ли вы решить, что пошло не так.
источник
+= 500
вызовы:ldc.i4 500
(500 толкает как Int32), а затемcall valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)
- так это то лечит в качествеdecimal
(96-бит) без какого - либо преобразования. Если вы используете+= 500M
его, он получает все правильно. Похоже, что компилятор думает, что он может сделать это одним способом (предположительно из-за неявного оператора int), а затем решает сделать это другим способом.C # поддерживает преобразования между массивами и списками, если массивы не являются многомерными, и между типами есть отношение наследования, а типы являются ссылочными типами.
Обратите внимание, что это не работает:
источник
Это самое странное, с чем я столкнулся случайно:
Используется следующим образом:
Выкину
NullReferenceException
. Оказывается, несколько дополнений компилируются компилятором C # для вызоваString.Concat(object[])
. До .NET 4 в этой перегрузке Concat была ошибка, когда объект проверяется на нулевое значение, но не результат ToString ():Это ошибка ECMA-334 §14.7.4:
источник
.ToString
действительности она никогда не должна возвращать ноль, кроме строки. Пусто. Тем не менее и ошибка в рамках.Интересно - когда я впервые посмотрел на это, я предположил, что это что-то, что проверял компилятор C #, но даже если вы генерируете IL напрямую, чтобы устранить любую возможность вмешательства, это все равно происходит, что означает, что это действительно
newobj
код операции, который выполняет проверка.Это также приравнивается к тому,
true
что вы проверяете,string.Empty
что означает, что этот код операции должен иметь специальное поведение для интернирования пустых строк.источник
Вывод «Попытка чтения защищенной памяти. Это признак того, что другая память повреждена».
источник
PropertyInfo.SetValue () может назначать целые числа для перечислений, целые числа для пустых чисел, перечисления для пустых перечислений, но не целые для пустых перечислений.
Полное описание здесь
источник
Что если у вас есть универсальный класс, у которого есть методы, которые можно сделать неоднозначными в зависимости от аргументов типа? Я столкнулся с этой ситуацией, недавно написав двусторонний словарь. Я хотел написать симметричные
Get()
методы, которые возвращали бы противоположность любого передаваемого аргумента. Что-то вроде этого:Все хорошо, если вы делаете экземпляр, где
T1
иT2
есть разные типы:Но если
T1
иT2
то же самое (и, вероятно, если один был подклассом другого), это ошибка компилятора:Интересно, что все остальные методы во втором случае все еще можно использовать; это только вызовы теперь неоднозначного метода, который вызывает ошибку компилятора. Интересный случай, если немного маловероятно и малоизвестно.
источник
C # Accessibility Puzzler
Следующий производный класс обращается к закрытому полю из своего базового класса, и компилятор молча смотрит на другую сторону:
Поле действительно личное:
Хотите догадаться, как мы можем заставить такой код компилироваться?
,
,
,
,
,
,
,
Ответ
Хитрость заключается в том, чтобы объявить
Derived
как внутренний классBase
:Внутренние классы получают полный доступ к внешним членам класса. В этом случае внутренний класс также происходит от внешнего класса. Это позволяет нам «сломать» инкапсуляцию частных членов.
источник
class A { private int _i; public void foo(A other) { int res = other._i; } }
Просто сегодня нашел приятную мелочь:
Это выдает ошибку компиляции.
Вызов метода Initialize должен выполняться динамически, но не может быть потому, что он является частью выражения базового доступа. Рассмотрите приведение динамических аргументов или устранение базового доступа.
Если я напишу base.Initialize (материал как объект); это работает отлично, однако это кажется "волшебным словом" здесь, поскольку оно делает то же самое, все по-прежнему воспринимается как динамическое
источник
В используемом нами API методы, которые возвращают объект домена, могут возвращать специальный «нулевой объект». В реализации этого оператор сравнения и
Equals()
метод переопределяются для возврата,true
если он сравнивается сnull
.Таким образом, пользователь этого API может иметь такой код:
или, возможно, немного более многословно, как это:
где
GetDefault()
это метод, возвращающий некоторое значение по умолчанию, которое мы хотим использовать вместоnull
. Удивление меня поразило, когда я использовал ReSharper и следовал рекомендациям переписать одно из следующего:Если тестовый объект является нулевым объектом, возвращаемым из API, а не собственно
null
, поведение кода теперь изменилось, поскольку оператор объединения нулей фактически проверяетnull
, не запущенoperator=
илиEquals()
.источник
Рассмотрим этот странный случай:
Если
Base
иDerived
объявлены в одной сборке, компилятор сделаетBase::Method
виртуальный и запечатанный (в CIL), даже еслиBase
интерфейс не реализуется.Если
Base
иDerived
находятся в разных сборках, то при компиляцииDerived
сборки компилятор не будет изменять другую сборку, поэтому он представит член,Derived
который будет явной реализацией, дляMyInterface::Method
которой будет просто делегирован вызовBase::Method
.Компилятор должен сделать это для поддержки полиморфной диспетчеризации в отношении интерфейса, то есть он должен сделать этот метод виртуальным.
источник
Следующее может быть общим знанием, которого мне просто не хватало, но да. Некоторое время назад у нас был случай ошибки, который включал виртуальные свойства. Немного абстрагируя контекст, рассмотрите следующий код и примените точку останова к указанной области:
Находясь в
Derived
контексте объекта, вы можете получить то же поведение при добавленииbase.Property
в качестве часов или набравbase.Property
в быстрые часы.Мне потребовалось некоторое время, чтобы понять, что происходит. В конце концов я был просветлен Quickwatch. Когда вы входите в Quickwatch и исследуете
Derived
объект d (или из контекста объектаthis
) и выбираете полеbase
, поле редактирования в верхней части Quickwatch отображает следующее приведение:Это означает, что если база будет заменена как таковая, вызов будет
для часов, Quickwatch и отладочных подсказок при наведении курсора, и тогда имело бы смысл отображать их
"AWESOME"
вместо"BASE_AWESOME"
рассмотрения полиморфизма. Я до сих пор не уверен, почему это превратило бы его в приведение, одна гипотеза которогоcall
может быть недоступна из контекста этих модулей, и толькоcallvirt
.В любом случае, это, очевидно, ничего не меняет с точки зрения функциональности,
Derived.BaseProperty
все равно действительно вернется"BASE_AWESOME"
, и, таким образом, это не было корнем нашей ошибки на работе, просто запутывающим компонентом. Однако мне было интересно, как это могло бы ввести в заблуждение разработчиков, которые не будут знать об этом во время их отладочных сессий, особенно если ониBase
не представлены в вашем проекте, а скорее упоминаются как сторонние DLL, в результате чего разработчики просто говорят:источник
Это довольно сложно. Я столкнулся с этим, когда пытался создать реализацию RealProxy, которая действительно поддерживает Begin / EndInvoke (спасибо MS за то, что это невозможно без ужасных хаков). Этот пример в основном является ошибкой в CLR, неуправляемый путь к коду для BeginInvoke не проверяет, что возвращаемое сообщение от RealProxy.PrivateInvoke (и мое переопределение Invoke) возвращает экземпляр IAsyncResult. Как только это возвращено, CLR становится невероятно запутанным и теряет любое представление о том, что происходит, как продемонстрировали тесты внизу.
Вывод:
источник
Я не уверен, что вы скажете, что это странность Windows Vista / 7 или .Net, но я немного почесал голову.
В Windows Vista / 7 файл фактически будет записан в
C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt
источник
Вы когда-нибудь думали, что компилятор C # может генерировать недопустимый CIL? Запустите это, и вы получите
TypeLoadException
:Я не знаю, как это происходит в компиляторе C # 4.0, хотя.
РЕДАКТИРОВАТЬ : это вывод из моей системы:
источник
В C # есть что-то действительно захватывающее, то, как он обрабатывает замыкания.
Вместо того чтобы копировать значения переменной стека в переменную без замыкания, он выполняет магическую обработку препроцессора, оборачивая все вхождения переменной в объект и, таким образом, перемещая ее из стека - прямо в кучу! :)
Я предполагаю, что это делает C # даже более функционально-полным (или лямбда-полным ху) языком, чем сам ML (который использует AFAIK для копирования значений стека). F # имеет такую же функцию, как C #.
Это доставляет мне большое удовольствие, спасибо вам, ребята из MS!
Это не странный случай или случайный случай ... но что-то действительно неожиданное из языка VM на основе стека :)
источник
Из вопроса, который я задал не так давно:
Условный оператор не может быть приведен неявно?
Дано:
куда
aBoolValue
присваивается значение True или False;Следующее не будет компилироваться:
Но это будет:
Ответ также довольно хорош.
источник
ve not test it I
уверен, что это будет работать: Byte aByteValue = aBoolValue? (Байт) 1: (байт) 0; Или: Byte aByteValue = (Byte) (aBoolValue? 1: 0);1 : 0
один будет неявно приведен к int, а не к Byte.Область видимости в C # иногда бывает странной. Позвольте мне привести один пример:
Это не удается скомпилировать, потому что команда объявлена повторно? Есть некоторые интересные догадки относительно того, почему это работает таким образом в этой теме на stackoverflow и в моем блоге .
источник