Недавно я работал с объектом DateTime и написал что-то вроде этого:
DateTime dt = DateTime.Now;
dt.AddDays(1);
return dt; // still today's date! WTF?
В документации intellisense для AddDays() говорится, что он добавляет день к дате, чего нет - на самом деле это возвращается дата с добавленным днем, поэтому вы должны написать это так:
DateTime dt = DateTime.Now;
dt = dt.AddDays(1);
return dt; // tomorrow's date
Этот уже несколько раз меня кусал, поэтому я подумал, что было бы полезно внести в каталог худшие ошибки C#.
AFAIK, все встроенные типы значений неизменяемы, по крайней мере, в том смысле, что любой метод, включенный в тип, возвращает новый элемент, а не изменяет существующий элемент. По крайней мере, я не могу придумать ни одного, который бы не делал этого: все хорошо и последовательно.
Вики сообщества, сейчас так много спама в ТАК. Если вопрос субъективен (нет окончательного ответа), это должна быть вики Сообщества.
Изменяемый тип значения: System.Collections.Generics.List.Enumerator :( (И да, вы можете увидеть, как он ведет себя странно, если вы достаточно постараетесь.)
Лол, я знал, что будут исключения. Перечислитель в любом случае кажется ... особенным. Позвольте мне уточнить это для типов непосредственно в пространстве имен System, хотя даже там вы все равно можете что-то найти.
Intellisense предоставит вам всю необходимую информацию. В нем говорится, что он возвращает объект DateTime. Если бы он просто изменил тот, который вы передали, это был бы недействительный метод.
Не обязательно: например, StringBuilder.Append (...) возвращает "this". Это довольно часто встречается в свободных интерфейсах.
Вот почему мне нравится "!" соглашение в Lisp, AddDays (возвращает новое значение) и AddDays! (изменяет существующее значение) легко и мгновенно различимы.
Разве вы не ненавидите все это, какие ваши самые большие / смешные / и т. д. Сообщения на SO?
Все структуры в System.Drawing (версии Point, Size, Rectangle и float) являются изменяемыми. А вот Color - нет.
Должен быть назван dt.NextDays(1);, программисты не будут знать, что он меняет свое собственное значение. Точно так же, как конструкции структуры данных (например, node->next, node.next) не изменяют значение узла. И ... уже слишком поздно, они могли бы дать dt.AddDays(1) семантику, которая добавляла дни в его собственном значении.
Я согласен с тем, что данный пример касается именования. Название метода подразумевает, что он прибавляется к дате. Что-то вроде dt.PlusDays(1) было бы понятнее. Другая проблема с этим методом заключается в том, что он принимает только значение double, поэтому результат неточный.
Я бы выиграл этот вопрос, если бы глупый человек не закрыл эту тему. Я знаю самую злую ошибку в мире. См. codeproject.com/Feature/…
@bluefeet, поскольку пользователи предоставили> = 62 ответа (включая Джона Скита), многие из которых получили сотни положительных голосов, по-видимому, пользователи хотят даже такие вопросы. Либо это вопрос подходящего типа, либо пришло время внести поправку в конституцию ко всем ограничивающим правилам SO.





Я видел это на днях, и я думаю, что это довольно непонятно и болезненно для тех, кто не знает
int x = 0;
x = x++;
return x;
Поскольку это вернет 0, а не 1, как большинство ожидает
Я надеюсь, что это на самом деле не укусит людей - я действительно надеюсь, что они вообще не напишут это! (Все равно это интересно, конечно.)
пре-инкремент действительно работает, пост-инкремент - нет. Я изменил это.
Я не думаю, что это очень непонятно ...
tvanfosson спасибо за исправление, я заметил это сразу после публикации. Я согласен, что вам НЕ СЛЕДУЕТ видеть это, но, черт возьми, я видел гораздо худшие вещи ...
Я согласен с Крисом, в этом нет ничего неясного. Очень удобно иметь приращение после присваивания.
По крайней мере, в C# результаты определены, если они неожиданные. В C++ это может быть 0 или 1 или любой другой результат, включая завершение программы!
Это не ошибка; x = x ++ -> x = x, затем увеличить x .... x = ++ x -> увеличить x, затем x = x
В следующем году я буду преподавать C#, и это именно то, что может делать студент! И если вы не ожидаете, это может быть проба для обнаружения.
Если я правильно понимаю, это функционально эквивалентно int temp = x; x ++; x = темп;
Это работает: int x = 0; x ++; вернуть x;
@Kevin: Я не думаю, что все так просто. Если бы x = x ++ был эквивалентен x = x, за которым следует x ++, тогда результат был бы x = 1. Вместо этого, я думаю, сначала вычисляется выражение справа от знака равенства (дается 0), затем x равно увеличивается (что дает x = 1), и, наконец, выполняется присваивание (снова возвращая x = 0).
@ Тим Гудман Я согласен, то, что я сказал, не имело смысла.
Это совсем не так очевидно. В C / C++ это фактически неопределенный код - x может быть 0 или 1, в зависимости от компилятора!
х = ++ х; вернуть x; // это то, что вы ищете
Да, результат мне кажется очевидным. Не знаете, что сбивает с толку в операторе пост-инкремента и в какой ситуации кто-нибудь мог бы написать такой код?
-1: это в точности так, как ожидалось, из-за очень хорошо определенного порядка выполнения C#.
«По крайней мере, в C# результаты определены». Нет, потому что это не потокобезопасно.
LOL многие старшие разработчики, которых я знаю, не могут отличить ++ x от x ++
Просто простое непонимание между x ++ и ++ x. Я считаю, что вы ищете: int x = 0; х = ++ х; вернуть x;
@IdahoSixString - Нет, это не так. У x= никогда нет причин быть частью оператора с ++x или 'x ++'. Это неправильное понимание того, что именно делает оператор приращения.
Собственно в каком-то унаследованном коде встречался с этой ошибкой. Это так странно выглядело, это привлекло мое внимание. Не знаю, почему они когда-либо думали, что это сработало. Я использовал его во вступительном интервью.
mystring.Replace("x","y")
Хотя похоже, что он должен выполнить замену в строке, для которой он вызывается, на самом деле он возвращает новую строку с заменами, сделанными без изменения строки, для которой она вызывается. Вы должны помнить, что строки неизменяемы.
Это непреложно; myString не изменяется, он возвращает новую строку, в которой «x» заменено на «y».
Хорошее соглашение (к сожалению, нечасто используется в .net) - использовать два шаблона: «string GetReplaced ()» для вызова, возвращающего копию, и «void Replace ()» для методов, изменяющих исходный объект. В этом случае довольно сложно смешать различные модели поведения / использования.
То же? Что ты имеешь в виду? (Я знаю, вы имеете в виду, что .Replace возвращает измененную строку, но не изменяет mystring. Вам действительно следует отредактировать свой ответ, чтобы он стоял отдельно).
Тип.GetType
Тот, который, как я видел, укусил множество людей, - это Type.GetType(string). Они задаются вопросом, почему это работает для типов в их собственной сборке и некоторых типов, таких как System.String, но не для System.Windows.Forms.Form. Ответ в том, что он смотрится только в текущей сборке и в mscorlib.
Анонимные методы
В C# 2.0 были введены анонимные методы, что приводило к таким неприятным ситуациям:
using System;
using System.Threading;
class Test
{
static void Main()
{
for (int i=0; i < 10; i++)
{
ThreadStart ts = delegate { Console.WriteLine(i); };
new Thread(ts).Start();
}
}
}
Что будет напечатано? Что ж, это полностью зависит от расписания. Он напечатает 10 чисел, но, вероятно, не напечатает 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, чего вы могли ожидать. Проблема в том, что была захвачена переменная i, а не ее значение в момент создания делегата. Это можно легко решить с помощью дополнительной локальной переменной нужной области:
using System;
using System.Threading;
class Test
{
static void Main()
{
for (int i=0; i < 10; i++)
{
int copy = i;
ThreadStart ts = delegate { Console.WriteLine(copy); };
new Thread(ts).Start();
}
}
}
Отложенное выполнение блоков итератора
Этот «юнит-тест для бедняков» не проходит - почему бы и нет?
using System;
using System.Collections.Generic;
using System.Diagnostics;
class Test
{
static IEnumerable<char> CapitalLetters(string input)
{
if (input == null)
{
throw new ArgumentNullException(input);
}
foreach (char c in input)
{
yield return char.ToUpper(c);
}
}
static void Main()
{
// Test that null input is handled correctly
try
{
CapitalLetters(null);
Console.WriteLine("An exception should have been thrown!");
}
catch (ArgumentNullException)
{
// Expected
}
}
}
Ответ заключается в том, что код в исходном коде CapitalLetters не выполняется до тех пор, пока не будет вызван метод MoveNext() итератора.
У меня на страница головоломок есть и другие странности.
Пример итератора коварный!
Тем не менее, это то, как это должно работать. Сложно понять это, но если немного поиграться с этим, это действительно очень полезно.
почему бы не разделить это на 3 ответа, чтобы мы могли проголосовать за каждого, а не за всех вместе?
@chakrit: Оглядываясь назад, наверное, это было бы хорошей идеей, но я думаю, что сейчас уже слишком поздно. Также могло показаться, что я просто пытался получить больше репутации ...
На самом деле Type.GetType работает, если вы указываете AssemblyQualifiedName. Type.GetType ("System.ServiceModel.EndpointNotFoundException, System.ServiceModel, Version = 3.0.0.0, Culture = нейтральный, PublicKeyToken = b77a5c561934e089");
Эй подожди!!! Я не могу понять №1 ваших головоломок ... "Derived.Foo (object)", можете ли вы объяснить, почему int разрешается как объект? О_О ;;
@kentaromiura: разрешение перегрузки начинается с наиболее производного типа и работает вверх по дереву, но только с учетом методов первоначально объявленный того типа, на который он смотрит. Foo (int) переопределяет базовый метод, поэтому не рассматривается. Foo (объект) применим, поэтому разрешение перегрузки на этом заканчивается. Странно, я знаю.
Что касается вашей проблемы с "Анонимными методами", я только что наткнулся на это на днях: blogs.msdn.com/ericlippert/archive/2009/11/12/…
может ли кто-нибудь из тел, пожалуйста, объяснить эту строку 'ThreadStart ts = delegate {Console.WriteLine (i); }; '... я не понимаю, что именно делает
@FosterZ: он создает делегата типа ThreadStart, который выводит текущее значение i на консоль.
@JonSkeet, вы упомянули, что из-за планирования ваш пример может не распечатать 1, 2, 3, 4, 5, 6, 7, 8, 9 и т. д. Применяется ли тот же принцип к циклам Parallel.For / Parallel.ForEach ? (Я только что задал этот вопрос: stackoverflow.com/questions/13142099/…)
@activwerx: Да - когда вы делаете что-то параллельно, вы не должны ожидать, что они будут выполняться в исходном порядке.
Я думаю, что последний пример работает как надо. Однако это может показаться странным - как может выглядеть странным любая структура управления, похожая на продолжение.
@SargeBorsch: Это зависит от того, что вы имеете в виду под «как должно». Он, конечно же, следует спецификациям - но он не ведет себя так, как многие люди ожидать.
перегруженные операторы == и нетипизированные контейнеры (массивы, наборы данных и т. д.):
string my = "my ";
Debug.Assert(my+"string" == "my string"); //true
var a = new ArrayList();
a.Add(my+"string");
a.Add("my string");
// uses ==(object) instead of ==(string)
Debug.Assert(a[1] == "my string"); // true, due to interning magic
Debug.Assert(a[0] == "my string"); // false
Решения?
всегда используйте string.Equals(a, b) при сравнении строковых типов
используя универсальные шаблоны, такие как List<string>, чтобы оба операнда были строками.
У вас есть лишние пробелы, которые делают все неправильно - но если вы уберете пробелы, последняя строка все равно будет истинной, поскольку "my" + "строка" все еще является константой.
да! ты прав :) ок, я немного отредактировал.
при таком использовании выдается предупреждение.
Да, одним из самых больших недостатков языка C# является оператор == в классе Object. Они должны были заставить нас использовать ReferenceEquals.
Любой, кто программировал на C, сразу подумает о вещах таким образом. Конечно, можно подумать, что это должен быть ==(string), не виртуальный?
@Earlz: операторы в C# никогда не бывают виртуальными.
@ Джимми, ах, где тогда проблема.
К счастью, начиная с версии 2.0 у нас есть дженерики. Не о чем беспокоиться, если вы используете List <string> в приведенном выше примере вместо ArrayList. Плюс мы получили от этого производительность, ура! Я всегда исключаю старые ссылки на ArrayLists в нашем устаревшем коде.
Вот еще один случай, который меня поймал:
static void PrintHowLong(DateTime a, DateTime b)
{
TimeSpan span = a - b;
Console.WriteLine(span.Seconds); // WRONG!
Console.WriteLine(span.TotalSeconds); // RIGHT!
}
TimeSpan.Seconds - это секундная часть промежутка времени (2 минуты и 0 секунд имеют значение секунд 0).
TimeSpan.TotalSeconds - это полный промежуток времени, измеренный в секундах (2 минуты имеют общее значение 120 секунд).
Я не знал этого. Хорошо, что я обычно использую Environment.TickCount
Примерно год назад этот обжигал меня неделями
Да, этот тоже меня достал. Я думаю, это должен быть TimeSpan.SecondsPart или что-то в этом роде, чтобы было более понятно, что он представляет.
Перечитывая это, я должен задаться вопросом, почему TimeSpan даже имеет является свойством Seconds вообще. Кому вообще надоедает, сколько секунд в промежутке времени? Это произвольное значение, зависящее от единицы измерения; Я не могу представить себе практического применения этого.
Для меня имеет смысл, что TimeSpan.TotalSeconds вернет ... общее количество секунд в промежутке времени.
@MusiGenesis свойство полезно. Что, если я хочу отобразить временной интервал с разбивкой по частям? Например. скажем, ваш временной интервал представляет собой продолжительность «3 часа 15 минут 10 секунд». Как вы можете получить доступ к этой информации без свойств Seconds, Hours, Minutes?
Мой связанный с этим вопрос здесь stackoverflow.com/questions/8894425/…
@SolutionYogi Вы должны легко модулировать TotalSeconds на 60, чтобы найти 10, но я согласен, что наличие свойства Seconds более дружелюбно. В любом случае, вы никогда не должны слепо предполагать, что делает свойство на основе его имени, не проверяя результат.
В аналогичных API я использовал SecondsPart и SecondsTotal, чтобы различать их.
private int myVar;
public int MyVar
{
get { return MyVar; }
}
Бламмо. Ваше приложение вылетает без трассировки стека. Все время случается.
(Обратите внимание на заглавную MyVar вместо строчной myVar в геттере.)
и ТАК подходит для этого сайта :)
Я поставил подчеркивание на закрытом члене, очень помогает!
Этот получает приз из-за своей злобности и иронии, связанной с переполнением стека.
Если я не ошибаюсь, resharper может предупредить вас об этой возможной проблеме.
Я использую автоматические свойства там, где могу, это часто решает такие проблемы;)
У меня была такая же проблема, когда я переопределял свойство и забывал указать base.PropertyName вместо PropertyName.
Я знаю, что невозможно программно определить бесконечный цикл, но это такой простой цикл, я всегда думал, что VS должен выдать предупреждение об этом!
Я действительно видел, как такие вещи приводят к сбою VS ide, когда свойство отображается во время разработки.
Это ОТЛИЧНАЯ причина использовать префиксы для ваших личных полей (есть и другие, но это хороший): _myVar, m_myVar
@jrista: О, пожалуйста, НЕТ ... не м_ ... ах уж ужас ...
@fretje: если тебе нравятся сбои больше, чем у м_ ...
Это делается очень быстро, но может занять некоторое время. Противный!
просто используйте _myVariableName для частного поля.
Я думаю, что префикс «мой» отлично работает: private int myAge; public int Age {получить {вернуть myAge; }}
Просто сделал это на днях, глядя на свой код, я понял, что вызываю свойство внутри свойства. И тот, кто сказал использовать m_, должен быть застрелен ;-)
В результате это ОЧЕНЬ маловероятно в выпущенном коде, если вы вообще когда-либо его тестировали. В отличие от множества других ошибок в этом потоке, этот обнаруживается при первом выполнении этого кода.
Мне не нравятся префиксы «m_», «s_» и т. д., Поэтому я предпочитаю ссылаться на такие поля, как this.fieldName, а не только на fieldName. Для статических полей я использую префикс подчеркивания: _staticFieldName, из-за отсутствия лучшего представления.
Решарпер говорит: «Функция рекурсивна на всех путях». , подчеркивает его синим цветом и помещает значок круга со стрелкой на панель точки останова. Это бы подбросило для меня достаточно красных флажков! (бесстыдный плагин для resharper ... и вы можете избежать всех этих уродливых переменных m_)
Есть много причин, по которым вы никогда не должны называть двух членов одним и тем же словом с разницей только в регистре. Одним из них является тот факт, что VB.NET нечувствителен к регистру, и любой интерфейс C#, доступный для VB.NET, будет представлять для использования только первую из одноименных переменных.
Сборка мусора и Dispose (). Хотя вам не нужно ничего делать, чтобы освободить объем памяти, вам все равно нужно освободить Ресурсы с помощью Dispose (). Об этом очень легко забыть, когда вы используете WinForms или каким-либо образом отслеживаете объекты.
Блок using () аккуратно решает эту проблему. Всякий раз, когда вы видите вызов Dispose, вы можете немедленно и безопасно выполнить рефакторинг для использования using ().
Я думаю, что проблема была в реализация IDisposable правильно.
Это большая проблема и в компактной платформе .NET, где ресурсы сильно ограничены.
С другой стороны, привычка using () может вас неожиданно укусить, например, при работе с PInvoke. Вы не хотите избавляться от чего-то, на что все еще ссылается API.
Правильная реализация IDisposable очень сложна, и даже лучший совет, который я нашел по этому поводу (Руководство по .NET Framework), может сбивать с толку, пока вы, наконец, не «получите его».
Лучший совет, который я когда-либо находил по IDisposable, исходит от Стивена Клири, включая три простых правила и подробная статья об IDisposable.
@JeremyFrey Неправда, вы можете избавляться от них спустя много времени после того, как начальный блок, объявивший значение, закончился.
Некоторый код:
List<int> a = new List<int>();
for (int i = 0; i < 10; i++)
{
a.Add(i);
}
var q1 = (from aa in a
where aa == 2
select aa).Single();
var q2 = (from aa in a
where aa == 2
select aa).First();
q1 - в этом запросе проверять все числа в списке; q2 - проверять целые числа, пока не найдешь "правильное" целое.
Это должно быть несколько очевидно ... q1 должен проверить весь список, чтобы убедиться, что есть только одно совпадение.
Я думаю, что многие люди не знают о .First ()
Если вы кодируете MOSS и получаете ссылку на сайт следующим образом:
SPSite oSiteCollection = SPContext.Current.Site;
а позже в вашем коде вы говорите:
oSiteCollection.Dispose();
От MSDN:
If you create an SPSite object, you can use the Dispose method to close the object. However, if you have a reference to a shared resource, such as when the object is provided by the GetContextSite method or Site property (for example, SPContext.Current.Site), do not use the Dispose method to close the object, but instead allow Windows SharePoint Services or your portal application to manage the object. For more information about object disposal, see Best Practices: Using Disposable Windows SharePoint Services Objects.
Это случается с каждым программистом MOSS и в какой-то момент.
Итак, что происходит, когда вы его утилизируете. Вы убиваете весь сайт?
Вы теряете ссылку на сайт
Когда вы запускаете процесс (используя System.Diagnostics), который записывает в консоль, но вы никогда не читаете поток Console.Out, после определенного количества выходных данных ваше приложение будет зависать.
То же самое может произойти, если вы перенаправляете как stdout, так и stderr и последовательно используете два вызова ReadToEnd. Для безопасной обработки как stdout, так и stderr вы должны создать поток чтения для каждого из них.
область видимости переменных цикла foreach!
var l = new List<Func<string>>();
var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" };
foreach (var s in strings)
{
l.Add(() => s);
}
foreach (var a in l)
Console.WriteLine(a());
выводит пять «амет», а следующий пример работает нормально
var l = new List<Func<string>>();
var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" };
foreach (var s in strings)
{
var t = s;
l.Add(() => t);
}
foreach (var a in l)
Console.WriteLine(a());
По сути, это эквивалентно примеру Джона с анонимными методами.
Помните, что это еще больше сбивает с толку с foreach, где переменную "s" легче смешивать с переменной с областью видимости. В обычных циклах for переменная индекса явно одна и та же для каждой итерации.
Это был исправлено в C# 5.
По сути, вы просто печатаете одну и ту же переменную снова и снова, не меняя ее.
@Johnbot Не исправлено, изменено.
Мне часто приходится напоминать себе, что DateTime - это тип значения, а не тип ссылки. Мне это кажется слишком странным, особенно учитывая разнообразие конструкторов для этого.
Я постоянно набираю дату и время в нижнем регистре ... к счастью, intellisense исправил это для меня :-)
Почему это должно иметь значение? DateTime в любом случае неизменен, и я не вижу ситуации, когда вам действительно нужно знать, является ли это ссылочным типом или нет.
Да, напоминание необходимо. Наиболее частое раздражение, которое у меня было в связи с этим, это то, что у устаревшего приложения, которое я поддерживаю, есть DateTimes справа налево и по центру, и они могут или не могут быть сброшены, неинициализированы, действительны и т. д., И, конечно, я хочу посмотреть, есть ли они " null ", которые, конечно, в БД часто бывают являются, но не могут быть в коде. Таким образом, они должны быть DateTime.MinValue или какое-то такое магическое число или другой Kludge, и я должен убедиться, что они есть, если они не были абсолютно и наверняка установлены на что-то, по крайней мере, правдоподобное.
На Проблемы с .NET есть целая книга
Мне больше всего нравится тот, где вы создаете класс на C#, наследуете его VB, а затем пытаетесь повторно наследовать обратно на C#, и это не работает. ARGGH
Думаю, это чисто странность Visual Studio.
Довольно забавно ... Антон, наверное, прав.
Я не считаю это ошибкой, но полезной функцией!
Я думаю об этом как о переводе с английского на французский, а затем обратно на английский. Часто у вас не получается именно то, с чего вы начали.
struct Point { ... }
List<Point> mypoints = ...;
mypoints[i].x = 10;
не имеет никакого эффекта.
mypoints[i] возвращает копию объекта значения Point. C# с радостью позволяет вам изменять поле копии. Молча ничего не делать.
Обновлять: Похоже, это исправлено в C# 3.0:
Cannot modify the return value of 'System.Collections.Generic.List<Foo>.this[int]' because it is not a variable
Я понимаю, почему это сбивает с толку, учитывая, что он действительно работает с массивами (вопреки вашему ответу), но не с другими динамическими коллекциями, такими как List <Point>.
Ты прав. Спасибо. Исправил свой ответ :). arr[i].attr= - это специальный синтаксис для массивов, которые нельзя закодировать в контейнерах библиотеки; (. Почему (<выражение значения>). Attr = <expr> вообще разрешено? Может ли это иметь смысл?
@Bjarke Ebert: В некоторых случаях это имело бы смысл, но, к сожалению, компилятор не может их идентифицировать и разрешить. Пример сценария использования: неизменяемая структура, которая содержит ссылку на квадратный двумерный массив вместе с индикатором поворота / отражения. Сама структура будет неизменной, поэтому запись в элемент экземпляра, доступного только для чтения, должна быть хорошей, но компилятор не будет знать, что установщик свойства на самом деле не собирается записывать структуру, и, следовательно, не позволит ему .
Если считать ASP.NET, я бы сказал, что жизненный цикл веб-форм - это для меня довольно большая проблема. Я потратил бесчисленное количество часов на отладку плохо написанного кода веб-форм только потому, что многие разработчики просто не понимают, когда использовать какой обработчик событий (к сожалению, я в том числе).
Вот почему я перешел на MVC ... головные боли viewstate ...
Был целый другой вопрос, посвященный конкретно подводным камням ASP.NET (вполне заслуженно). Базовая концепция ASP.NET (создание веб-приложений для разработчика похожими на приложения для Windows) настолько ошибочна, что я не уверен, что это даже можно считать ошибкой.
MusiGenesis Я хотел бы проголосовать за ваш комментарий сто раз.
@MusiGenesis Сейчас это кажется заблуждением, но в то время люди хотели, чтобы их веб-приложения (приложения были ключевым словом - ASP.NET WebForms на самом деле не были предназначены для размещения блогов), чтобы они вели себя так же, как их приложения Windows. Это изменилось только относительно недавно, и многие люди все еще «не совсем там». Вся проблема заключалась в том, что абстракция была слишком дырявой - сеть не вела себя как настольное приложение так много, это приводило к путанице почти у всех.
Как ни странно, первое, что я когда-либо видел об ASP.NET, было видео от Microsoft, демонстрирующее, как легко можно создать сайт блога с помощью ASP.NET!
Проблемой, которая привлекает множество новых разработчиков, является семантика повторной генерации исключений.
Часто я вижу такой код
catch(Exception e)
{
// Do stuff
throw e;
}
Проблема в том, что он стирает трассировку стека и значительно усложняет диагностику проблем, потому что вы не можете отследить, где возникло исключение.
Правильный код - это либо оператор throw без аргументов:
catch(Exception)
{
throw;
}
Или оберните исключение в другое и используйте внутреннее исключение для получения исходной трассировки стека:
catch(Exception e)
{
// Do stuff
throw new MySpecialException(e);
}
К счастью, меня кто-то научил этому в первую неделю, и я нашел это в коде более опытных разработчиков. Это: catch () {throw; } То же, что и второй фрагмент кода? catch (исключение e) {бросить; } только он не создает объект Exception и не заполняет его?
Помимо ошибки использования throw ex (или throw e) вместо просто throw, я должен задаться вопросом, какие бывают случаи, когда стоит поймать исключение только для того, чтобы выбросить его снова.
@Kyralessa: есть много случаев: например, если вы хотите откатить транзакцию до того, как вызывающий получит исключение. Вы откатываетесь, а потом снова бросаете.
Я постоянно вижу это, когда люди перехватывают и повторно генерируют исключения только потому, что их учат, что они должны перехватывать все исключения, не понимая, что они будут отлавливаться дальше по стеку вызовов. Это сводит меня с ума.
@Kyralessa - самый большой случай, когда вам нужно вести журнал. Зарегистрируйте ошибку в перехвате и повторно выбросьте ..
Исключения повторного создания не являются эксклюзивными для .Net, они применимы и к Java. Совершенно верно, что переменную правильно опускать и просто throw.
Следующее не будет перехватывать исключение в .Net. Вместо этого возникает исключение StackOverflow.
private void button1_Click( object sender, EventArgs e ) {
try {
CallMe(234);
} catch (Exception ex) {
label1.Text = ex.Message.ToString();
}
}
private void CallMe( Int32 x ) {
CallMe(x);
}
Для комментаторов (и голосов против):
Было бы крайне редко, чтобы переполнение стека было настолько очевидным. Однако, если это произойдет, вы не поймаете исключение и, скорее всего, потратите несколько часов, пытаясь выследить, где именно находится проблема. Это может быть осложнено, если SO возникает в малоиспользуемых логических путях, особенно в веб-приложении, где вы можете не знать точных условий, которые привели к возникновению проблемы.
Это точно такая же ситуация, как и в принятом ответе на этот вопрос (https://stackoverflow.com/a/241194/2424). Получатель свойства в этом ответе по сути делает то же самое, что и приведенный выше код, и дает сбой без трассировки стека.
После переполнения стека выполнение другого кода, включая обработчик исключений, невозможно. Я не уверен, что это действительно ошибка, но я думаю, это могло бы сбить с толку, если бы вы не привыкли к этой идее.
Это просто плохой код, а не ошибка.
MemoryStream.GetBuffer() против MemoryStream.ToArray(). Первый возвращает весь буфер, второй - только использованную часть. Фу.
Да, ручьи могут вас укусить, если вы не подозреваете. У меня была очень плохая привычка не забывать закрывать их.
@MusiGenesis, используйте using (Stream stream = new ...) {}.
Мое худшее, что я выяснил сегодня ... Если вы переопределите object.Equals (object obj), вы можете обнаружить, что:
((MyObject)obj).Equals(this);
не ведет себя так же, как:
((MyObject)obj) == this;
Один вызовет вашу функцию переопределения, другой - НЕ.
Раньше я работал с некоторыми бывшими Java-парнями, которые любили отвергать все, что попадалось им в руки, и они постоянно портили друг друга этим. Я всегда использовал ==, поэтому на меня ничего не влияло.
Я думал, вы тоже можете перегрузить оператор == ...
Вы можете переопределить оператор ==, но переопределение .Equals () не сделает этого за вас. Таким образом, вы можете гипотетически переопределить оба .Equals () и ==, и заставить их делать разные вещи: \
Вы можете отвергать Equals и перегрузка ==. Разница небольшая, но очень важная. Больше информации здесь. stackoverflow.com/questions/1766492/…
Кажется, некоторое время назад я помню, что == - это сравнение значений, а .Equals () - это средство сравнения объектов. А также, если вы переопределите Equals (), вам нужно переопределить GetHashCode () ... не уверен. (Я могу ошибаться в этом)
Еще одна проблема, связанная с равенством, заключается в том, что вы не можете переопределить == для интерфейсов. Даже если в ваших классах есть пользовательские Equals, == нельзя указать использовать его, и он сможет только сравнить ссылку.
Для программистов C / C++ переход на C# является естественным. Однако самая большая проблема, с которой я столкнулся лично (и видел, как другие делали такой же переход), заключается в неполном понимании разницы между классами и структурами в C#.
В C++ классы и структуры идентичны; они отличаются только видимостью по умолчанию, где классы по умолчанию имеют частную видимость, а структуры - публичную видимость. В C++ это определение класса
class A
{
public:
int i;
};
функционально эквивалентен этому определению структуры.
struct A
{
int i;
};
Однако в C# классы являются ссылочными типами, а структуры - типами значений. Это делает разницу БОЛЬШОЙ в (1) выборе того, когда использовать один по сравнению с другим, (2) проверке равенства объектов, (3) производительности (например, упаковка / распаковка) и т. д.
В сети есть всевозможная информация, связанная с различиями между ними (например, здесь). Я настоятельно рекомендую любому, кто переходит на C#, по крайней мере, иметь практическое представление о различиях и их последствиях.
Итак, худшая ошибка - это люди, которые не удосуживаются найти время, чтобы выучить язык, прежде чем использовать его?
@ BlueRaja-DannyPflughoeft Больше похоже на классическую ошибку явно похожих языков - они используют очень похожие ключевые слова и во многих случаях синтаксис, но работают совершенно по-другому.
Dictionary <,>: «Порядок, в котором возвращаются элементы, не определен». Это ужасно, потому что иногда он может укусить вас, но работать с другими, и если вы просто слепо предположили, что Dictionary будет хорошо себя вести («почему бы и нет? Я подумал, что у List»), вам действительно нужно засуньте нос в это, прежде чем вы наконец начнете подвергать сомнению свое предположение.
(Аналогичный вопрос здесь.)
Ну, List <T> неплохо играет, правда? Верно? Потому что мне нужно переписать кое-что, если это не так.
Конечно, List <T> играет хорошо. И используйте SortedDictionary <T>, если порядок элементов важен для вас.
List <T> поддерживает порядок, в котором добавляются элементы, однако, когда вы вызываете GetEnumerator (т. Е. В цикле foreach), возвращаемый перечислитель НЕ гарантирует сохранение порядка. Вам нужно будет пройти по индексу, чтобы гарантировать порядок при обработке.
@ck, у тебя есть для этого ссылка? Думаю, многие будут удивлены ...
@ck: Неправильно. Список <T> .GetEnumerator делает сохраняет порядок.
Я думаю, что @ck предполагает, что в контракте нет гарантий, что перечисление будет происходить по порядку.
Хм. Это чисто хэш-таблица, а не ошибка .Net.
DateTime.ToString ("дд / ММ / гггг"); На самом деле нет всегда будет давать вам dd / MM / yyyy, но вместо этого он будет учитывать региональные настройки и заменять разделитель даты в зависимости от того, где вы находитесь. Так что вы можете получить дд-мм-гггг или что-то в этом роде.
Правильный способ сделать это - использовать DateTime.ToString ("дд '/' ММ '/' гггг");
DateTime.ToString ("r") должен преобразовывать в RFC1123, который использует GMT. GMT находится в пределах доли секунды от UTC, и все же спецификатор формата «r» не конвертируется в UTC, даже если рассматриваемый DateTime указан как Local.
Это приводит к следующей ошибке (зависит от того, насколько далеко ваше местное время от UTC):
DateTime.Parse("Tue, 06 Sep 2011 16:35:12 GMT").ToString("r")
> "Tue, 06 Sep 2011 17:35:12 GMT"
Ой!
Мм изменено на ММ - мм - это минуты, а ММ - месяцы. Еще одна ошибка, я думаю ...
Я мог видеть, как это было бы ошибкой, если бы вы этого не знали (я не знал) ... но я пытаюсь выяснить, когда вам нужно поведение, при котором вы специально пытаетесь напечатать дату, которая не соответствует вашим региональным настройкам.
@Beska: Поскольку вы пишете в файл, он должен быть в определенном формате с указанным форматом даты.
Я считаю, что локализация дефолтов хуже, чем наоборот. По крайней мере, разработчик полностью проигнорировал локализацию кода работает на машинах, локализованных иначе. Таким образом, код, вероятно, не работает.
На самом деле я считаю, что правильным способом сделать это будет DateTime.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture);.
Сегодня я исправил ошибку, которая долго ускользала. Ошибка была в универсальном классе, который использовался в многопоточном сценарии, а поле static int использовалось для обеспечения синхронизации без блокировки с помощью Interlocked. Ошибка была вызвана тем, что каждый экземпляр универсального класса для типа имеет свой собственный статический. Таким образом, у каждого потока было собственное статическое поле, и он не использовал блокировку, как предполагалось.
class SomeGeneric<T>
{
public static int i = 0;
}
class Test
{
public static void main(string[] args)
{
SomeGeneric<int>.i = 5;
SomeGeneric<string>.i = 10;
Console.WriteLine(SomeGeneric<int>.i);
Console.WriteLine(SomeGeneric<string>.i);
Console.WriteLine(SomeGeneric<int>.i);
}
}
Это печатает 5 10 5
у вас может быть неуниверсальный базовый класс, определяющий статику, и наследовать от него универсальные. Хотя я никогда не попадался на такое поведение в C# - я все еще помню долгие часы отладки некоторых шаблонов C++ ... Фу! :)
Странно, я думал, что это очевидно. Только подумайте, что бы он делал, если бы у i был тип T.
Параметр типа является частью Type. SomeGeneric<int> - это тип, отличный от SomeGeneric<string>; так что конечно у каждого свой public static int i
Худшим, что со мной случилось, была проблема с webBrowser documentText:
http://geekswithblogs.net/paulwhitblog/archive/2005/12/12/62961.aspx#107062
решения AllowNavigation работают в формах Windows ...
но в компактных рамках собственности не существует ...
... пока что единственным решением, которое я нашел, было перестроить элемент управления браузера:
Но при этом вам нужно обрабатывать историю браузера под рукой ...: P
Я немного опоздал на эту вечеринку, но у меня есть две проблемы, обе укусившие меня недавно:
Свойство Ticks измеряет время в 10-миллионных долях секунды (блоки по 100 наносекунд), однако разрешение составляет не 100 наносекунд, а около 15 мс.
Этот код:
long now = DateTime.Now.Ticks;
for (int i = 0; i < 10; i++)
{
System.Threading.Thread.Sleep(1);
Console.WriteLine(DateTime.Now.Ticks - now);
}
даст вам результат (например):
0
0
0
0
0
0
0
156254
156254
156254
Точно так же, если вы посмотрите на DateTime.Now.Millisecond, вы получите значения округленными фрагментами по 15,625 мс: 15, 31, 46 и т. д.
Это конкретное поведение варьируется от системы к системе, но Есть и другие подводные камни, связанные с разрешением в этом API даты / времени.
Отличный способ комбинировать пути к файлам, но он не всегда ведет себя так, как вы ожидаете.
Если второй параметр начинается с символа \, он не даст вам полного пути:
Этот код:
string prefix1 = "C:\\MyFolder\\MySubFolder";
string prefix2 = "C:\\MyFolder\\MySubFolder\\";
string suffix1 = "log\\";
string suffix2 = "\\log\\";
Console.WriteLine(Path.Combine(prefix1, suffix1));
Console.WriteLine(Path.Combine(prefix1, suffix2));
Console.WriteLine(Path.Combine(prefix2, suffix1));
Console.WriteLine(Path.Combine(prefix2, suffix2));
Дает вам этот вывод:
C:\MyFolder\MySubFolder\log\
\log\
C:\MyFolder\MySubFolder\log\
\log\
Квантование времен с интервалами ~ 15 мс не связано с отсутствием точности в основном механизме синхронизации (я не стал подробно останавливаться на этом ранее). Это потому, что ваше приложение работает в многозадачной ОС. Windows проверяет ваше приложение каждые 15 мсек или около того, и в течение полученного небольшого отрезка времени ваше приложение обрабатывает все сообщения, которые были поставлены в очередь с момента вашего последнего фрагмента. Все ваши вызовы в этом фрагменте возвращаются в одно и то же время, потому что все они фактически совершаются в одно и то же время.
@MusiGenesis: я знаю (теперь), как это работает, но мне кажется неправильным иметь такую точную меру, которая на самом деле не так точна. Это все равно, что сказать, что я знаю свой рост в нанометрах, хотя на самом деле я просто округляю его до ближайших десяти миллионов.
DateTime вполне может хранить до одного тика; это DateTime.Now, который не использует такую точность.
Дополнительный '\' - ошибка многих пользователей unix / mac / linux. В Windows, если в начале стоит '\', это означает, что мы хотим перейти в корень диска (например, C :) попробуйте это с помощью команды CD, чтобы понять, что я имею в виду .... 1) Перейти к C:\Windows\System32 2) Введите CD \Users 3 ) Вау! Теперь вы на C:\Users ... ПОЛУЧИЛО? ... Path.Combine (@ "C: \ Windows \ System32", @ "\ Users") должен возвращать \Users, что означает именно [current_drive_here]:\Users
Даже без «сна» это работает так же. Это не имеет ничего общего с расписанием приложения каждые 15 мс. Встроенная функция GetSystemTimeAsFileTime, вызываемая DateTime.UtcNow, имеет плохое разрешение.
@Jimbo Честно говоря, это функция, которая показывает текущее время. Я бы не удивился, если бы вместо этого он был с точностью до секунды.
[Serializable]
class Hello
{
readonly object accountsLock = new object();
}
//Do stuff to deserialize Hello with BinaryFormatter
//and now... accountsLock == null ;)
Мораль истории: инициализаторы полей не запускаются при десериализации объекта.
Да, я ненавижу сериализацию .NET за то, что не запускается конструктор по умолчанию. Хотелось бы, чтобы невозможно было построить объект без вызова конструкторов, но, увы, это не так.
ASP.NET:
Если вы используете Linq-To-SQL, вы вызываете SubmitChanges() в контексте данных, и он генерирует исключение (например, дублированный ключ или другое нарушение ограничения), значения объектов-нарушителей остаются в вашей памяти во время отладки и будут повторно отправляться каждые раз вы впоследствии позвоните SubmitChanges().
Теперь вот кикер настоящий: плохие значения останутся в памяти даже если вы нажмете кнопку «стоп» в своей среде IDE и перезапустите! Я не понимаю, почему кто-то подумал, что это хорошая идея, но этот маленький значок ASP.NET, который появляется в панели задач, продолжает работать, и, похоже, он спасает кеш вашего объекта. Если вы хотите очистить память, щелкните этот значок правой кнопкой мыши и принудительно выключите его! ПОПАЛСЯ!
Конечно, вы имеете в виду паузу, а не остановку.
Дэвид - нет, я имел в виду то, что сказал. Даже если вы нажмете «Стоп», маленький значок ASP.NET будет продолжать работать на панели задач. Вот почему это ошибка!
+1 из-за первой части нет самостоятельного знания кикера.
MS SQL Server не может обрабатывать даты до 1753 года. Примечательно, что это не синхронизируется с константой .NET DateTime.MinDate, которая равна 1/1/1. Так что если вы попытаетесь сохранить память, неверную дату (как недавно случилось со мной при импорте данных) или просто дату рождения Вильгельма Завоевателя, у вас будут проблемы. Для этого нет встроенного обходного пути; если вам, вероятно, понадобится работать с датами до 1753 года, вам нужно написать свой собственный обходной путь.
Честно говоря, я думаю, что MS SQL Server имеет это право, а .Net - нет. Если вы проведете исследование, то знаете, что даты до 1751 года выглядят странно из-за календарных изменений, полностью пропущенных дней и т. д. Большинство RDBM имеют некоторую точку отсечения. Это должно дать вам отправную точку: ancestry.com/learn/library/article.aspx?article=3358
Кроме того, дата - 1753 год. Это был практически первый раз, когда у нас появился непрерывный календарь без пропуска дат. В SQL 2008 представлены типы даты Date и datetime2, которые могут принимать даты с 01.01.01 по 31.12.9999. Однако сравнение дат с использованием этих типов следует рассматривать с подозрением, если вы действительно сравниваете даты до 1753 года.
Ах да, 1753 год, поправил, спасибо.
Есть ли смысл сравнивать даты с такими датами? Я имею в виду, что для History Channel это имеет большой смысл, но я не думаю, что хочу знать точный день недели, когда была открыта Америка.
В Википедии в Юлианский день вы можете найти 13-строчную базовую программу CALJD.BAS, опубликованную в 1984 году, которая может производить расчеты дат примерно до 5000 г. до н. «системы вроде SQL2008 должны работать хуже. Возможно, вас не интересует правильное представление даты в 15 веке, но другие могут, и наше программное обеспечение должно справиться с этим без ошибок. Другая проблема - это дополнительные секунды. . .
Из-за этих проблем системы часто используют en.wikipedia.org/wiki/Proleptic_Gregorian_calendar, что означает дату в прошлом, как если бы мы были в соответствии с теми же правилами календаря, что и сегодня, хотя это исторически неверно. Или используйте более подходящие фреймворки для определения даты, такие как NodaTime nodatime.org от @ jon-skeet Джона Скита.
LINQ to SQL и отношения "один ко многим"
Это прекрасный вариант, который пару раз укусил меня, и MS предоставила одному из своих разработчиков передать его в ее блог. Я не могу выразить это лучше, чем она, так что взгляните туда.
Linq-To-Sql и неоднозначность базы данных и локального кода
Иногда Linq просто не может определить, предназначен ли определенный метод для выполнения в БД или в локальном коде.
См. здесь и здесь для постановки проблемы и решения.
См. Также: stackoverflow.com/questions/2675536
Утечка памяти из-за того, что вы не отключили события.
Это зацепило даже некоторых знакомых мне старших разработчиков.
Представьте себе форму WPF с множеством вещей в ней, и где-то там вы подписываетесь на событие. Если вы не откажетесь от подписки, вся форма будет храниться в памяти после закрытия и отмены ссылки.
Я считаю, что проблема, которую я видел, заключалась в создании DispatchTimer в форме WPF и подписке на событие Tick, если вы не сделаете - = на таймере, ваша форма утекает память!
В этом примере ваш код разборки должен иметь
timer.Tick -= TimerTickEventHandler;
Это особенно сложно, поскольку вы создали экземпляр DispatchTimer внутри формы WPF, поэтому вы могли бы подумать, что это будет внутренняя ссылка, обрабатываемая процессом сборки мусора ... к сожалению, DispatchTimer использует статический внутренний список подписок и служб. запросы в потоке пользовательского интерфейса, поэтому ссылка «принадлежит» статическому классу.
Уловка состоит в том, чтобы всегда освобождать все созданные вами подписки на события. Если вы начнете полагаться на то, что Forms сделает это за вас, вы можете быть уверены, что приобретете привычку и однажды забудете выпустить событие где-нибудь, где это нужно сделать.
Существует предложение MS-connect для слабых эталонных событий здесь, которое решило бы эту проблему, хотя, на мой взгляд, мы должны просто полностью заменить невероятно плохую модель событий на слабосвязанную, подобную той, что используется CAB.
+1 от меня, спасибо! Что ж, нет, спасибо за работу по обзору кода, которую мне пришлось выполнять!
@ BlueRaja-DannyPflughoeft Со слабыми событиями у вас есть еще одна проблема - вы не можете подписаться на лямбды. Вы не можете записать timer.Tick += (s, e,) => { Console.WriteLine(s); }
@ Ark-kun: да, лямбды делают это еще сложнее, вам придется сохранить лямбду в переменной и использовать ее в своем коде удаления. Kinda разрушает простоту написания лямбд, не так ли?
Это проблема только в том случае, если издатель находится ниже в дереве ссылок (то есть ближе к корню приложения), как статический класс или родительский класс. Это эквивалентно тому, что издатель имеет ссылку на подписчика. Если вы удалите все другие ссылки на подписчика, у издателя все еще будет ссылка, что предотвратит ее сбор и облегчит проблему с памятью. Людям нравится немного переусердствовать с этим. Я имею в виду, что это ничего не повредит, но во многих случаях в этом нет необходимости и усложняется.
Попалась неприятная ошибка с кешированием Linq
См. мой вопрос, который привел к этому обнаружению, и блогер, который обнаружил проблему.
Короче говоря, DataContext хранит кеш всех объектов Linq-to-Sql, которые вы когда-либо загружали. Если кто-то еще внесет какие-либо изменения в ранее загруженную вами запись, вы не сможете получить последние данные, даже если вы явно перезагрузите запись!
Это из-за свойства ObjectTrackingEnabled в DataContext, которое по умолчанию имеет значение true. Если вы установите для этого свойства значение false, запись будет загружаться заново каждый раз ... НО ... вы не можете сохранить какие-либо изменения в этой записи с помощью SubmitChanges ().
ПОПАЛСЯ!
Ив просто потратил полтора дня (и кучу волос!), Гоняясь за этим ЖУКОМ ...
Это называется конфликтом параллелизма, и сегодня это все еще проблема, хотя сейчас есть определенные способы обойти это, хотя они, как правило, немного жесткие. DataContext был кошмаром. О_о
Возможно, это не совсем проблема, потому что поведение четко написано в MSDN, но однажды сломало мне шею, потому что я нашел это довольно нелогичным:
Image image = System.Drawing.Image.FromFile("nice.pic");
Этот парень оставляет файл "nice.pic" заблокированным до тех пор, пока образ не будет удален. В то время, когда я столкнулся с этим, я подумал, что было бы неплохо загружать значки на лету, и не осознавал (сначала), что у меня были десятки открытых и заблокированных файлов! Изображение отслеживает, откуда был загружен файл ...
Как это решить? Я думал, что одинарный лайнер сработает. Я ожидал дополнительного параметра для FromFile(), но его не было, поэтому я написал это ...
using (Stream fs = new FileStream("nice.pic", FileMode.Open, FileAccess.Read))
{
image = System.Drawing.Image.FromStream(fs);
}
Я согласен с тем, что такое поведение не имеет смысла. Я не могу найти этому никакого объяснения, кроме «такое поведение было задумано».
О, и что замечательно в этом обходном пути, так это то, что если вы попытаетесь вызвать Image.ToStream (я забыл точное имя), это не сработает.
нужно проверить код. Brb.
@EsbenSkovPedersen Такой простой, но забавный и сухой комментарий. Сделал мой день.
В Linq-To-Sql нет ярлыков операторов
См. здесь.
Короче говоря, внутри условного предложения запроса Linq-To-Sql вы не можете использовать условные ярлыки, такие как || и &&, чтобы избежать исключений нулевой ссылки; Linq-To-Sql оценивает обе стороны оператора OR или AND, даже если первое условие избавляет от необходимости оценивать второе условие!
TIL. BRB, повторная оптимизация нескольких сотен запросов LINQ ...
Перечисления можно оценивать более одного раза
Он укусит вас, если у вас есть перечислимое с ленивым перечислением, и вы дважды перебираете его и получаете разные результаты. (или вы получите те же результаты, но он выполняется дважды без необходимости)
Например, при написании определенного теста мне понадобилось несколько временных файлов для проверки логики:
var files = Enumerable.Range(0, 5)
.Select(i => Path.GetTempFileName());
foreach (var file in files)
File.WriteAllText(file, "HELLO WORLD!");
/* ... many lines of codes later ... */
foreach (var file in files)
File.Delete(file);
Каково же было мое удивление, когда File.Delete(file) выкидывает FileNotFound !!
Здесь происходит то, что перечислимый files получил итерацию дважды (результаты первой итерации просто запоминаются нет), и на каждой новой итерации вы будете повторно вызывать Path.GetTempFilename(), чтобы получить другой набор временных имен файлов.
Решение, конечно, состоит в том, чтобы перечислить значение с помощью ToArray() или ToList():
var files = Enumerable.Range(0, 5)
.Select(i => Path.GetTempFileName())
.ToArray();
Это еще страшнее, когда вы делаете что-то многопоточное, например:
foreach (var file in files)
content = content + File.ReadAllText(file);
и вы обнаруживаете, что content.Length все еще 0 после всех записей !! Затем вы начинаете тщательно проверять, что у вас нет состояния гонки, когда ... после одного потраченного впустую часа ... вы выясняете, что это всего лишь та крошечная мелочь Enumerable, которую вы забыли ...
Это сделано намеренно. Это называется отложенным исполнением. Среди прочего, он предназначен для моделирования конструкций TSQL. Каждый раз, когда вы выбираете из представления sql, вы получаете разные результаты. Он также позволяет создавать цепочки, что полезно для удаленных хранилищ данных, таких как SQL Server. В противном случае x.Select.Where.OrderBy отправит в базу данных 3 отдельные команды ...
@AYS вы пропустили слово "Попался" в заголовке вопроса?
Я думал, что ошибка означала оплошность дизайнеров, а не что-то намеренное.
Может быть, должен быть другой тип для не перезапускаемых IEnumerables. Типа AutoBufferedEnumerable? Это можно было легко реализовать. Эта ошибка кажется в основном из-за недостатка знаний программиста, я не думаю, что что-то не так с текущим поведением.
Связанный объект и внешний ключ не синхронизированы
Microsoft признала эту ошибку.
У меня есть класс Thing, у которого FK до Category. Категория не имеет определенного отношения к Вещи, чтобы не загрязнять интерфейс.
var thing = CreateThing(); // does stuff to create a thing
var category = GetCategoryByID(123); // loads the Category with ID 123
thing.Category = category;
Console.WriteLine("Category ID: {0}", thing.CategoryID);
Выход:
Category ID: 0
Так же:
var thing = CreateThing();
thing.CategoryID = 123;
Console.WriteLine("Category name: {0}", order.Category.Name);
выкидывает NullReferenceException. Связанный объект Category не загружает запись категории с идентификатором 123.
Однако после отправки изменений в БД эти значения синхронизируются. Но до того, как вы посетите БД, значение FK и связанный с ним объект работают практически независимо!
(Интересно, что неспособность синхронизировать значение FK со связанным объектом, кажется, происходит только тогда, когда не определены дочерние отношения, то есть Категория не имеет свойства «Вещи». Но «загрузка по запросу», когда вы просто устанавливаете значение FK НИКОГДА работает.)
ПОПАЛСЯ!
Окно часов Гейзенберга
Это может сильно вас укусить, если вы занимаетесь нагрузкой по требованию, например:
private MyClass _myObj;
public MyClass MyObj {
get {
if (_myObj == null)
_myObj = CreateMyObj(); // some other code to create my object
return _myObj;
}
}
Теперь предположим, что у вас есть код в другом месте, использующий это:
// blah
// blah
MyObj.DoStuff(); // Line 3
// blah
Теперь вы хотите отладить свой метод CreateMyObj(). Итак, вы устанавливаете точку останова в строке 3 выше, с намерением войти в код. На всякий случай вы также поместите точку останова в строку выше, которая говорит _myObj = CreateMyObj();, и даже точку останова внутри самого CreateMyObj().
Код достигает точки останова в строке 3. Вы входите в код. Вы ожидаете ввести условный код, потому что _myObj явно равен нулю, верно? Э ... так ... почему он пропустил условие и сразу перешел на return _myObj ?! Вы наводите указатель мыши на _myObj ... и действительно, у него есть значение! Как это случилось?!
Ответ заключается в том, что ваша IDE вызвала получение значения, потому что у вас открыто окно «слежения», особенно окно слежения «Авто», которое отображает значения всех переменных / свойств, относящихся к текущей или предыдущей строке выполнения. Когда вы достигли точки останова в строке 3, окно просмотра решило, что вам будет интересно узнать значение MyObj - поэтому за кулисами, игнорирование любой из ваших точек останова, он пошел и вычислил значение MyObj для вас - включая вызов CreateMyObj(), который устанавливает значение _myObj!
Вот почему я называю это окном наблюдения Гейзенберга - вы не можете наблюдать значение, не влияя на него ... :)
ПОПАЛСЯ!
Редактировать - я считаю, что комментарий @ ChristianHayter заслуживает включения в основной ответ, потому что он выглядит как эффективное решение этой проблемы. Поэтому в любое время, когда у вас есть свойство с отложенной загрузкой ...
Decorate your property with [DebuggerBrowsable(DebuggerBrowsableState.Never)] or [DebuggerDisplay("<loaded on demand>")]. – Christian Hayter
блестящая находка! ты не программист, ты настоящий отладчик.
На прошлой неделе это сводило меня с ума, но для статического конструктора, а не для ленивого загрузчика. Как бы я ни старался, мне не удавалось заставить его достичь моих точек останова, хотя код явно выполнялся. В итоге пришлось использовать низкотехнологичный подход Debug.WriteLine.
Я столкнулся с этим даже при наведении курсора на переменную, а не только на окно просмотра.
Украсьте свою собственность [DebuggerBrowsable(DebuggerBrowsableState.Never)] или [DebuggerDisplay("<loaded on demand>")].
Если вы разрабатываете класс фреймворка и хотите, чтобы функциональность окна просмотра без изменения поведения во время выполнения лениво сконструированного свойства, вы можете использовать прокси-сервер типа отладчика, чтобы вернуть значение, если оно уже было создано, и сообщение о том, что свойство не имеет был построен, если это так. Класс Lazy<T> (в частности, из-за его свойства Value) является одним из примеров того, где это используется.
Я вспоминаю человека, который (по какой-то непонятной причине) изменил значение объекта в перегрузке ToString. Каждый раз, когда он наводил на него курсор, всплывающая подсказка давала ему другое значение - он не мог этого понять ...
Я столкнулся с этой ситуацией и считаю ее естественным (хотя и неожиданным для некоторых) последствием наличия функции watch / locals / hover. Кстати, название просто веселое!
LinqToSQL и агрегат пустого набора
См. этот вопрос.
Если у вас есть запрос LinqToSql, по которому вы запускаете агрегат, - если ваш набор результатов пуст, Linq не может определить тип данных, даже если он был объявлен.
например Предположим, у вас есть таблица Claim с полем Amount, которое в LinqToSql имеет тип decimal.
var sum = Claims.Where(c => c.ID < 0).Sum(c => c.Amount);
Очевидно, что никакие претензии не имеют идентификатора меньше нуля, поэтому вы ожидаете увидеть sum = null, верно? Неправильный! Вы получаете InvalidOperationException, потому что SQL-запрос, лежащий в основе запроса Linq, не имеет типа данных. Вы должны явно указать Linq, что это десятичное число! Таким образом:
var sum = Claims.Where(c => c.ID < 0).Sum(c => (decimal?)c.Amount);
Это действительно глупо, и ИМО - ошибка дизайна со стороны Microsoft.
ПОПАЛСЯ!
Нет, это просто глупость. Как .Sum() должен возвращать null, если вы используете decimal в качестве статического типа?
@Timwi Я ожидал, что он вернет 0, если честно. Если я не ошибаюсь, он возвращает 0 для запросов LINQ в памяти. Кроме того, это имеет смысл логически
@Rob: Да, я согласен с Sum, но ни одной из других агрегатных функций (Average, Min и Max).
На самом деле проблема, с которой я столкнулся с этим ответом, заключается в том, что он неправильно диагностирует проблему и неверно формулирует решение. Проблема не в том, что «запрос SQL не имеет типа данных», и решение не в том, чтобы «сообщить Linq, что это десятичное число». Проблема в том, что запрос SQL возвращает null, и решение состоит в том, чтобы указать Linq использовать тип, допускающий значение NULL.
Для LINQ-to-SQL и LINQ-to-Entities
return result = from o in table
where o.column == null
select o;
//Returns all rows where column is null
int? myNullInt = null;
return result = from o in table
where o.column == myNullInt
select o;
//Never returns anything!
Есть отчет об ошибке для LINQ-to-Entites здесь, хотя они, похоже, не часто проверяют этот форум. Может быть, кому-то стоит подать еще и для LINQ-to-SQL?
Для тех, кто нашел эту страницу из-за того, что столкнулся с этой самой ошибкой, можно найти обходной путь здесь
TextInfo textInfo = Thread.CurrentThread.CurrentCulture.TextInfo;
textInfo.ToTitleCase("hello world!"); //Returns "Hello World!"
textInfo.ToTitleCase("hElLo WoRld!"); //Returns "Hello World!"
textInfo.ToTitleCase("Hello World!"); //Returns "Hello World!"
textInfo.ToTitleCase("HELLO WORLD!"); //Returns "HELLO WORLD!"
Да, такое поведение задокументировано, но это определенно неверно.
Я не согласен - когда слово написано заглавными буквами, оно может иметь особое значение, которое вы не хотите испортить с регистром заголовка, например «президент США» -> «Президент США», а не «Президент США».
@Shaul: В этом случае они должны указать это как параметр, чтобы избежать путаницы, потому что я никогда не встречал никого, кто ожидал бы такого поведения раньше времени - что делает это Попался!
Возможно, не самое плохое, но некоторые части фреймворка .net использовать степени, в то время как другие используют радианы(и документация, которая появляется с Intellisense, никогда не говорит вам, что, вы должны посетить MSDN, чтобы узнать)
Всего этого можно было бы избежать, имея вместо этого класс Angle ...
Я удивлен, что за него так много голосов, учитывая, что другие мои ошибки значительно хуже, чем это
VisibleChanged - это обычно не называется при изменении видимости.
Кажется, более фундаментальная проблема заключается в том, что "Visible" имеет разные значения в геттере и сеттере. Свойство «Visible» должно указывать, должен ли элемент управления «пытаться» показать себя, а отдельным свойством «CanBeSeen» должно быть «и», если собственное свойство «visible» элемента управления и свойство его родительского элемента «CanBeSeen».
Свойство DesignMode во всех UserControls выполняет на самом деле не говорю вам, если вы находитесь в режиме разработки.
Ключевое слово base не работает должным образом при оценке в среде отладки: вызов метода по-прежнему использует виртуальную диспетчеризацию.
Это потратило много времени, когда я наткнулся на это, и я подумал, что натолкнулся на какой-то разрыв в пространстве-времени CLR, но затем я понял, что это известная (и даже несколько преднамеренная) ошибка:
http://blogs.msdn.com/jmstall/archive/2006/06/29/funceval-does-virtual-dispatch.aspx
Пакеты LinqToSql становятся медленнее с квадратом размера пакета
Вот вопрос (и отвечать), где я исследовал эту проблему.
Короче говоря, если вы попытаетесь создать в памяти слишком много объектов перед вызовом DataContext.SubmitChanges(), вы начнете испытывать медлительность с геометрической скоростью. Я не подтвердил на 100%, что это так, но мне кажется, что вызов DataContext.GetChangeSet() заставляет контекст данных выполнять оценку эквивалентности (.Equals()) для каждой отдельной комбинации двух элементов в наборе изменений, вероятно, чтобы убедиться, что это не двойная вставка или другие проблемы с параллелизмом. Проблема в том, что если у вас очень большие партии, количество сравнений увеличивается пропорционально квадрату п, то есть (п ^ 2 + п) / 2. 1000 элементов в памяти означают более 500 000 сравнений ... а это может занять чертовски много времени.
Чтобы избежать этого, вы должны гарантировать, что для любых пакетов, в которых вы ожидаете большое количество элементов, вы делаете все в рамках транзакции, сохраняя каждый отдельный элемент по мере его создания, а не в одном большом сохранении в конце.
Использование параметров по умолчанию с виртуальными методами
abstract class Base
{
public virtual void foo(string s = "base") { Console.WriteLine("base " + s); }
}
class Derived : Base
{
public override void foo(string s = "derived") { Console.WriteLine("derived " + s); }
}
...
Base b = new Derived();
b.foo();
Output:
derived base
Черт, чувак, ты действительно хорошо разбираешься в C#, не так ли?
@Musi: Нет, это просто язык, с которым я работаю больше всего :)
Странно, я думал, что это совершенно очевидно. Если объявлен тип Base, откуда компилятор должен получить значение по умолчанию, если не Base? Я бы подумал, что это небольшая ошибка более, что значение по умолчанию может быть другим, если объявленным типом является тип полученный, даже если вызываемый метод (статически) является базовым методом.
Только для записи. В C++ методы могут иметь аргументы по умолчанию, и здесь у вас точно такая же ситуация. Статические аргументы по умолчанию. Хорошая практика - не изменять аргументы по умолчанию для унаследованных методов.
почему одна реализация метода может получить значение по умолчанию другой реализации?
@staafl Аргументы по умолчанию разрешаются во время компиляции, а не во время выполнения.
Я бы сказал, что эта ошибка является параметрами по умолчанию в целом - люди часто не понимают, что они решаются во время компиляции, а не во время выполнения.
@FredOverflow, мой вопрос был концептуальным. Хотя такое поведение имеет смысл с точки зрения реализации, оно не интуитивно понятно и может стать источником ошибок. IMHO компилятор C# не должен позволять изменять значения параметров по умолчанию при переопределении.
Можно ли получить доступ к Когда-либо подкласса по умолчанию? Имеет ли значение, является ли родительский класс абстрактным или нет?
Достаточно интересно, что если бы оба класса придерживались interface с набором параметров по умолчанию, это было бы, как и ожидалось, через интерфейс.
Если вы объявите базовое значение необязательного параметра как null, оно будет работать, как и следовало ожидать.
Массивы реализуют IList
Но не применяйте это. Когда вы вызываете Add, он сообщает вам, что это не работает. Так почему же класс реализует интерфейс, если он не может его поддерживать?
Компилируется, но не работает:
IList<int> myList = new int[] { 1, 2, 4 };
myList.Add(5);
У нас часто возникает эта проблема, потому что сериализатор (WCF) превращает все списки IList в массивы, и мы получаем ошибки времени выполнения.
ИМХО, проблема в том, что у Microsoft недостаточно интерфейсов, определенных для коллекций. IMHO, он должен иметь iEnumerable, iMultipassEnumerable (поддерживает Reset и гарантирует, что несколько проходов будут совпадать), iLiveEnumerable (будет иметь частично определенную семантику, если коллекция изменяется во время перечисления - изменения могут или не могут отображаться в перечислении, но не должны вызывать фиктивные результаты или исключения), iReadIndexable, iReadWriteIndexable и т. д. Поскольку интерфейсы могут «наследовать» другие интерфейсы, это не добавило бы много дополнительной работы, если таковая будет (это сохранит заглушки NotImplemented).
@supercat, это чертовски запутало бы новичков и некоторых опытных программистов. Я считаю, что коллекции .NET и их интерфейсы удивительно элегантны. Но я ценю ваше смирение. ;)
@Jordan: С момента написания вышеизложенного я решил, что лучшим подходом было бы, чтобы как IEnumerable<T>, так и IEnumerator<T> поддерживали свойство Features, а также некоторые «необязательные» методы, полезность которых будет определяться тем, что сообщает «Features». Тем не менее, я придерживаюсь своей основной точки зрения, а именно, что есть случаи, когда код, получающий IEnumerable<T>, потребует более сильных обещаний, чем предоставляет IEnumerable<T>. Обращение к ToList приведет к получению IEnumerable<T>, который выполняет такие обещания, но во многих случаях будет излишне дорогостоящим. Я бы сказал, что должно быть ...
... средство, с помощью которого код, принимающий IEnumerable<T>, может сделать копию содержимого если нужно, но может воздержаться от этого без необходимости.
Ваш вариант абсолютно не читается. Когда я вижу IList в коде, я знаю, с чем работаю, вместо того, чтобы проверять свойство Features. Программисты любят забывать, что важной особенностью кода является то, что его могут читать люди, а не только компьютеры. Пространство имен .NET collections не идеально, но это хорошо, и иногда поиск лучшего решения не является вопросом более идеального соответствия принципу. Один из худших кодов, с которыми я каждый работал, был кодом, который пытался идеально соответствовать DRY. Я выбросил и переписал. Это был просто плохой код. Я бы вообще не хотел использовать ваш фреймворк.
Вы также предполагаете, что они могут отбросить пространство имен, которое восходит к созданию .NET, и начать заново. Этого просто не произойдет. Я считаю, что такое причудливое использование является артефактом некоторых требований, которые в противном случае потребовали бы переписывания.
@Jordan: Предположим, что метод объекта получает ссылку на IEnumerable<int> и хочет сохранить ссылку на IEnumerable<int>, который навсегда будет содержать те же элементы, что и полученный сейчас. Если полученная ссылка идентифицирует неизменяемую последовательность, вызов ToList() для нее может быть ненужным и дорогостоящим (например, если один и тот же перечислимый список передается 100 объектам, вызов ToList без необходимости может увеличить требования к памяти в сотни раз). Однако в настоящее время нет средств, с помощью которых код мог бы определить, нужно ли ему делать копию списка.
@Jordan: Учитывая вещи такими, какие они есть, лучшим подходом было бы, IMHO, добавить средство, с помощью которого интерфейс мог бы указывать, что методы, которые отсутствуют в реализации, должны быть автоматически сгенерированы для цепочки статических методов в назначенном статическом классе и разрешить добавление методов к существующим интерфейсам, если определены подходящие статические методы. Затем при вызове Features в существующих экземплярах IEnumerable<T> будет получен ответ, указывающий, среди прочего, «Я не могу сказать, неизменяем я или нет», но классы, которые фактически неизменяемы, могут быть обновлены, чтобы сообщить об этом.
@supercat: если метод намерен сохранить копию предоставленных ему элементов, он должен запросить что-то более конкретное, чем IEnumerable(T), который не зря называется последовательностью. IEnumerable(T) - это только то, что можно перечислить. Вот и все. Заставлять его делать больше - просто плохой дизайн. Похоже, вы ищете оптимизацию не в той области. Чрезмерная оптимизация и неправильная оптимизация - такая же проблема, как и при оптимизации. У меня никогда не было проблем с перфомансом, вызванных System.Collections.
@Jordan: Какой тип вы бы посоветовали? Метод должен иметь возможность принимать любую реализацию IEnumerable<T>, которая является неизменяемой либо или достаточно малой, чтобы сделать ToList() разумным. Наличие либо добавленного метода интерфейса, либо метода расширения AsImmutable, который будет возвращать неизменяемую копию списка если он не сообщил о себе как неизменяемый или не предоставил средства для запроса неизменяемой копии, будет означать, что вызываемый метод может просто сохранить receivedSequence.AsImmutable(), не беспокоясь о том, что он получил. Какой лучший подход вы могли бы предложить?
Позвольте нам продолжить обсуждение в чате.
События
Я никогда не понимал, почему события являются особенностью языка. Их сложно использовать: вам нужно проверить наличие null перед вызовом, вам нужно отменить регистрацию (себя), вы не можете узнать, кто зарегистрирован (например: я зарегистрировался?). Почему событие - это не просто класс в библиотеке? В основном специализированный List<delegate>?
Кроме того, многопоточность болезненна. Все эти проблемы, кроме нулевого, исправлены в CAB (функции которого должны быть просто встроены в язык) - события объявляются глобально, и любой метод может объявить себя «подписчиком» любого события. Моя единственная проблема с CAB заключается в том, что имена глобальных событий представляют собой строки, а не перечисления (что можно исправить с помощью более интеллектуальных перечислений, таких как Java, которые по своей сути работают как строки!). CAB сложно настроить, но есть простой клон с открытым исходным кодом здесь.
Мне не нравится реализация событий .net. Подписка на события должна обрабатываться путем вызова метода, который добавляет подписку и возвращает IDisposable, который при Dispose'd удалит подписку. Нет необходимости в специальной конструкции, объединяющей методы «добавить» и «удалить», семантика которых может быть несколько хитрой, особенно если кто-то пытается добавить, а затем удалить многоадресный делегат (например, добавить «B», за которым следует «AB», затем удалить «B» (покидая «BA») и «AB» (все еще покидая «BA»).
@supercat Как бы вы переписали button.Click += (s, e) => { Console.WriteLine(s); }?
Если бы мне пришлось отказаться от подписки отдельно от других событий, IEventSubscription clickSubscription = button.SubscribeClick((s,e)=>{Console.WriteLine(s);}); и отказаться от подписки через clickSubscription.Dispose();. Если бы мой объект сохранял все подписки на протяжении всей своей жизни, MySubscriptions.Add(button.SubscribeClick((s,e)=>{Console.WriteLine(s);}));, а затем MySubscriptions.Dispose(), чтобы уничтожить все подписки.
@ Ark-kun: необходимость хранить объекты, которые инкапсулируют внешние подписки, может показаться неприятностью, но в отношении подписок как сущностей можно было бы объединить их с типом, который может гарантировать, что все они будут очищены, что в противном случае очень сложно.
@ Ark-kun: Кстати, еще одна вещь о подписках как сущностях заключается в том, что если издатель событий хранит связанный список сущностей, связанных с его подписками, для отмены подписки на объект просто требуется сделать его объект подписки недействительным, поскольку издатель события должен будет в любом случае опрашивать присоединенные объекты при возникновении события, он может безопасно отсоединить их в этот момент; если событие не срабатывает, издатель может периодически проверять недействительные подписки при добавлении новых.
Рекурсивное свойство
Я думаю, это не относится к C#, и я уверен, что видел, как это упоминалось в другом месте на SO (это - это вопрос, который напомнил мне об этом)
Это может произойти двумя способами, но конечный результат один и тот же:
Забывание ссылки на base. при переопределении свойства:
public override bool IsRecursive
{
get { return IsRecursive; }
set { IsRecursive = value; }
}
Переход с авто- на поддерживаемые- свойства, но не до конца:
public bool IsRecursive
{
get { return IsRecursive; }
set { IsRecursive = value; }
}
На самом деле это не проблема, просто небрежное кодирование.
Есть вопросы по этому поводу? :)
Параметры Oracle необходимо добавлять по порядку
Это основная проблема в реализации параметризованных запросов Oracle .Net в ODP.
Когда вы добавляете параметры в запрос, поведение по умолчанию таково, что имена параметров - игнорируется, а значения используются в том порядке, в котором они были добавлены.
Решение состоит в том, чтобы установить для свойства BindByName объекта OracleCommand значение true - по умолчанию это false ... что качественно (если не совсем количественно) чем-то вроде свойства DropDatabaseOnQueryExecution со значением по умолчанию true.
Они называют это особенностью; Я называю это ямой в свободном доступе.
Подробнее см. здесь.
То же самое для доступа, мой вопрос здесь stackoverflow.com/questions/7165661/…
Linq2SQL: отображение члена интерфейса [...] не поддерживается.
Если вы выполните запрос Linq2Sql для объекта, реализующего интерфейс, вы получите очень странное поведение. Допустим, у вас есть класс MyClass, который реализует интерфейс IHasDescription, таким образом:
public interface IHasDescription {
string Description { get; set; }
}
public partial class MyClass : IHasDescription { }
(Другая половина MyClass - это класс, созданный Linq2Sql, включая свойство Description.)
Теперь вы пишете код (обычно это происходит в универсальном методе):
public static T GetByDescription<T>(System.Data.Linq.Table<T> table, string desc)
where T : class, IHasDescription {
return table.Where(t => t.Description == desc).FirstOrDefault();
}
Компилируется нормально, но вы получаете ошибку во время выполнения:
NotSupportedException: The mapping of interface member IHasDescription.Description is not supported.
Что теперь с этим делать? Что ж, на самом деле это очевидно: просто замените свой == на .Equals(), таким образом:
return table.Where(t => t.Description.Equals(desc)).FirstOrDefault();
И теперь все работает нормально!
См. здесь.
Контракт на Stream.Read - это то, что я видел, как сбивает с толку множество людей:
// Read 8 bytes and turn them into a ulong
byte[] data = new byte[8];
stream.Read(data, 0, 8); // <-- WRONG!
ulong data = BitConverter.ToUInt64(data);
Причина, по которой это неверно, заключается в том, что Stream.Read будет читать в большинстве указанное количество байтов, но совершенно бесплатно будет читать только 1 байт, даже если до конца потока доступны еще 7 байтов.
Не помогает то, что это выглядит так похоже на Stream.Write, который является гарантированно записал все байты, если он вернется без исключения. Также не помогает, что приведенный выше код работает почти все время. И конечно не помогает то, что нет готового удобного метода для правильного чтения ровно N байт.
Итак, чтобы заткнуть дыру и повысить осведомленность об этом, вот пример правильного способа сделать это:
/// <summary>
/// Attempts to fill the buffer with the specified number of bytes from the
/// stream. If there are fewer bytes left in the stream than requested then
/// all available bytes will be read into the buffer.
/// </summary>
/// <param name = "stream">Stream to read from.</param>
/// <param name = "buffer">Buffer to write the bytes to.</param>
/// <param name = "offset">Offset at which to write the first byte read from
/// the stream.</param>
/// <param name = "length">Number of bytes to read from the stream.</param>
/// <returns>Number of bytes read from the stream into buffer. This may be
/// less than requested, but only if the stream ended before the
/// required number of bytes were read.</returns>
public static int FillBuffer(this Stream stream,
byte[] buffer, int offset, int length)
{
int totalRead = 0;
while (length > 0)
{
var read = stream.Read(buffer, offset, length);
if (read == 0)
return totalRead;
offset += read;
length -= read;
totalRead += read;
}
return totalRead;
}
/// <summary>
/// Attempts to read the specified number of bytes from the stream. If
/// there are fewer bytes left before the end of the stream, a shorter
/// (possibly empty) array is returned.
/// </summary>
/// <param name = "stream">Stream to read from.</param>
/// <param name = "length">Number of bytes to read from the stream.</param>
public static byte[] Read(this Stream stream, int length)
{
byte[] buf = new byte[length];
int read = stream.FillBuffer(buf, 0, length);
if (read < length)
Array.Resize(ref buf, read);
return buf;
}
Или в вашем явном примере: var r = new BinaryReader(stream); ulong data = r.ReadUInt64();. В BinaryReader тоже есть метод FillBuffer ...
Я всегда думал, что типы value всегда присутствуют на stack, а типы reference - на heap.
Ну это не так. Когда я недавно увидел этот вопрос на SO (и, возможно, ответил неправильно), я понял, что это не так.
Поскольку Джон Скит ответил (давая ссылку на Сообщение в блоге Эрика Липперта), это Миф.
Очень важные ссылки:
Стек - это деталь реализации, часть 1
Стек - это деталь реализации, часть 2
Но насколько это важно при кодировании? Памятью управляет фреймворк. В большинстве случаев вам не о чем беспокоиться.
Статические конструкторы выполняются под замком. В результате вызов кода потоковой передачи из статического конструктора может привести к тупиковой ситуации. Вот пример, демонстрирующий это:
using System.Threading;
class Blah
{
static void Main() { /* Won’t run because the static constructor deadlocks. */ }
static Blah()
{
Thread thread = new Thread(ThreadBody);
thread.Start();
thread.Join();
}
static void ThreadBody() { }
}
Проверьте это:
class Program
{
static void Main(string[] args)
{
var originalNumbers = new List<int> { 1, 2, 3, 4, 5, 6 };
var list = new List<int>(originalNumbers);
var collection = new Collection<int>(originalNumbers);
originalNumbers.RemoveAt(0);
DisplayItems(list, "List items: ");
DisplayItems(collection, "Collection items: ");
Console.ReadLine();
}
private static void DisplayItems(IEnumerable<int> items, string title)
{
Console.WriteLine(title);
foreach (var item in items)
Console.Write(item);
Console.WriteLine();
}
}
И вывод:
List items: 123456
Collection items: 23456
Конструктор коллекции, который принимает IList, создает оболочку вокруг исходного списка, а конструктор списка создает новый список и копирует все ссылки из исходного в новый список.
Подробнее см. Здесь: http://blog.roboblob.com/2012/09/19/dot-net-gotcha-nr1-list-versus-collection-constructor/
Боже мой ... это ужасно от BCL
Это супер-ошибка, на устранение которой я потратил 2 дня. Никаких исключений не было, просто вылетел веб-сервер с некоторым странные сообщения об ошибках. Не смог воспроизвести проблему в DEV. Более того, эксперименты с настройками сборки проекта как-то убрали его в PROD, а потом вернули. Наконец то я понял.
Сообщите мне, если вы видите проблему в следующем фрагменте кода:
private void DumpError(Exception exception, Stack<String> context)
{
if (context.Any())
{
Trace.WriteLine(context.Pop());
Trace.Indent();
this.DumpError(exception, context);
Trace.Unindent();
}
else
{
Trace.WriteLine(exception.Message);
}
}
Итак, если вы цените свой рассудок:
!!! Никогда не добавляйте никакой логики в методы трассировки !!!
Код должен был выглядеть так:
private void DumpError(Exception exception, Stack<String> context)
{
if (context.Any())
{
var popped = context.Pop();
Trace.WriteLine(popped);
Trace.Indent();
this.DumpError(exception, context);
Trace.Unindent();
}
else
{
Trace.WriteLine(exception.Message);
}
}
Это бы меня достало. +1. Я никогда не знал, что .NET выбросит код (например, assert в режиме отладки C++)
enum Seasons
{
Spring = 1, Summer = 2, Automn = 3, Winter = 4
}
public string HowYouFeelAbout(Seasons season)
{
switch (season)
{
case Seasons.Spring:
return "Nice.";
case Seasons.Summer:
return "Hot.";
case Seasons.Automn:
return "Cool.";
case Seasons.Winter:
return "Chilly.";
}
}
Ошибка?
не все пути кода возвращают значение ...
Ты шутишь, что ли? Бьюсь об заклад, все пути кода действительно возвращают значение, потому что здесь упоминается каждый член Seasons. Он должен был проверять все члены перечисления, и если член отсутствовал в случаях переключения, такая ошибка была бы значимой, но теперь я должен добавить случай Default, который является избыточным и никогда не достигается кодом.
РЕДАКТИРОВАТЬ :
после дополнительных исследований по этому поводу я пришел к Хороший письменный и полезный пост Эрика Липперта, но это все еще немного странно. Ты согласен?
Случай по умолчанию будет поражен этим кодом HowYouFeel(42);. Да, для 42 нет enum, но он все еще действителен. .NET не ограничивает типы перечисления определенными значениями, и можно передавать значение из базового типа перечисления (в данном случае целое число).
Иногда номера строк в трассировке стека не совпадают с номерами строк в исходном коде. Это могло произойти из-за встраивания простых (однострочных) функций для оптимизации. Это серьезный источник путаницы для людей, выполняющих отладку с использованием журналов.
Обновлено: Пример: иногда вы видите исключение нулевой ссылки в трассировке стека, где оно указывает на строку кода без абсолютно никаких шансов на исключение нулевой ссылки, например, простое целочисленное присвоение.
Только что нашел странный, из-за которого я на какое-то время застрял в отладке:
Вы можете увеличивать значение null для int, допускающего значение NULL, без создания исключения, и значение остается нулевым.
int? i = null;
i++; // I would have expected an exception but runs fine and stays as null
Это результат того, как C# использует операции для типов, допускающих значение NULL. Это немного похоже на то, что NaN потребляет все, что вы ему бросаете.
Не худший, но еще не затронутый. Заводские методы, переданные в качестве аргументов в методы System.Collections.Concurrent, могут вызываться несколько раз, даже если когда-либо использовалось только одно возвращаемое значение. Учитывая, насколько сильно .NET пытается защитить вас от ложного пробуждения в потоковых примитивах, это может стать неожиданностью.
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ValueFactoryBehavingBadlyExample
{
class Program
{
static ConcurrentDictionary<int, int> m_Dict = new ConcurrentDictionary<int, int>();
static ManualResetEventSlim m_MRES = new ManualResetEventSlim(false);
static void Main(string[] args)
{
for (int i = 0; i < 8; ++i)
{
Task.Factory.StartNew(ThreadGate, TaskCreationOptions.LongRunning);
}
Thread.Sleep(1000);
m_MRES.Set();
Thread.Sleep(1000);
Console.WriteLine("Dictionary Size: " + m_Dict.Count);
Console.Read();
}
static void ThreadGate()
{
m_MRES.Wait();
int value = m_Dict.GetOrAdd(0, ValueFactory);
}
static int ValueFactory(int key)
{
Thread.Sleep(1000);
Console.WriteLine("Value Factory Called");
return key;
}
}
}
(Возможный) Выход:
Value Factory Called
Value Factory Called
Value Factory Called
Value Factory Called
Dictionary Size: 0
Value Factory Called
Value Factory Called
Value Factory Called
Value Factory Called
Передача емкости в List<int> вместо использования инициализатора коллекции.
var thisOnePasses = new List<int> {2}; // collection initializer
var thisOneFails = new List<int> (2); // oops, use capacity by mistake #gotcha#
thisOnePasses.Count.Should().Be(1);
thisOnePasses.First().Should().Be(2);
thisOneFails.Count.Should().Be(1); // it's zero
thisOneFails.First().Should().Be(2); // Sequence contains no elements...
Ваши утверждения относительно thisOneFails неверны. Count будет 0, и, следовательно, .First() выйдет из строя. (Кстати, они также не демонстрируют поведение, упомянутое в начале вашего сообщения)
thisOneFails должен потерпеть неудачу
Приношу извинения. Я нашел схему примера неудобной: thisOneFails не работает в двух разных местах (.Be(1) в первом операторе и .First() во втором операторе).
Ты за то, что нашел время ответить :)
вернуть DateTime.Now.AddDays (1);