Я собираю несколько угловых случаев и головоломки и всегда хотел бы услышать больше. Эта страница действительно охватывает только части языка C#, но я также нахожу интересными основные вещи .NET. Например, вот тот, которого нет на странице, но который я считаю невероятным:
string x = new string(new char[0]);
string y = new string(new char[0]);
Console.WriteLine(object.ReferenceEquals(x, y));
Я ожидал, что напечатает False - в конце концов, «новый» (со ссылочным типом) всегда создает новый объект, не так ли? В спецификациях как для C#, так и для интерфейса командной строки указано, что это необходимо. Ну, не в этом конкретном случае. Он печатает True и работает с каждой версией фреймворка, с которой я его тестировал. (Признаюсь, я не пробовал на Mono ...)
Чтобы быть ясным, это всего лишь пример того, что я ищу - я особо не искал обсуждения / объяснения этой странности. (Это не то же самое, что обычное интернирование строк; в частности, интернирование строк обычно не происходит при вызове конструктора.) Я действительно просил аналогичного странного поведения.
Есть ли еще какие-то сокровища?
Проверено на Mono 2.0 RC; возвращает True
Отредактировано, чтобы объяснить, что пример строки был всего лишь примером.
я думал, что строки .net неизменяемы и уникальны по содержанию; вы проверяли object.ReferenceEquals (x, string.Empty)?
Они неизменны, но не обязательно уникальны. Если вы измените «new char [0]» на «new char [] {'x'}» в обеих строках, вы получите ссылки на отдельные, но равные объекты.
Там должна быть какая-то прикольная оптимизация ...
Ага - оптимизация, которая нарушает спецификации, которые говорят, что будет выделен новый объект.
обе строки в конечном итоге становятся string.Empty, и кажется, что фреймворк хранит только одну ссылку на это
Есть где-нибудь ответ?
@Fowl: ответ на вопрос, почему конструктор строк ведет себя именно так? Нет, не совсем.
Это экономия памяти. Найдите в документации MSDN строку статического метода. CLR поддерживает пул строк. Вот почему строки с одинаковым содержимым отображаются как ссылки на одну и ту же память, то есть на объект.
Я слышал, что это один из краеугольных камней неизменности строк.
@John: Интернирование строк происходит автоматически только для литералы. Здесь дело обстоит не так. @DanielSwe: Интернирование - это не требуется для того, чтобы сделать строки неизменяемыми. Тот факт, что это возможно, является хорошим следствием неизменности, но нормального интернирования здесь все равно не происходит.
Ну, пустая строка - это, может быть, литерал. Это в Дельфах
@Marco: "" - строковый литерал. Создание пустой строки другим способом - это не то же самое, что строковый литерал.
Вы должны добавить один отсюда: groups.google.com/group/…
@Downvoter: Не могли бы вы прокомментировать, почему вы проголосовали против этого?
Возможно, сейчас уже немного поздно, но ... Должно быть вики сообщества.
@finnw: Готово. Не уверен, почему этого не было раньше ...
@opc: У меня мертвая страница по этой ссылке :(
Подробности реализации, вызывающие такое поведение, объясняются здесь: blog.liranchen.com/2010/08/brain-teasing-with-strings.html
Почти интересно, что люди не ожидают, что ссылка будет равной, но они ожидают, что == будет работать между двумя строками. Это связано с Intern и тем, как .Net хранит строки. Любая строка, содержащая одни и те же данные, является одним и тем же объектом.
@Tedd: Это просто неправда. Строка литералы попадает в один и тот же объект, но вы можете легко создать два разных строковых объекта с одинаковыми текстовыми данными. Они по-прежнему будут сравниваться как равные с использованием перегруженного оператора ==, пока оба выражения имеют строковый тип во время компиляции, поскольку тогда компилятор знает, что нужно вызвать перегруженный оператор.
строка a = "Тест"; строка b = String.IsInterned ("Te" + "st"); Debug.WriteLine (объект.ReferenceEquals (a, b)); // Правда
@Jon - Верно, можно. Но .Net пытается сохранить одну строку только один раз - отсюда и результат. String.Intern и String.IsInterned - это методы, используемые для поиска существующих строк.
@Tedd: Я не уверен, что должен показать этот код. Мы знаем, что литералы интернированы, поэтому безымянный временный объект, переданный в IsInterned, ищет ранее интернированное значение. Однако, если бы он не был буквально или интернирован вызовом Intern, результат был бы нулевым. На практике люди редко вызывают какой-либо метод, поэтому строки, которые генерируются на лету (в отличие от литералов), сравниваются посимвольно, а не по ссылке.
@Tedd: Он не «пытается» сохранить одну строку только один раз. Да, вы может вызываете Intern и IsInterned, но они редко встречаются.





Думаю, я уже показывал вам это раньше, но мне нравится, как здесь весело - это потребовало некоторой отладки, чтобы отследить! (исходный код был явно более сложным и тонким ...)
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());
}
Так что же было Т ...
Ответ: любой Nullable<T> - например int?. Все методы переопределяются, кроме GetType (), которого не может быть; поэтому он приводится (помещается в коробку) к объекту (и, следовательно, к null) для вызова object.GetType () ... который вызывает null ;-p
Обновление: сюжет сгущается ... Айенде Райен бросила аналогичный вызов в его блоге, но с 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?");
}
Но его можно победить! Использование того же косвенного обращения, что и при удаленном взаимодействии; предупреждение - это чистое зло:
class MyFunnyProxyAttribute : ProxyAttribute {
public override MarshalByRefObject CreateInstance(Type serverType) {
return null;
}
}
[MyFunnyProxy]
class MyFunnyType : ContextBoundObject { }
После этого вызов new() перенаправляется на прокси (MyFunnyProxyAttribute), который возвращает null. А теперь иди и промой глаза!
Почему нельзя определить Nullable <T> .GetType ()? Разве результатом не должно быть typeof (Nullable <T>)?
Дрю: проблема в том, что GetType () не виртуальный, поэтому он не переопределяется - это означает, что значение помещено в рамку для вызова метода. Поле становится пустой ссылкой, следовательно, NRE.
@Нарисовался; кроме того, существуют специальные правила упаковки для Nullable <T>, что означает, что пустой Nullable <T> будет иметь значение NULL, а не поле, содержащее пустой Nullable <T> (и пустое поле отключается от пустого Nullable <T >)
Очень-очень круто. В некотором роде не круто. ;-)
Вау, для меня это довольно неожиданно. Пришлось поиграть в тестовом приложении, чтобы убедиться в этом сам.
Не могли бы вы дать ссылку на ту часть документации по языку, которая объясняет, что означает «where T : new()»?
Конструктор-ограничение, 10.1.5 в спецификации языка C# 3.0
Вау, спасибо за информацию о ProxyAttribute!
Может быть, стирание типов в Java не было такой уж плохой идеей.
@finnw: Я очень надеюсь, что это было саркастично. «Да, давайте превратим жизнь программиста в ад, потому что типы, допускающие значение NULL, будут упакованы в NULL при вызове GetType () в экземпляре, использующем дженерики».
@John Skeet: Правильно ли было бы сказать, что GetType не требовал бы бокса, если бы у Microsoft была каждая структура, автоматически затенявшая Object.GetType с собственной реализацией, но Microsoft этого не сделала, бокс необходим для вызова Object. GetType? Сломало бы это что-нибудь, если бы в .Net 5.0 Microsoft имела структуру (или, по крайней мере, обнуляемые типы), включающую такую теневую реализацию?
@supercat; @Jon не увидит этого, если вы не удалите "h", но: GetType () сам по себе не является виртуальным, и его изменение будет огромным изменением. Конечно, во многих случаях для структур существует нет нужды называть это - компилятор может сделать подстановку (он уже знает); но для этого потребуется изменение спецификации.
@Marc Gravell: Он увидит ваш комментарий? Я думал, эффективен только первый тег @? Думаю, я вижу проблему. Структура может реализовать GetType таким образом, чтобы затенять Object.GetType, и Nullable <T>, вероятно, тоже может. Это позволило бы вызвать GetType для объекта, о котором было известно, что он имеет тип Nullable <T> без упаковки. Однако это не решит проблему в общем случае, поскольку отправка метода определяется до привязки универсального типа. Можно определить структуру с помощью метода GetType, который возвращает 1.GetType; если вызвать GetType для такой структуры ...
@Marc Gravell: ... результат будет (System.Int32), но если передать такую структуру универсальной функции, которая вызывает для нее GetType, результатом будет тип структуры. Думаю, меня немного удивляет то, что Nullable <T> не затеняет метод GetType, чтобы его можно было использовать без упаковки, и добавление затененного метода GetType к Nullable <T>, вероятно, ничего не сломает, но даже если такой теневой метод не повлияет на необходимость вставки в универсальную функцию. Спасибо за письмо.
В этом посте я впервые увидел переопределение CreateInstance. Я сразу понял, что он делает, и почувствовал, как липкий ужас охватил мою душу ...
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
Результатом будет «Попытка прочитать защищенную память. Это признак того, что другая память повреждена».
Интересно! Хотя звучит как ошибка компилятора; Я портировал на C#, и он отлично работает. Тем не менее, существует много проблем с исключениями, возникающими в Load, и он ведет себя по-разному с / без отладчика - вы можете поймать с помощью отладчика, но не без него (в некоторых случаях).
Извините, я забыл, вам нужно добавить поле со списком в форму, прежде чем оно появится.
Связано ли это с инициализацией диалога с использованием SEH в качестве ужасного механизма внутренней связи? Смутно припоминаю что-то подобное в Win32.
Нет. При подключении неуправляемого отладчика выяснилось, что это разыменование нулевого указателя.
Это та же проблема cbp выше. Возвращаемый тип значения является копией, поэтому любые ссылки на любые свойства, происходящие из указанной копии, направляются в область битового ведра ... bytes.com/topic/net/answers/…
Неа. Здесь нет структур. Я действительно отлаживал это. Он добавляет NULL в коллекцию элементов списка собственного поля со списком, вызывая отложенный сбой.
Банковское округление.
Это не столько ошибка компилятора или неисправность, сколько, конечно, странный угловой случай ...
В .Net Framework используется схема округления, известная как округление Банковского.
В Bankers 'Rounding числа 0,5 округляются до ближайшего четного числа, поэтому
Math.Round(-0.5) == 0
Math.Round(0.5) == 0
Math.Round(1.5) == 2
Math.Round(2.5) == 2
etc...
Это может привести к неожиданным ошибкам в финансовых расчетах, основанных на более известном округлении с округлением до половины.
Это также верно и для Visual Basic.
Мне это тоже показалось странным. По крайней мере, до тех пор, пока я не округлил большой список чисел и не вычислил их сумму. Затем вы понимаете, что если вы просто округляете в большую сторону, вы получите потенциально огромную разницу от суммы неотокругленных чисел. Очень плохо, если вы делаете финансовые расчеты!
Я бы не согласился, работая в финансовой среде. Если ваши бизнес-требования нуждаются в одном способе округления, и если ваше программное обеспечение действует иначе, именно здесь и возникают проблемы. В тяжелых статистических расчетах округление Банкиров более точное - я думаю, это то, что вы имеете в виду.
Если люди не знали, вы можете сделать: Math.Round (x, MidpointRounding.AwayFromZero); Изменить схему округления.
Из документации: Поведение этого метода соответствует стандарту IEEE 754, раздел 4. Этот вид округления иногда называют округлением до ближайшего или банковским округлением. Это сводит к минимуму ошибки округления, которые возникают в результате последовательного округления среднего значения в одном направлении.
Интересно, поэтому я так часто вижу int(fVal + 0.5) даже на языках, в которых есть встроенная функция округления.
Хороший ответ! Это свело меня с ума !!! Я не мог понять, почему ...
По иронии судьбы, однажды я работал с банк, и другие программисты начали нервничать по этому поводу, думая, что округление было нарушено во фреймворке.
Я написал приложение для сортировки и агрегирования биржевой торговли в MS Access несколько лет назад (я покаялся, теперь нет нужды в публичном унижении). Если я правильно помню, в нем не было банковского округления и были неприятные побочные эффекты из-за фундаментальной неточности реальных чисел. В конечном итоге мы с командой переписали все это как приложение .Net 1.0 ASP.Net, используя сохраненные процедуры. Как вы могли догадаться, это было немного быстрее.
@ Бен Бланк: Я использую это просто потому, что мне нужен int, а не округлый поплавок.
Да уж! Это одна из причин, почему мне нравится .NET. Подожди ... Я не знаю !!!
Это не просто .net, это, по крайней мере, восходит к VB4. Учитывая, что это не приводит к искажению результатов в большинстве сценариев при агрегировании, почему этот «нормальный» способ округления не преподается в школе?
@ Джим Леонардо, в школе, 0,5 округляют. В этом коде 0,5 округляется в большую сторону, если вверх четно, и в меньшую сторону, если вверх нечетно. Учитывая следующий массив { 0.5, 1.5, 2.5, 3.5, 4.5, 5.5 }, округление вернет { 0, 2, 2, 4, 4, 6 }.
Также обратите внимание, что округление, которому нас учили в школах (или, по крайней мере, тому, чему меня учили в школах), должно было идти на одну цифру больше, чем желаемый результат, а затем округлять. При стандартном округлении IEEE (так называемом «банковском») вам нужно рассматривать 5 по-другому, если есть дополнительный остаток. (Например, 2,5 раунда до 2, но 2,5001 раунда до 3). Я полагаю, что эта дополнительная деталь была слишком велика для ума третьего класса.
На самом деле, если вы посмотрите глубже, это не имеет ничего общего с .Net. Это FPU Intel x86, который использует округление Банкира. Легко найти в гугле.
гм .. за исключением того, что вы можете выбрать, какой вид округления использовать со вторым параметром Math.Round, как указано в ICR.
Я думаю, что ответ на вопрос заключается в том, что .net использует интернирование строк чем-то, что может привести к тому, что одинаковые строки будут указывать на один и тот же объект (поскольку строки изменяемы, это не проблема)
(Я не говорю о переопределенном операторе равенства в строковом классе)
Строки - это неизменный, не изменяемые. И это не «нормальное» интернирование строк - это происходит только тогда, когда вы передаете пустой массив символов. Однако на самом деле вопрос не в том, «почему это происходит?» но "что подобное вы видели?"
Напоминает мне, как любое обсуждение проблемы Fizz Buzz приводит к тому, что по крайней мере половина ответов является решением проблемы.
Половина из которых были неверными.
Интересно - когда я впервые посмотрел на это, я предположил, что это то, что проверяет компилятор C#, но даже если вы испускаете IL напрямую, чтобы исключить любую возможность вмешательства, это все равно происходит, что означает, что на самом деле это операционный код newobj, который выполняет проверка.
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);
Это также приравнивается к true, если вы проверяете string.Empty, что означает, что этот код операции должен иметь особое поведение для вставки пустых строк.
не быть умным оленем или что-то в этом роде, но слышали ли вы о отражатель? это очень удобно в таких случаях;
Вы не ведете себя умно; вы упускаете суть - я хотел создать конкретный IL для этого одного случая. И в любом случае, учитывая, что Reflection.Emit является тривиальным для этого типа сценария, это, вероятно, так же быстро, как написать программу на C#, затем открыть отражатель, найти двоичный файл, найти метод и т.д ... И мне даже не нужно оставьте IDE для этого.
Когда логическое значение не является ни истинным, ни ложным?
Билл обнаружил, что вы можете взломать логическое значение, так что если A истинно, а B истинно, (A и B) ложны.
Взломанные логические значения
Конечно, когда это FILE_NOT_FOUND!
Это интересно, потому что с математической точки зрения это означает, что ни одно выражение в C# не является доказуемым. Ой.
@Simon Johnson - вы имеете в виду VB.NET, о котором идет речь в этом посте, верно?
Я тестировал его, и он тоже работает на C#.
Когда-нибудь я напишу программу, которая будет зависеть от этого поведения, и демоны мрачного ада приготовят мне прием. Бвахахахахаха!
В этом примере используются побитовые, а не логические операторы. Что в этом удивительного?
Ну, он взламывает макет структуры, конечно, вы получите странные результаты, это не так уж удивительно или неожиданно!
Что будет делать эта функция при вызове Rec(0) (не в отладчике)?
static void Rec(int i)
{
Console.WriteLine(i);
if (i < int.MaxValue)
{
Rec(i + 1);
}
}
Отвечать:
Это потому, что 64-битный JIT-компилятор применяет оптимизацию хвостового вызова, тогда как 32-битный JIT - нет.
К сожалению, у меня нет 64-битной машины, чтобы проверить это, но метод действительно удовлетворяет всем условиям для оптимизации хвостового вызова. Если у кого-то есть, мне было бы интересно узнать, правда ли это.
Должен быть скомпилирован в режиме выпуска, но определенно работает на x64 =)
Возможно, стоит обновить свой ответ, когда выйдет VS 2010, поскольку все текущие JIT будут выполнять TCO в режиме выпуска
Только что попробовал VS2010 Beta 1 на 32-битной WinXP. По-прежнему получаю исключение StackOverflowException.
Да, поддержка хвостового вызова в JIT полезна только в том случае, если компилятор генерирует префиксы хвостового кода операции, чего, похоже, компилятор C# по-прежнему не делает. Однако эквивалентный код F# должен работать отлично. :)
+1 для исключения StackOverflowException
У меня не работает на win7 x64 :( выдает исключение переполнения стека
@Ilya - Вы уверены, что компилируете приложение для платформы Any CPU или x64? Visual Studio по умолчанию использует x86, поэтому даже в 64-битной системе вы увидите 32-битное поведение, если вы не измените параметр компиляции.
@Gred, да я уверен :)
@Ilya - Возможно, вы изменили цель сборки на x64 для режима отладки, а затем скомпилировали в Release? Я сам допустил эту ошибку.
Тот ++ меня совершенно сбил с толку. Разве вы не можете вызвать Rec(i + 1) как нормальный человек?
@configurator ++ i является нормальным для языков стиля C.
Этот tail call optimization интересен: есть ли аналогичные функции в любой из JVM? Спасибо!
@configurator, разница в том, что оператор i++ возвращает значение переменной, а затем увеличивает его, ++i увеличивает значение и затем возвращает. а оператор ++ намного проще, чем i + 1.
@Shimmy: Чем ++i проще, чем i+1? Оба они представляют собой ровно 3 символа. Далее ++i изменяет значение i, что здесь не нужно (и может сбивать с толку). Все, что требуется, - это передать методу значение «на единицу больше, чем i».
@configurator - я изменил i++ на i + 1, потому что я согласен с тем, что состояние мутации - плохая форма для метода функционального стиля.
@JamesCurran, согласился с частью ненужного увеличения локальной переменной в текущей рекурсии.
Следующее выводит False вместо исключения переполнения:
Console.WriteLine("{0}", yep(int.MaxValue ));
private bool yep( int val )
{
return ( 0 < val * 2);
}
Вы можете получить исключение OverflowException, заключив тест в флажок checked {} или установив соответствующий параметр компилятора. Не сразу понятно, почему по умолчанию не установлен флажок ... msdn.microsoft.com/en-us/library/khy08726(VS.71).aspx
По умолчанию флажок не установлен, потому что снижение производительности при выполнении этой проверки для каждой целочисленной операции в коде является дорогостоящим.
Кроме того, по умолчанию для VB все это проверено. Команда компиляторов C# сделала другой выбор для своего значения по умолчанию, пытаясь более точно соответствовать ожиданиям их целевой аудитории.
int.MaxValue * 2 - отрицательное число в непроверенной арифметике, которое используется по умолчанию в C#, там для сравнения возвращается false. Это не неожиданное поведение: P
Этот меня по-настоящему озадачил (прошу прощения за длину, но это WinForm). Я недавно разместил это в группы новостей.
I've come across an interesting bug. I have workarounds but i'd like to know the root of the problem. I've stripped it down into a short file and hope someone might have an idea about what's going on.
It's a simple program that loads a control onto a form and binds "Foo" against a combobox ("SelectedItem") for it's "Bar" property and a datetimepicker ("Value") for it's "DateTime" property. The DateTimePicker.Visible value is set to false. Once it's loaded up, select the combobox and then attempt to deselect it by selecting the checkbox. This is rendered impossible by the combobox retaining the focus, you cannot even close the form, such is it's grasp on the focus.
I have found three ways of fixing this problem.
a) Remove the binding to Bar (a bit obvious)
b) Remove the binding to DateTime
c) Make the DateTimePicker visible !?!
I'm currently running Win2k. And .NET 2.00, I think 1.1 has the same problem. Code is below.
using System;
using System.Collections;
using System.Windows.Forms;
namespace WindowsApplication6
{
public class Bar
{
public Bar()
{
}
}
public class Foo
{
private Bar m_Bar = new Bar();
private DateTime m_DateTime = DateTime.Now;
public Foo()
{
}
public Bar Bar
{
get
{
return m_Bar;
}
set
{
m_Bar = value;
}
}
public DateTime DateTime
{
get
{
return m_DateTime;
}
set
{
m_DateTime = value;
}
}
}
public class TestBugControl : UserControl
{
public TestBugControl()
{
InitializeComponent();
}
public void InitializeData(IList types)
{
this.cBoxType.DataSource = types;
}
public void BindFoo(Foo foo)
{
this.cBoxType.DataBindings.Add("SelectedItem", foo, "Bar");
this.dtStart.DataBindings.Add("Value", foo, "DateTime");
}
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name = "disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.checkBox1 = new System.Windows.Forms.CheckBox();
this.cBoxType = new System.Windows.Forms.ComboBox();
this.dtStart = new System.Windows.Forms.DateTimePicker();
this.SuspendLayout();
//
// checkBox1
//
this.checkBox1.AutoSize = true;
this.checkBox1.Location = new System.Drawing.Point(14, 5);
this.checkBox1.Name = "checkBox1";
this.checkBox1.Size = new System.Drawing.Size(97, 20);
this.checkBox1.TabIndex = 0;
this.checkBox1.Text = "checkBox1";
this.checkBox1.UseVisualStyleBackColor = true;
//
// cBoxType
//
this.cBoxType.FormattingEnabled = true;
this.cBoxType.Location = new System.Drawing.Point(117, 3);
this.cBoxType.Name = "cBoxType";
this.cBoxType.Size = new System.Drawing.Size(165, 24);
this.cBoxType.TabIndex = 1;
//
// dtStart
//
this.dtStart.Location = new System.Drawing.Point(117, 40);
this.dtStart.Name = "dtStart";
this.dtStart.Size = new System.Drawing.Size(165, 23);
this.dtStart.TabIndex = 2;
this.dtStart.Visible = false;
//
// TestBugControl
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.dtStart);
this.Controls.Add(this.cBoxType);
this.Controls.Add(this.checkBox1);
this.Font = new System.Drawing.Font("Verdana", 9.75F,
System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point,
((byte)(0)));
this.Margin = new System.Windows.Forms.Padding(4);
this.Name = "TestBugControl";
this.Size = new System.Drawing.Size(285, 66);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.CheckBox checkBox1;
private System.Windows.Forms.ComboBox cBoxType;
private System.Windows.Forms.DateTimePicker dtStart;
}
public class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Load += new EventHandler(Form1_Load);
}
void Form1_Load(object sender, EventArgs e)
{
InitializeControl();
}
public void InitializeControl()
{
TestBugControl control = new TestBugControl();
IList list = new ArrayList();
for (int i = 0; i < 10; i++)
{
list.Add(new Bar());
}
control.InitializeData(list);
control.BindFoo(new Foo());
this.Controls.Add(control);
}
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name = "disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Text = "Form1";
}
#endregion
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
C# поддерживает преобразования между массивами и списками, если массивы не являются многомерными и между типами существует отношение наследования, а типы являются ссылочными типами.
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" };
Обратите внимание, что это не работает:
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[]'
Пример IList <T> - это просто приведение, потому что string [] уже реализует ICloneable, IList, ICollection, IEnumerable, IList <string>, ICollection <string> и IEnumerable <string>.
Я немного опаздываю на вечеринку, но у меня пять тричетыре:
Если вы опрашиваете InvokeRequired для элемента управления, который не был загружен / показан, он скажет false - и взорвется вам в лицо, если вы попытаетесь изменить его из другого потока (решение должен ссылаться на это. ).
Еще один, который меня сбил с толку, - это сборка с:
enum MyEnum
{
Red,
Blue,
}
если вы вычисляете MyEnum.Red.ToString () в другой сборке, и в промежутке между ними кто-то перекомпилировал ваше перечисление, чтобы:
enum MyEnum
{
Black,
Red,
Blue,
}
во время выполнения вы получите «черный».
У меня была общая сборка с некоторыми удобными константами. Мой предшественник оставил кучу уродливых свойств только для получения, я подумал, что избавлюсь от беспорядка и просто буду использовать public const. Я был более чем немного удивлен, когда VS скомпилировал их по своим значениям, а не по ссылкам.
Если вы реализуете новый метод интерфейса из другой сборки, но перестраиваете, ссылаясь на старую версию этой сборки, вы получаете исключение TypeLoadException (без реализации NewMethod), даже если вы реализовали его в имеют (см. здесь).
Dictionary <,>: «Порядок, в котором возвращаются элементы, не определен». Это какой ужас, потому что иногда он может вас укусить, но работать с другими, и если вы просто слепо предположили, что Dictionary будет хорошо себя вести («почему бы и нет? Я подумал, что List»), вам действительно нужно засуньте нос в это, прежде чем вы наконец начнете подвергать сомнению свое предположение.
№2 - интересный пример. Перечисления - это сопоставления компилятора с целыми значениями. Таким образом, даже если вы явно не присвоили им значения, компилятор это сделал, в результате чего MyEnum.Red = 0 и MyEnum.Blue = 1. Когда вы добавили черный цвет, вы переопределили значение 0 для отображения с красного на черный. Я подозреваю, что проблема могла бы проявиться и в других случаях использования, таких как сериализация.
Требуется +1 для призыва. В нашем случае мы предпочитаем явно присваивать значения перечислениям, например Red = 1, Blue = 2, поэтому новое значение может быть вставлено до или после того, как оно всегда будет приводить к одному и тому же значению. Это особенно необходимо, если вы сохраняете значения в базы данных.
@ aman.tur, возможно, вас заинтересует этот вопрос: stackoverflow.com/questions/881726/…
Я не согласен с тем, что № 5 - это «крайний случай». Словарь не должен иметь определенный порядок, основанный на том, когда вы вставляете значения. Если вам нужен определенный порядок, используйте список или используйте ключ, который может быть отсортирован удобным для вас способом, или используйте совершенно другую структуру данных.
Ну, я думаю, это не крайний случай, но это определенно ошибка (по крайней мере, для меня)
Я потратил много часов на отладку ошибки, связанной с InvokeRequired - это было очень неприятно.
@Wedge, например, SortedDictionary?
Словарь определенно сделан по дизайну, так как отсортирован по хеш-коду ... не так ли?
# 3 происходит потому, что константы вставляются как литералы везде, где они используются (по крайней мере, в C#). Ваш предшественник, возможно, уже заметил это, поэтому они использовали свойство только для получения. Однако переменная только для чтения (в отличие от константы) будет работать точно так же.
№1 - ужасный случай, который нас сильно укусил. Здесь есть отличное объяснение: 209.85.129.132/search?q=cache:www.ikriv.com/en/prog/info/dot net /… (из Google Cache, потому что сайт, кажется, не работает). Это очень хорошая статья, написанная как загадочный случай.
# 2 Еще одна проблема с перечислениями C# ... # 5 Так работают хеш-таблицы. Если вам нужно его заказать, используйте SortedDictionary
Вот пример того, как вы можете создать структуру, которая вызывает сообщение об ошибке «Попытка чтения или записи в защищенную память. Это часто указывает на то, что другая память повреждена». Разница между успехом и неудачей очень тонкая.
Следующий модульный тест демонстрирует проблему.
Посмотри, сможешь ли ты понять, что пошло не так.
[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);
}
}
Голова болит ... Почему не работает?
Второй вопрос - можно ли получить объяснение?
Хм, я написал это несколько месяцев назад, но не могу вспомнить, почему именно это произошло.
Похоже на ошибку компилятора; += 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), а затем решает сделать это другим способом.
Структура - это ValueType, поэтому свойство возвращает копию. Нет смысла пытаться обновить копию типа значения, которая попадает в битовое ведро. Если вы хотите обновить это значение, вы, вероятно, захотите ... MyStruct Temp = bar.Foo; Темп + = 500; bar.Foo = Temp;
Извините за двойной пост, вот более подробное объяснение. Я добавлю это, меня это укусило, и это отстой, хотя я понимаю, почему это происходит. Для меня это досадное ограничение типа структуры / значения. bytes.com/topic/net/answers/…
@Ben получает ошибки компилятора или модификация, не влияющая на исходную структуру, в порядке. Нарушение прав доступа - совсем другое дело. Среда выполнения никогда не должна бросать его, если вы просто пишете безопасный чистый управляемый код.
@configurator: исправлено в C# 4.0
Несколько лет назад при работе над программой лояльности у нас возникла проблема с количеством начисляемых клиентам баллов. Проблема была связана с приведением / преобразованием double в int.
В коде ниже:
double d = 13.6;
int i1 = Convert.ToInt32(d);
int i2 = (int)d;
делает i1 == i2?
Получается, что i1! = I2. Из-за различных политик округления в операторе преобразования и приведения фактические значения:
i1 == 14
i2 == 13
Всегда лучше вызывать Math.Ceiling () или Math.Floor () (или Math.Round с MidpointRounding, который соответствует нашим требованиям)
int i1 = Convert.ToInt32( Math.Ceiling(d) );
int i2 = (int) Math.Ceiling(d);
Приведение к целому числу не округляется, а просто обрезается (фактически всегда округляется в меньшую сторону). Так что в этом есть смысл.
@Max: да, но почему Convert round?
@Stefan Steinegger Если бы все, что он делал, было брошено, для этого не было бы вообще никакой причины, не так ли? Также обратите внимание, что имя класса - Convert not Cast.
В VB: раунды CInt (). Fix () усекает. Сжег меня однажды (blog.wassupy.com/2006/01/i-can-believe-it-not-truncating.ht мл)
Они должны были сделать 0 целым числом, даже если есть перегрузка функции перечисления.
Я знал, что основная команда разработчиков C# использует отображение 0 в enum, но все же это не так ортогонально, как должно быть. Пример из Npgsql.
Пример теста:
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");
}
}
}
Вау, это новость для меня. Также странно, как работает ConverT.ToInt32 (), но приведение к (int) 0 - нет. И любое другое число> 0 работает. (Под «работает» я подразумеваю вызов перегрузки объекта.)
Существует рекомендуемое правило анализа кода для применения передовых практик в отношении этого поведения: msdn.microsoft.com/en-us/library/ms182149%28VS.80%29.aspx Эта ссылка также содержит хорошее описание того, как работает 0-отображение.
@Chris Clark: я попытался поставить None = 0 в enum Symbol. по-прежнему компилятор выбирает enum для 0 и даже (int) 0
Не думаю, что Крис понял истинную суть вашего поста. Не имеет ничего общего с перечислениями как флагами.
Это определено в спецификации, и я думаю, причина в том, что 0 является значением по умолчанию для всех перечислений. Я не программист-компилятор, но я предполагаю, что другой вариант - инициализировать все перечисления первым значением в их списке значений. Что на самом деле было бы намного большей чепухой.
IMO они должны были ввести ключевое слово none, которое можно использовать для преобразования в любое перечисление, и сделать 0 всегда int и неявно преобразованным в перечисление.
ConverTo.ToIn32 () работает, потому что его результат не является константой времени компиляции. И только константа времени компиляции 0 может быть преобразована в перечисление. В более ранних версиях .net даже буквальный 0 должен был быть преобразован в enum. См. Блог Эрика Липперта: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspx
Дело не в преждевременной оптимизации. Я стремлюсь к ортогональности и согласованности языка или его паттернов. В самом деле, только константа 0 может быть преобразована в перечисление; но если 1 соответствует целому числу, почему не 0? (Я знаю ответ, это как-то связано с традициями языка C и приветствием программистов из этого языкового лагеря).
Вау ... я не знала об этом ... действительно шокирует ... Спасибо ...
Если вы хотите вызвать перегрузку object, почему вы выполняете приведение к int? JustTest((object)0) должен звонить правильно.
Область видимости в C# временами бывает действительно причудливой. Приведу один пример:
if (true)
{
OleDbCommand command = SQLServer.CreateCommand();
}
OleDbCommand command = SQLServer.CreateCommand();
Это не может быть скомпилировано, потому что команда повторно объявлена? Есть некоторые интересные предположения относительно того, почему это работает таким образом в этом поток в stackoverflow и в мой блог.
Я не считаю это особенно странным. То, что вы называете «совершенно правильным кодом» в своем блоге, в соответствии со спецификацией языка, является неверный. Это может быть правильным на каком-то воображаемом языке, которым вы бы хотели быть нравиться C#, но в спецификации языка совершенно ясно, что в C# это недопустимо.
Ну действительно в C / C++. И поскольку это C#, мне бы хотелось, чтобы он по-прежнему работал. Что меня больше всего беспокоит, так это то, что у компилятора нет причин делать это. Вложенная область видимости не сложна. Думаю, все сводится к элементу наименьшего удивления. Это означает, что может быть так, что спецификация говорит то или это, но это мне не очень помогает, если совершенно нелогично, что он ведет себя таким образом.
C#! = С / С ++. Хотели бы вы также использовать cout << "Hello World!" << endl; вместо Console.WriteLine ("Hello World!") ;? Также это не нелогично, просто прочтите спецификацию.
Я говорю о правилах области видимости, которые являются частью ядра языка. Вы говорите о стандартной библиотеке. Но теперь мне ясно, что я должен просто прочитать крошечную спецификацию языка C#, прежде чем я начну программировать на нем.
Эрик Липперт недавно опубликовал причины, по которым C# так спроектирован: blogs.msdn.com/ericlippert/archive/2009/11/02/…. Резюме сделано потому, что менее вероятно, что изменения будут иметь непредвиденные последствия.
Спасибо. Это проливает на него довольно много света. Но на самом деле его примеры мне кажутся неправдоподобными. Пример 1 просто выдал бы предупреждение на C++. Я действительно не вижу в них проблемы. Написание такой большой функции также является небрежной практикой программирования, поэтому я не вижу смысла отказываться от языка только для того, чтобы соответствовать людям, которые не могут писать правильный код ;-)
Вот один, о котором я узнал только недавно ...
interface IFoo
{
string Message {get;}
}
...
IFoo obj = new IFoo("abc");
Console.WriteLine(obj.Message);
Вышеупомянутое на первый взгляд выглядит сумасшедшим, но это фактически законный. Нет, на самом деле (хотя я пропустил ключевую часть, но это не что-то хакерское, например «добавить класс с именем IFoo» или «добавить псевдоним using, чтобы указать IFoo на класс").
Посмотрим, сможешь ли ты понять почему, тогда: Кто сказал, что вы не можете создать интерфейс?
+1 за "использование псевдонима" - я никогда не знал, что вы можете сделать который!
взломать компилятор для COM Interop :-)
Сволочь! Вы могли бы хотя бы сказать «при определенных обстоятельствах» ... Мой компилятор опровергает!
Это одно из самых необычных, которые я когда-либо видел (кроме, конечно, здесь!):
public class Turtle<T> where T : Turtle<T>
{
}
Он позволяет вам объявлять его, но не имеет реального применения, так как он всегда будет просить вас обернуть любой класс, который вы поместите в центр, с другой черепахой.
[шутка] Думаю, это черепахи ... [/ шутка]
Однако вы можете создавать экземпляры: class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Верно. Это паттерн, который с большим эффектом используют перечисления Java. Я тоже использую его в буферах протокола.
Но вы не можете этого сделать, поскольку RealTurtle - это не Черепаха <RealTurtle> ....
RCIX, да, это так.
Я довольно часто использовал этот паттерн в причудливых дженериках. Он позволяет делать такие вещи, как правильно типизированный клон или создавать собственные экземпляры.
Я не знаю. Возможно, это не является незаконным, но это определенно одна из самых странных вещей, которые я когда-либо видел.
Это «любопытно повторяющийся шаблон шаблона» en.wikipedia.org/wiki/Curiously_recurring_template_pattern
Я второй Lucero ... используйте его, если вы хотите, чтобы методы базового класса возвращали точный тип наследника.
Это совершенно верно и может быть очень полезно, чтобы позволить базовому классу ссылаться на тип дочернего класса (например, Перечисления Java использует его, чтобы убедиться, что compareTo может передавать только значения этого перечисления). -1 ...
Это не «странно повторяющийся шаблон шаблона», поскольку это не шаблоны, а общие. Не педантично. Использование шаблона в C++ не работает с универсальными шаблонами C#.
+1 за глупую шутку про черепаху.
Я действительно использовал этот шаблон.
Я нашел второй действительно странный угловой случай, который намного превосходит мой первый.
Метод String.Equals (String, String, StringComparison) на самом деле не свободен от побочных эффектов.
Я работал над блоком кода, в котором это было отдельной строкой в верхней части некоторой функции:
stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);
Удаление этой строки приведет к переполнению стека где-то еще в программе.
Оказалось, что код устанавливает обработчик для события BeforeAssemblyLoad и пытается выполнить
if (assemblyfilename.EndsWith("someparticular.dll", StringComparison.InvariantCultureIgnoreCase))
{
assemblyfilename = "someparticular_modified.dll";
}
К настоящему времени я не должен был вам говорить. Использование языка и региональных параметров, который ранее не использовался при сравнении строк, вызывает сборку нагрузки. InvariantCulture не является исключением.
Я предполагаю, что «загрузка сборки» - это побочный эффект, поскольку вы можете наблюдать это с помощью BeforeAssemblyLoad!
Вау. Это отличный выстрел в ногу обслуживающему персоналу. Думаю, написание обработчика BeforeAssemblyLoad может привести к множеству таких сюрпризов.
VB.NET, значения NULL и тернарный оператор:
Dim i As Integer? = If(True, Nothing, 5)
На отладку у меня ушло некоторое время, так как я ожидал, что i будет содержать Nothing.
Что я на самом деле содержу? 0.
Это удивительно, но на самом деле «правильное» поведение: Nothing в VB.NET не совсем то же самое, что null в CLR: Nothing может означать null или default(T) для типа значения T, в зависимости от контекста. В приведенном выше случае If подразумевает Integer как общий тип Nothing и 5, поэтому в данном случае Nothing означает 0.
Интересно, что мне не удалось найти этот ответ, поэтому мне пришлось создать вопрос. Ну кто знал ответ в этой ветке?
PropertyInfo.SetValue () может присваивать целые числа перечислениям, целые числа - целым значениям, допускающим значение NULL, перечисления - перечислениям, допускающим значение NULL, но не целым числам - перечислениям, допускающим значение NULL.
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 !!!
Полное описание здесь
Это то, что я люблю спрашивать на вечеринках (наверное, поэтому меня больше не приглашают):
Сможете ли вы скомпилировать следующий фрагмент кода?
public void Foo()
{
this = new Teaser();
}
Простым читом может быть:
string cheat = @"
public void Foo()
{
this = new Teaser();
}
";
Но настоящее решение таково:
public struct Teaser
{
public void Foo()
{
this = new Teaser();
}
}
Итак, малоизвестный факт, что типы значений (структуры) могут переназначать свою переменную this.
Классы C++ тоже могут это делать ... как я обнаружил недавно, только для того, чтобы на меня накричали за то, что они действительно пытались использовать его для оптимизации: p
На самом деле я использовал новый на месте. Просто хотел эффективный способ обновить все поля :)
Это тоже чит: //this = new Teaser(); :-)
:-) Я бы предпочел эти читы в моем производственном коде, чем эту мерзость переназначения ...
Из CLR через C#: они сделали это потому, что вы можете вызвать конструктор структуры без параметров в другом конструкторе. Если вы хотите инициализировать только одно значение структуры и хотите, чтобы другие значения были равны нулю / нулю (по умолчанию), вы можете написать public Foo(int bar){this = new Foo(); specialVar = bar;}. Это неэффективно и не совсем оправдано (specialVar назначается дважды), но просто к сведению. (Это причина, указанная в книге, я не знаю, почему мы не должны просто делать public Foo(int bar) : this())
@Mark, вы действительно уверены, что можете переназначить само «это»? Вы не говорите о присвоении "* this"?
@dascandy: Ну, вы перезаписываете часть памяти, которую занимает this, IIRC. Интерпретируйте это как хотите.
Тизер @this; @this = новый тизер (); // Также подойдет для класса.
Следующее может быть общими знаниями, которых мне просто не хватало, но да. Некоторое время назад у нас был случай с ошибкой, который касался виртуальных свойств. Немного абстрагируясь от контекста, рассмотрите следующий код и примените точку останова к указанной области:
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 { } }
}
Находясь в контексте объекта Derived, вы можете получить такое же поведение при добавлении base.Property в качестве часов или при вводе base.Property в quickwatch.
Мне потребовалось время, чтобы понять, что происходит. В конце концов, Quickwatch меня просветил. При входе в Quickwatch и изучении объекта d Derived (или из контекста объекта, this) и выборе поля base в поле редактирования в верхней части Quickwatch отображается следующее приведение:
((TestProject1.Base)(d))
Это означает, что при замене базы как таковой вызов будет
public string BaseProperty { get { return ((TestProject1.Base)(d)).Property; } private set { } }
для Watch, Quickwatch и всплывающих подсказок при наведении указателя мыши, и тогда было бы разумно отображать "AWESOME" вместо "BASE_AWESOME" при рассмотрении полиморфизма. Я до сих пор не уверен, почему это преобразовало бы его в приведение, одна из гипотез состоит в том, что call может быть недоступен из контекста этих модулей, и только callvirt.
Во всяком случае, это, очевидно, ничего не меняет с точки зрения функциональности, Derived.BaseProperty действительно вернет "BASE_AWESOME", и, таким образом, это не было корнем нашей ошибки на работе, а просто сбивающим с толку компонентом. Однако мне было интересно, как это может ввести в заблуждение разработчиков, которые не знали об этом факте во время сеансов отладки, особенно если Base не отображается в вашем проекте, а упоминается как сторонняя DLL, в результате чего разработчики просто говорят:
"Oi, wait..what ? omg that DLL is like, ..doing something funny"
Ничего особенного, просто так работают переопределения.
Я не уверен, что это странность Windows Vista / 7 или .Net, но я некоторое время почесал в затылке.
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."
В Windows Vista / 7 файл будет записан на C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt.
Это действительно перспективное (а не 7, афайк) улучшение безопасности. Но круто то, что вы можете прочитать и открыть файл с путем к программным файлам, а если вы посмотрите туда с помощью проводника, там ничего нет. Это заняло у меня почти день работы с клиентом, прежде чем я наконец узнал об этом.
Это определенно вещь Windows 7. Это то, что я использовал, когда столкнулся с этим. Я понимаю причину этого, но все равно было сложно понять.
В Vista / Win 7 (технически WinXP тоже) приложения должны записывать в папку AppData на земле папок «Пользователи» в качестве своих технически пользовательских данных. Приложения не должны писать в файлы программ / windows / system32 / и т. д., Если у них нет прав администратора, и эти привилегии должны быть там только для того, чтобы сказать: обновить программу / удалить ее / установить новую функцию. НО! По-прежнему не пишите в system32 / windows / etc :) Если вы запустили этот код выше от имени администратора (щелкните правой кнопкой мыши> запустить от имени администратора), он теоретически должен записать в папку приложения программных файлов.
Похоже на виртуализацию - crispybit.spaces.live.com/blog/cns!1B71C2122AD43308!134.entr y
Это самое странное, с чем я столкнулся случайно:
public class DummyObject
{
public override string ToString()
{
return null;
}
}
Используется следующим образом:
DummyObject obj = new DummyObject();
Console.WriteLine("The text: " + obj.GetType() + " is " + obj);
Кинет NullReferenceException. Оказывается, несколько добавлений компилируются компилятором C# для вызова String.Concat(object[]). До .NET 4 была ошибка только в той перегрузке Concat, когда объект проверялся на null, но не на результат 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;
Это ошибка ECMA-334 §14.7.4:
The binary + operator performs string concatenation when one or both operands are of type
string. If an operand of string concatenation isnull, an empty string is substituted. Otherwise, any non-string operand is converted to its string representation by invoking the virtualToStringmethod inherited from typeobject. IfToStringreturnsnull, an empty string is substituted.
Хм, но я могу представить себе эту ошибку, поскольку .ToString действительно никогда не должен возвращать null, а string.Empty. Тем не менее и ошибка в рамках.
В C# есть что-то действительно захватывающее - то, как он обрабатывает замыкания.
Вместо того, чтобы копировать значения переменных стека в переменную, свободную от замыканий, он выполняет эту магию препроцессора, оборачивая все вхождения переменной в объект и, таким образом, перемещает его из стека - прямо в кучу! :)
Я предполагаю, что это делает C# даже более функционально-полным (или лямбда-полным, да)) языком, чем сам ML (который использует копирование значений стека AFAIK). У F# тоже есть эта функция, как у C#.
Мне это очень порадовало, спасибо, ребята, MS!
Хотя это не странность или угловой случай ... а что-то действительно неожиданное из языка виртуальных машин на основе стека :)
Из вопроса, который я задал недавно:
Условный оператор не может привести к неявному преобразованию?
Дано:
Bool aBoolValue;
Где aBoolValue присвоено значение "Истина" или "Ложь";
Следующее не будет компилироваться:
Byte aByteValue = aBoolValue ? 1 : 0;
Но это могло бы:
Int anIntValue = aBoolValue ? 1 : 0;
Предоставленный ответ тоже довольно хорош.
хотя Ive not test it Iм уверен, что это сработает: Byte aByteValue = aBoolValue? (Байт) 1: (Байт) 0; Или: Byte aByteValue = (Byte) (aBoolValue? 1: 0);
Да, Алекс, это сработает. Ключ кроется в неявном приведении типов. Только 1 : 0 будет неявно приводить к типу int, а не Byte.
Вы когда-нибудь думали, что компилятор C# может генерировать недопустимый CIL? Запустите это, и вы получите 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));
}
}
Однако я не знаю, как это работает в компиляторе C# 4.0.
РЕДАКТИРОВАТЬ: это результат моей системы:
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]
У меня работает как с компилятором C# 3.5, так и с компилятором C# 4 ...
В моей системе это не работает. Я вставлю вывод в вопрос.
В .NET 3.5 у меня ничего не вышло (нет времени тестировать 4.0). И я могу воспроизвести проблему с кодом VB.NET.
Что делать, если у вас есть универсальный класс, в котором есть методы, которые могут быть неоднозначными в зависимости от аргументов типа? Я столкнулся с этой ситуацией недавно, когда писал двусторонний словарь. Я хотел написать симметричные методы Get(), которые возвращали бы противоположность переданному аргументу. Что-то вроде этого:
class TwoWayRelationship<T1, T2>
{
public T2 Get(T1 key) { /* ... */ }
public T1 Get(T2 key) { /* ... */ }
}
Все хорошо, если вы сделаете пример, в котором T1 и T2 - это разные типы:
var r1 = new TwoWayRelationship<int, string>();
r1.Get(1);
r1.Get("a");
Но если T1 и T2 одинаковы (и, вероятно, если один был подклассом другого), это ошибка компилятора:
var r2 = new TwoWayRelationship<int, int>();
r2.Get(1); // "The call is ambiguous..."
Интересно, что все остальные методы во втором случае по-прежнему можно использовать; это только вызовы неоднозначного теперь метода, вызывающего ошибку компилятора. Случай интересный, хоть и маловероятный и непонятный.
Противникам перегрузки методов он понравится ^^.
Я не знаю, для меня это имеет смысл.
В используемом нами API методы, возвращающие объект домена, могут возвращать специальный «нулевой объект». В реализации этого оператор сравнения и метод Equals() переопределяются для возврата true, если он сравнивается с null.
Таким образом, у пользователя этого API может быть такой код:
return test != null ? test : GetDefault();
или, возможно, более подробно, например:
if (test == null)
return GetDefault();
return test;
где GetDefault() - это метод, возвращающий некоторое значение по умолчанию, которое мы хотим использовать вместо null. Я был удивлен, когда использовал ReSharper и, следуя его рекомендации, переписал любое из этого следующим образом:
return test ?? GetDefault();
Если тестовый объект является нулевым объектом, возвращаемым из API, а не правильным null, поведение кода теперь изменилось, поскольку нулевой оператор объединения фактически проверяет null, а не запускает operator= или Equals().
не совсем угловой случай C#, но дорогой господин, кто это придумал?!?
Разве этот код не использует только типы, допускающие значение NULL? Поэтому ReSharper рекомендует знак "??" использовать. Как сказал Рэй, я бы не подумал, что это угловой случай; или я ошибаюсь?
Да, типы допускают значение NULL - кроме того, существует NullObject. Если это угловой случай, я не знаю, но, по крайней мере, это тот случай, когда 'if (a! = Null) return a; return b; ' это не то же самое, что «вернуть ?? б '. Я абсолютно согласен с тем, что это проблема с дизайном фреймворка / API - перегрузка == null для возврата true для объекта, конечно, не лучшая идея!
Следующее не работает:
if (something)
doit();
else
var v = 1 + 2;
Но это работает:
if (something)
doit();
else {
var v = 1 + 2;
}
Я не понимаю, почему это угловой случай ... В первом примере вы не можете использовать переменную v, так как ее область действия - блок else, и вы можете иметь в ней только одну инструкцию, если вы этого не сделаете. я поставил брекеты
Я не вижу разницы в двух фрагментах кода.
@ Томас: Да, но почему это ошибка? Я мог бы захотеть добавить это выражение, чтобы иметь возможность разбить предложение else. В C++ это совершенно верно. Мне неприятно, что существует семантическая разница между else {} и else, когда в предложении есть только одно выражение.
@Anders: В ваших ответах большое внимание уделяется тому факту, что C# отличается от C++, например, здесь: stackoverflow.com/questions/194484/… Этот поток не касается различий между C# и C++. Пограничный регистр в C# ничем не отличается от C++. Другие отметили, что вы найдете ответы в спецификации C#.
@jdk: Я добавил примечание C++ для полноты. Я согласен с тем, что это может быть не самый большой крайний случай, который я видел, меня просто удивило, когда я нашел его вчера.
После небольшого размышления я вижу, что это поможет людям, плохо знакомым с программированием, и почти не повредит нормальным программистам. Так что вместо того, чтобы называть это угловым случаем, можно было бы назвать это особенностью :-)
Следующий производный класс обращается к частное поле из своего базового класса, и компилятор молча смотрит на другую сторону:
public class Derived : Base
{
public int BrokenAccess()
{
return base.m_basePrivateField;
}
}
Поле действительно частное:
private int m_basePrivateField = 0;
Угадайте, как можно скомпилировать такой код?
.
.
.
.
.
.
.
Хитрость заключается в том, чтобы объявить Derived как внутренний класс Base:
public class Base
{
private int m_basePrivateField = 0;
public class Derived : Base
{
public int BrokenAccess()
{
return base.m_basePrivateField;
}
}
}
Внутренним классам предоставляется полный доступ к внешним членам класса. В этом случае внутренний класс также является производным от внешнего класса. Это позволяет нам «нарушить» инкапсуляцию закрытых членов.
Это действительно хорошо задокументировано; msdn.microsoft.com/en-us/library/ms173120%28VS.80%29.aspx. Иногда это может быть полезной функцией, особенно если внешний класс статичен.
Да, конечно, это задокументировано. Однако очень немногие люди решали эту головоломку, поэтому я подумал, что это классная мелочь.
Похоже, у вас будет очень большая вероятность переполнения стека, если внутренний класс унаследует своего владельца ...
Еще один похожий (и совершенно правильный) случай - объект может получить доступ к закрытому члену другого объекта того же типа: class A { private int _i; public void foo(A other) { int res = other._i; } }
Если у вас есть метод расширения:
public static bool? ToBoolean(this string s)
{
bool result;
if (bool.TryParse(s, out result))
return result;
else
return null;
}
и этот код:
string nullStr = null;
var res = nullStr.ToBoolean();
Это не вызовет исключения, потому что это метод расширения (и на самом деле HelperClass.ToBoolean(null)), а не метод экземпляра. Это может сбивать с толку.
Я не думаю, что это какой-то странный угловой вариант, более заурядный синтаксис. Такое поведение позволяет вам делать такие вещи, как static void IfNotNull<T>(Action<T> action) ... Если у вашего метода расширения есть проблема с нулевым параметром this, то бросьте ArgumentNullException.
@Keith Это, безусловно, может быть полезно, но когда вы посмотрите на это (с точки зрения Java, C++, C# 2), это будет странно, и как разработчику C# 3+ вам все равно придется проверять, действительно ли это метод расширения (не в строках, а в более сложных примерах), а не в методе экземпляра, где они (другой код) забыли нулевую проверку.
Я полагаю, что моя точка зрения заключается в том, что метод расширения лучше во всех местах, где вы бы использовали его, а не метод экземпляра. Возьмите свой примерный метод: он возвращает bool? - вполне приемлемо (даже предпочтительно), чтобы ваш nullStr.ToBoolean() возвращал null, а не выдавал NullReferenceException
Я думаю, он говорит, что если бы вы унаследовали код и увидели фрагмент, не зная определения метода расширения, это бы сбило с толку.
Я думаю, им следовало использовать другой символ. Как конвейер в F#. nullStr|>ToBoolean или nullStr->ToBoolean.
вот несколько моих:
Сначала простой: перечисление NoZero { Число = 1 }
public bool ReturnsFalse()
{
//The default value is not defined!
return Enum.IsDefined(typeof (NoZero), default(NoZero));
}
Приведенный ниже код действительно может печатать истину!
internal sealed class Strange
{
public void Foo()
{
Console.WriteLine(this == null);
}
}
Простой фрагмент клиентского кода, который приведет к тому, что делегат void HelloDelegate (Странная полоса);
public class Program
{
[STAThread()]
public static void Main(string[] args)
{
Strange bar = null;
var hello = new DynamicMethod("ThisIsNull",
typeof(void), new[] { typeof(Strange) },
typeof(Strange).Module);
ILGenerator il = hello.GetILGenerator(256);
il.Emit(OpCodes.Ldarg_0);
var foo = typeof(Strange).GetMethod("Foo");
il.Emit(OpCodes.Call, foo);
il.Emit(OpCodes.Ret);
var print = (HelloDelegate)hello.CreateDelegate(typeof(HelloDelegate));
print(bar);
Console.ReadLine();
}
}
это действительно так для большинства языков, пока метод экземпляра при вызове не использует состояние объекта. это разыменовывается только при обращении к состоянию объекта
Случай с перечислением на самом деле не удивителен, типом, лежащим в основе перечисления по умолчанию, является int, поэтому по умолчанию перечисление вернет 0, что действительно не определено в NoZero. Даже при указании настраиваемого типа (в пределах byte, sbyte, short, ushort, int, uint, long или ulong) для вашего перечисления значение по умолчанию для всех этих типов по-прежнему равно 0.
@Dynami да, это из-за значения по умолчанию для базового типа, но (для меня) довольно бессмысленно иметь недопустимое значение по умолчанию для типа значения, которое он имеет (1, -1) по умолчанию для int. Значение просто не имеет смысла в контексте данного типа
Рассмотрим этот странный случай:
public interface MyInterface {
void Method();
}
public class Base {
public void Method() { }
}
public class Derived : Base, MyInterface { }
Если Base и Derived объявлены в одной сборке, компилятор сделает Base::Method виртуальным и запечатанным (в CIL), даже если Base не реализует интерфейс.
Если Base и Derived находятся в разных сборках, при компиляции сборки Derived компилятор не изменит другую сборку, поэтому он представит член в Derived, который будет явной реализацией для MyInterface::Method, который просто делегирует вызов Base::Method.
Компилятор должен сделать это, чтобы поддерживать полиморфную отправку в отношении интерфейса, т.е. он должен сделать этот метод виртуальным.
Это действительно звучит странно. Придется разобраться позже :)
@Jon Skeet: Я нашел это, исследуя стратегии реализации для роли в C#. Было бы здорово получить ваш отзыв об этом!
Только что нашел сегодня приятную вещицу:
public class Base
{
public virtual void Initialize(dynamic stuff) {
//...
}
}
public class Derived:Base
{
public override void Initialize(dynamic stuff) {
base.Initialize(stuff);
//...
}
}
Это вызывает ошибку компиляции.
Вызов метода Initialize должен быть отправлен динамически, но не может быть, потому что он является частью базового выражения доступа. Рассмотрите возможность преобразования динамических аргументов или исключения базового доступа.
Если я напишу base.Initialize (материал как объект); он работает отлично, однако здесь это кажется "волшебным словом", поскольку он делает то же самое, все по-прежнему воспринимается как динамическое ...
Это довольно просто, но я все же считаю его несколько интересным. Каким будет значение x после вызова Foo?
static int x = 0;
public static void Foo()
{
try { return; }
finally { x = 1; }
}
static void Main() { Foo(); }
Каков крайний случай в вашем ответе?
Максим: Верно. Дэнни: Это не совсем крайний случай, но он сочетается с крайними случаями - это то, что нелегко отследить, особенно когда вы работаете с чьим-то кодом.
Это довольно сложно превзойти. Я столкнулся с этим, когда пытался создать реализацию RealProxy, которая действительно поддерживает Begin / EndInvoke (спасибо MS за то, что это невозможно без ужасных хаков). Этот пример в основном представляет собой ошибку в CLR, путь неуправляемого кода для BeginInvoke не подтверждает, что возвращаемое сообщение от RealProxy.PrivateInvoke (и мое переопределение Invoke) возвращает экземпляр IAsyncResult. Как только он возвращается, CLR невероятно сбивается с толку и теряет представление о том, что происходит, как показывают тесты внизу.
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.
}
}
}
Выход:
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)
Мне нравятся эти головоломки. Я думаю, что большинство из них - просто интересные и странные крайние случаи, но тот, который касается захвата переменных анонимными методами, больше похож на нечто, что каждый должен понять.