[Из песочницы] С#: 10 распространенных ловушек и ошибок
Предлагаю вашему вниманию перевод статьи о «ловушках» в С#. Данная статья будет полезна начинающим программистам пока еще не знакомым с тонкостями языка.
«Это все мелочи, мелочи. Но нет ничего важнее мелочей.»
1. Использование ошибочного типа коллекции
.Net имеет множество классов коллекций, и все они специализируются на специфических задачах. Выбор класса нужно делать внимательно. Ошибившись в выборе, вы получите неэффективный код, непредвиденные значения, а также сделаете непонятным смысл кода.
При перечислении объектов для вызова следует использовать оператор yield return, а не создавать возвращающую коллекцию. Преимущества этого шаблона:
3. Анализ двусмысленных дат
Обязательно нужно указывать формат, если речь идет об анализе неоднозначных дат. Например, в строке »03/04/05» непонятно, что является днем, что месяцем, а что годом, и это может привести к серьезным ошибкам для пользователя.
4. Повторная обработка исключения с его экземпляром
Если вы хотите поймать и повторно обработать исключение, обязательно используйте простой throw. Если вы вдруг используете throw ex, он не будет сохранять исключение стека вызова.
И не используйте:
5. Обращение к виртуальным компонентам в конструкторе
Виртуальный дескриптор позволяет членам класса быть переопределенными в производном классе. Конструкторы выполняются по ходу от базового класса к производным. То есть, если вы вызываете переопределенный метод из конструктора базового класса, может быть вызван код, не готовый к выполнению (это может зависеть от завершения инициализации в его собственном конструкторе).
Инициализация дочернего класса будет выглядеть следующим образом:
Т.е. дочерний метод вызывается перед дочерним конструктором.6. Исключения в статическом конструкторе
Если класс генерирует исключение в статическом конструкторе, он рендерит бесполезный класс. Даже нестатическая конструкция будет невозможна. Класс будет генерировать System.TypeInitializationException всякий раз, когда на него ссылаются (статически или нет).7. Необязательные параметры во внешней сборке
Необязательные параметры могут работать не так, как ожидалось, если на них ссылаться из внешней сборки. Скажем, у вас есть некоторый функционал, упакованный в DLL. И, скажем, вы хотите внести незначительные изменения в код (изменить необязательный параметр на другое значение).
Скажем, вы имеете универсальный метод, принимающий параметр типа Т и еще один метод с тем же именем, но принимающий параметр конкретного типа. Компилятор будет выбирать более подходящий метод для каждого типа параметра, и явно определённый тип более корректен, чем универсальный.
И следующий код…
Выдаст вам результат:
Т.е. компилятор выбирает более специфический строковый метод при втором вызове.9. Изменение хэш-кода после добавления объекта в словарь
Словари зависят от значений ключей, возвращенных методом Object.GetHashCode (). Это означает, что хэш-код ключа не может быть изменен после добавления в словарь.10. ValueType.Equals будет тормозить, если структура содержит компонент-ссылку
Убедитесь, что ваша структура не содержит компонент-ссылку, если вы хотите использовать ValueType.Equals для сравнения двух экземпляров. Медленная работа связана с тем, что ValueType.Equals будет использовать отражение, чтобы определить ссылочный компонент, а это может быть немного медленнее, чем ожидалось.
Приятного чтения.
«Это все мелочи, мелочи. Но нет ничего важнее мелочей.»
1. Использование ошибочного типа коллекции
.Net имеет множество классов коллекций, и все они специализируются на специфических задачах. Выбор класса нужно делать внимательно. Ошибившись в выборе, вы получите неэффективный код, непредвиденные значения, а также сделаете непонятным смысл кода.
→ Подробнее здесь
2. Неиспользование yield returnПри перечислении объектов для вызова следует использовать оператор yield return, а не создавать возвращающую коллекцию. Преимущества этого шаблона:
- не нужно хранить целую коллекцию в памяти (она может быть очень большой)
- yield return непосредственно возвращает управление вызвавшей функции после каждой итерации
- идет обработка только тех результатов, которые будут использоваться (зачем перебирать всю коллекцию, если вызывающая функция хочет получить первое значение)
3. Анализ двусмысленных дат
Обязательно нужно указывать формат, если речь идет об анализе неоднозначных дат. Например, в строке »03/04/05» непонятно, что является днем, что месяцем, а что годом, и это может привести к серьезным ошибкам для пользователя.
Здесь используйте DateTime.ParseExact / DateTimeOffset.ParseExact для предоставления спецификатора формата:
var date = DateTime.ParseExact("01/12/2000", "MM/dd/yyyy", null)
4. Повторная обработка исключения с его экземпляром
Если вы хотите поймать и повторно обработать исключение, обязательно используйте простой throw. Если вы вдруг используете throw ex, он не будет сохранять исключение стека вызова.
Используйте:
catch(SomeException ex)
{
logger.log(ex);
throw;
}
И не используйте:
catch(SomeException ex)
{
logger.log(ex);
throw ex;
}
5. Обращение к виртуальным компонентам в конструкторе
Виртуальный дескриптор позволяет членам класса быть переопределенными в производном классе. Конструкторы выполняются по ходу от базового класса к производным. То есть, если вы вызываете переопределенный метод из конструктора базового класса, может быть вызван код, не готовый к выполнению (это может зависеть от завершения инициализации в его собственном конструкторе).
Например:
public class Parent
{
public Parent()
{
Console.WriteLine("Parent Ctor");
Method();
}
public virtual void Method()
{
Console.WriteLine("Parent method");
}
}
public class Child : Parent
{
public Child()
{
Console.WriteLine("Child Ctor");
}
public override void Method()
{
Console.WriteLine("Child method");
}
}
Инициализация дочернего класса будет выглядеть следующим образом:
- Родительский конструктор
- Дочерний метод
- Дочерний конструктор
Т.е. дочерний метод вызывается перед дочерним конструктором.6. Исключения в статическом конструкторе
Если класс генерирует исключение в статическом конструкторе, он рендерит бесполезный класс. Даже нестатическая конструкция будет невозможна. Класс будет генерировать System.TypeInitializationException всякий раз, когда на него ссылаются (статически или нет).7. Необязательные параметры во внешней сборке
Необязательные параметры могут работать не так, как ожидалось, если на них ссылаться из внешней сборки. Скажем, у вас есть некоторый функционал, упакованный в DLL. И, скажем, вы хотите внести незначительные изменения в код (изменить необязательный параметр на другое значение).
Потребитель DLL должен будет произвести перекомпиляцию для того, чтобы ваши изменения вступили в силу.
→ Подробнее здесь
8. Универсальные методы с неуниверсальной перегрузкойСкажем, вы имеете универсальный метод, принимающий параметр типа Т и еще один метод с тем же именем, но принимающий параметр конкретного типа. Компилятор будет выбирать более подходящий метод для каждого типа параметра, и явно определённый тип более корректен, чем универсальный.
Например, есть следующий класс:
class GenericTest
{
public void Test(T parameter)
{
Console.WriteLine("Generic method!");
}
public void Test(string parameter)
{
Console.WriteLine("Non-generic method!");
}
}
И следующий код…
var instance = new GenericTest();
instance.Test(7);
instance.Test("foo");
Выдаст вам результат:
Generic method!
Non-generic method!
Т.е. компилятор выбирает более специфический строковый метод при втором вызове.9. Изменение хэш-кода после добавления объекта в словарь
Словари зависят от значений ключей, возвращенных методом Object.GetHashCode (). Это означает, что хэш-код ключа не может быть изменен после добавления в словарь.10. ValueType.Equals будет тормозить, если структура содержит компонент-ссылку
Убедитесь, что ваша структура не содержит компонент-ссылку, если вы хотите использовать ValueType.Equals для сравнения двух экземпляров. Медленная работа связана с тем, что ValueType.Equals будет использовать отражение, чтобы определить ссылочный компонент, а это может быть немного медленнее, чем ожидалось.
Автор оригинала: Robert Bengtsson