Какая худшая ошибка в С# или .NET?

Недавно я работал с объектом DateTime и писал что-то вроде этого:

DateTime dt = DateTime.Now;
dt.AddDays(1);
return dt; // still today date! WTF?

Документация intellisense для AddDays() говорит, что добавляет день к дате, а это не означает, что на самом деле она возвращает дату с добавленным к ней днем, поэтому вам нужно написать ее так:

DateTime dt = DateTime.Now;
dt = dt.AddDays(1);
return dt; // tomorrow date

Этот укусил меня несколько раз раньше, поэтому я подумал, что было бы полезно каталогизировать худшие С# -часы.

Ответ 1

private int myVar;
public int MyVar
{
    get { return MyVar; }
}

Blammo. Ваше приложение отключается без трассировки стека. Случается постоянно.

(обратите внимание на капитал MyVar вместо строчного MyVar в получателе.)

Ответ 2

Type.GetType

Тот, который я видел, кусает много людей Type.GetType(string). Они задаются вопросом, почему он работает для типов в собственной сборке, а некоторые типы вроде System.String, но не System.Windows.Forms.Form. Ответ заключается в том, что он просматривается только в текущей сборке и в mscorlib.


Анонимные методы

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

Отложенное выполнение блоков итератора

Этот "бедный человек unit test" не проходит - почему бы и нет?

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 не запускается до тех пор, пока не будет вызван метод iterator MoveNext().

У меня есть некоторые другие странности на моей странице .

Ответ 3

Исключения повторного броска

В результате получается множество новых разработчиков, это семантика исключений re-throw.

Сколько времени я вижу, например, код

catch(Exception e) 
{
   // Do stuff 
   throw e; 
}

Проблема заключается в том, что он стирает трассировку стека и значительно затрудняет диагностику, потому что вы не можете отслеживать, откуда возникло исключение.

Правильный код - это либо оператор throw без аргументов:

catch(Exception)
{
    throw;
}

Или обертывание исключения в другом и использование внутреннего исключения для получения исходной трассировки стека:

catch(Exception e) 
{
   // Do stuff 
   throw new MySpecialException(e); 
}

Ответ 4

Окно просмотра Гейзенберга

Это может сильно укусить вас, если вы делаете нагрузку по требованию, например:

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, очевидно, null, не так ли? Ну... так... почему он пропустил условие и пошел прямо к return _myObj?! Вы наводите указатель мыши на _myObj... и действительно, это имеет значение! Как это произошло?!

Ответ заключается в том, что ваша IDE заставила его получить значение, потому что у вас открыто окно "смотреть", особенно окно "Авто", в котором отображаются значения всех переменных/свойств, относящихся к текущей или предыдущей строке исполнения. Когда вы нажмете свою точку останова на Строке 3, окно просмотра решит, что вам будет интересно узнать значение MyObj - так что за кулисами игнорируя любую из ваших контрольных точек, он пошел и вычислил значение MyObj для вас - , включая вызов CreateMyObj(), который устанавливает значение _myObj!

Вот почему я называю это Окно просмотра Гейзенберга - вы не можете наблюдать значение, не затрагивая его...:)

GOTCHA!


Изменить. Я чувствую, что комментарий @ChristianHayter заслуживает включения в главный ответ, потому что он выглядит как эффективный обходной путь для этой проблемы. Поэтому в любое время вы обладаете ленивым свойством...

Украсьте свое свойство с помощью [DebuggerBrowsable (DebuggerBrowsableState.Never)] или [DebuggerDisplay ("")]. - Христиан Хейтер

Ответ 5

Вот еще один раз, когда я получаю:

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

Ответ 6

Утечка памяти, потому что вы не отключили события.

Это даже показало, что некоторые старшие разработчики я знаю.

Представьте форму WPF с большим количеством вещей в ней, и где-нибудь там вы подписаны на событие. Если вы не отмените подписку, вся форма хранится в памяти после закрытия и отмены ссылки.

Я считаю, что проблема, которую я видел, это создание DispatchTimer в форме WPF и подписка на событие Tick, если вы не делаете a = = на таймере ваша форма утечки памяти!

В этом примере ваш код разрыва должен иметь

timer.Tick -= TimerTickEventHandler;

Это особенно сложно, поскольку вы создали экземпляр DispatchTimer внутри формы WPF, поэтому вы можете подумать, что это будет внутренняя ссылка, обрабатываемая процессом сбора мусора... К сожалению DispatchTimer использует статический внутренний список подписки и запросы служб в потоке пользовательского интерфейса, поэтому ссылка "принадлежит" статическому классу.

Ответ 7

Возможно, на самом деле это не так, потому что поведение написано четко в 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);
}

Ответ 8

Если вы подсчитаете ASP.NET, я бы сказал, что жизненный цикл webforms для меня довольно большой. Я потратил бесчисленное количество часов на отладку плохо написанного кода веб-форм только потому, что многие разработчики просто не понимают, когда использовать обработчик событий (включая меня, к сожалению).

Ответ 9

overloaded == операторы и нетипизированные контейнеры (arraylists, наборы данных и т.д.):

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>, чтобы гарантировать, что оба операнда являются строками.

Ответ 10

DateTime.ToString( "дд/мм/гггг" ); На самом деле не всегда будет давать вам dd/MM/yyyy, но вместо этого он учитывает региональные настройки и заменяет ваш разделитель дат в зависимости от того, где вы находитесь. Таким образом, вы можете получить dd-MM-yyyy или что-то подобное.

Правильный способ сделать это - использовать DateTime.ToString( "dd '/' MM '/' yyyy" );


DateTime.ToString( "r" ) предполагается преобразовать в RFC1123, который использует GMT. GMT находится в пределах долей секунды от UTC, но спецификатор формата "r" не конвертируется в UTC, даже если рассматриваемый DateTime указан как локальный.

В результате получается следующая информация (зависит от того, насколько далеко ваше местное время от UTC):

DateTime.Parse("Tue, 06 Sep 2011 16:35:12 GMT").ToString("r")
>              "Tue, 06 Sep 2011 17:35:12 GMT"

Упс!

Ответ 11

[Serializable]
class Hello
{
    readonly object accountsLock = new object();
}

//Do stuff to deserialize Hello with BinaryFormatter
//and now... accountsLock == null ;)

Мораль истории: инициализаторы полей не запускаются при десериализации объекта

Ответ 12

Я видел, что это было опубликовано на днях, и я думаю, что это довольно неясное и болезненное для тех, кто не знает

int x = 0;
x = x++;
return x;

Так как это вернет 0 и не 1, как ожидалось бы большинство

Ответ 13

Я немного опаздываю на эту вечеринку, но у меня есть две gotchas, которые недавно укусили меня:

Разрешение DateTime

Свойство 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.625ms: 15, 31, 46 и т.д.

Это конкретное поведение варьируется от системы к системе, но есть другие исправления, связанные с разрешением. API даты/времени.


Path.Combine

Отличный способ объединить пути файлов, но он не всегда ведет себя так, как вы ожидали.

Если второй параметр начинается с символа \, он не даст вам полный путь:

Этот код:

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\

Ответ 14

Когда вы запускаете процесс (используя System.Diagnostics), который записывает на консоль, но вы никогда не читаете поток Console.Out, после определенного объема вывода ваше приложение будет зависать.

Ответ 15

Нет ярлыков операторов в Linq-To-Sql

Смотрите здесь.

Короче говоря, внутри условного предложения запроса Linq-To-Sql вы не можете использовать условные ярлыки, такие как || и &&, чтобы исключить исключения для нулевой ссылки; Linq-To-Sql оценивает обе стороны оператора OR или AND, даже если первое условие устраняет необходимость оценки второго условия!

Ответ 16

Использование параметров по умолчанию с виртуальными методами

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

Вывод:
производная база

Ответ 17

Объекты Value в изменяемых коллекциях

struct Point { ... }
List<Point> mypoints = ...;

mypoints[i].x = 10;

не влияет.

mypoints[i] возвращает копию объекта значения Point. С# счастливо позволяет вам изменить поле копии. Безмолвно ничего не делает.


Update: Это, по-видимому, исправлено в С# 3.0:

Cannot modify the return value of 'System.Collections.Generic.List<Foo>.this[int]' because it is not a variable

Ответ 18

Возможно, не самое худшее, но некоторые части .net framework используют градусы, в то время как другие используют radians (и документация, которая появляется в Intellisense, никогда не сообщает вам, что вам нужно посетить MSDN, чтобы узнать)

Все это можно было бы избежать, если вместо этого был класс Angle...

Ответ 19

Для программистов на C/С++ переход на С# является естественным. Однако самая большая проблема, с которой я столкнулся лично (и видел с другими, делающими тот же переход), не полностью понимает разницу между классами и структурами на С#.

В С++ классы и структуры идентичны; они отличаются только видимостью по умолчанию, при которой классы по умолчанию закрывают видимость и структуры по умолчанию для публичной видимости. В С++ это определение класса

    class A
    {
    public:
        int i;
    };

функционально эквивалентен этому описанию структуры.

    struct A
    {
        int i;
    };

В С#, однако, классы являются ссылочными типами, а structs - типами значений. Это делает разницу БОЛЬШОЙ в (1) решающей, когда использовать один над другим, (2) тестировать равенство объекта, (3) производительность (например, бокс/распаковка) и т.д.

В Интернете есть все виды информации, связанные с различиями между ними (например, здесь). Я бы настоятельно рекомендовал любому, кто перешел на С#, по крайней мере, иметь рабочее знание о различиях и их последствиях.

Ответ 20

Сбор мусора и утилизация(). Хотя вам не нужно ничего делать, чтобы освободить память, вам все равно придется освобождать ресурсы через Dispose(). Это очень легко забыть, когда вы используете WinForms или отслеживаете объекты каким-либо образом.

Ответ 21

диапазон переменных foreach loops!

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

печатает пять "amet", в то время как следующий пример отлично работает

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

Ответ 22

MS SQL Server не может обрабатывать даты до 1753. Значительно, что это не синхронизируется с константой .NET DateTime.MinDate, которая равна 1/1/1. Поэтому, если вы попытаетесь сохранить отзыв, неверную дату (как это недавно случилось со мной при импорте данных) или просто дату рождения Уильяма Завоевателя, у вас будут проблемы. Для этого нет встроенного обходного пути; если вам нужно будет работать с датами до 1753 года, вам нужно написать свое обходное решение.

Ответ 23

The Nasty Linq Caching Gotcha

См. мой вопрос, который привел к этому открытию, и блоггер, который обнаружил проблему.

Короче говоря, DataContext хранит кеш всех объектов Linq-to-Sql, которые вы когда-либо загружали. Если кто-либо еще внесет какие-либо изменения в ранее загруженную запись, вы не сможете получить последние данные, , даже если вы явно перезагрузите запись!

Это из-за свойства, называемого ObjectTrackingEnabled в DataContext, которое по умолчанию является истинным. Если вы установите для этого свойства значение false, запись будет загружаться заново каждый раз... НО... вы не можете сохранять какие-либо изменения в этой записи с помощью SubmitChanges().

GOTCHA!

Ответ 24

Массивы реализуют IList

Но не реализуйте его. Когда вы вызываете "Добавить", он сообщает вам, что он не работает. Итак, почему класс реализует интерфейс, если он не может его поддерживать?

Скомпилирует, но не работает:

IList<int> myList = new int[] { 1, 2, 4 };
myList.Add(5);

У нас эта проблема много, потому что сериализатор (WCF) превращает все ILists в массивы, и мы получаем ошибки времени выполнения.

Ответ 25

Контракт на 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;
    }

Ответ 26

События

Я никогда не понимал, почему события являются языковой особенностью. Они сложны в использовании: вам нужно проверить нуль перед вызовом, вам нужно отменить регистрацию (самостоятельно), вы не можете узнать, кто зарегистрирован (например: зарегистрирован ли я?). Почему это событие не является классом в библиотеке? В основном специализированный List<delegate>?

Ответ 27

Сегодня я исправил ошибку, которая ускользала в течение длительного времени. Ошибка была в родовом классе, который использовался в многопоточном сценарии, и статическое поле int использовалось для обеспечения синхронизации без блокировки с использованием блокировки. Ошибка была вызвана тем, что каждый экземпляр родового класса для типа имеет свою собственную статичность. Таким образом, каждый поток получил свое собственное статическое поле и не использовал блокировку, как предполагалось.

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

Ответ 28

Перечисления можно оценивать более одного раза

Это вас укусит, когда вы будете перечислить лениво перечисляемые, и вы повторите его дважды и получите разные результаты. (или вы получаете те же результаты, но это выполняется дважды без необходимости)

Например, при написании определенного теста мне понадобилось несколько файлов temp для проверки логики:

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(), чтобы вы получили другую набор temp файлов.

Конечно, решение состоит в том, чтобы запрограммировать значение с помощью 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 после всех записей!! Затем вы начинаете тщательно проверять, что у вас нет состояния гонки, когда... после одного потраченного впустую часа... вы выяснили, что это крошечная маленькая перечислимая информация, которую вы забыли...

Ответ 29

Просто нашел странный, который заставлял меня откладывать на некоторое время:

Вы можете увеличивать значение null для NULL int без выделения исключения, а значение остается равным нулю.

int? i = null;
i++; // I would have expected an exception but runs fine and stays as null

Ответ 30

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

Да, это поведение задокументировано, но это, конечно, не делает это правильно.