C# .NET: Пять маленьких чудес, которые сделают ваш код лучше. Часть 1

Здравствуй, Хабрасообщество. Хотелось бы представить на суд твой свои переводы серии статей Джеймса Майкла Харе (James Michael Hare) «Маленькие чудеса C#». Итак, первая часть перед вами!
Эти маленькие советы и рекомендации сделают ваш код более кратким, производительным и обслуживаемым. Наверное, многие из вас знают обо всех или некоторых из них, но игнорируют их, либо просто не знают.

1.Оператор ??


Этот маленький оператор может быть чрезвычайно полезным в некоторых случаях. Как часто случается такое, что у вас есть переменная, значение которой вы бы хотели использовать, но если это значение-null, то вы хотите заменить его значением по умолчанию? Допустим, вы хотите присвоить строку переменной, но если строка равна null, вы хотите подставить пустую строку вместо нее:
Можно, конечно, написать условное выражение, используя if:
string name = value; if (value == null) { name = string.Empty; }

Или еще лучше, если использовать тернарный оператор «?»:
string name = (value != null) ? value : string.Empty;

О, уже более кратко, но мы можем улучшить и этот код! В C# имеется оператор ??, представляющий собой укороченную версию оператора? с проверкой значения на null:
string name = value ?? string.Empty;

Очень лаконично и красиво! Вы можете написать вспомогательный метод, чтобы урезать строку и возвращать null, если строка состоит только из пробелов, так что он сможет использовать ??..
public static class StringUtility 
{ 
public static string TrimToNull(string source) 
{ 
return string.IsNullOrWhiteSpace(source) ? null : source.Trim(); 
} 
}

Также этот оператор может быть использован, чтобы преобразовать пустые строки в значение по умолчанию:
string name = StringUtility.TrimToNull(value) ?? "Не определено";

2.Приведение типов с помощью as


Сколько раз вы видели подобный код:
if (employee is SalariedEmployee) 
{ 
var salEmp = (SalariedEmployee)employee; 
pay = salEmp.WeeklySalary; 
// ... 
}

Он является излишним, так как вы выполняете проверку типа дважды. Первый раз-проверка в условном операторе, и второй-приведение типов. Каждый раз, когда вы обнаруживаете, что идете по этому пути, предпочтите ему приведение типов с помощью as. Это удобный метод приведения, поскольку оно возвращает ссылку на тип, если типы совместимы, и null, если нет:
var salEmployee = employee as SalariedEmployee; 
if (salEmployee != null) 
{ 
pay = salEmployee.WeeklySalary;
 // ... 
}

Код читается лучше, а вы избегаете двойной проверки типов.

3.Автоматические свойства


Все мы знаем о свойствах в C#, и большинство знает об автоматических свойствах. Они превращают следующий код, содержащий два обычных свойства и два приватных поля:
public class Point 
{ 
int _x, _y; 
public int X 
{ 
{ return _x; } 
set { _x = value; } 
} 
public int Y 
{ 
get { return _y; } 
set { _y = value; } 
} 
}

в гораздо более краткий и понятный:
public class Point 
{ 
public int X { get; set; } 
public int Y { get; set; } 
}

Намного короче! Всякий раз, когда у вас имеется свойство, просто устанавливающее или возвращающее значение приватного поля, вам следует предпочти автоматические свойства обыкновенным, хотя бы для того, чтобы не писать много кода. Все что вам нужно, это написать свойство с аксессорами, не содержащими кода, и компилятор автоматически сгенерирует поля для вас «за кулисами».
Вы можете также сделать свойство с ассиметричными уровнями доступа! То есть вы может сделать автоматическое свойство доступным только для чтения или только для записи:
public class Asymetrical 
{ 
public string ThisIsReadOnly { get; private set; } 
public double ThisIsWriteOnly { private get; set; } 
}

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

4. Класс Stopwatch (секундомер)


Сколько раз вы хотели засечь, сколько времени выполняется кусок кода? Может быть, вы пытались отследить среднее время выполнения запроса или другую подобную информацию, или, возможно, пытались отправить предупреждающее сообщение, когда метод требует более одной секунды для выполнения?
Вы могли бы сделать это, используя DateTime:
DateTime start = DateTime.Now; SomeCodeToTime(); 
DateTime end = DateTime.Now; 
Console.WriteLine("Выполнение метода заняло {0} мс", (end-start).TotalMilliseconds);

Но DateTime неточен для такого рода вычислений. Вы можете обратиться к таймеру более высокой точности через Win32 API, но этот способ более «грязный» и подвержен ошибкам.
Так что же делать разработчику? Использовать класс Stopwatch, который расположен в пространстве имен System.Diagnostics. Класс Stopwatch упрощает использование высокоточного таймера:
var timer = Stopwatch.StartNew(); 
SomeCodeToTime(); 
timer.Stop(); 
Console.WriteLine("Выполнение метода заняло {0} мс", timer.ElapsedMilliseconds);

Гораздо более точно. Обратите внимание, что есть статический метод, который создает и запускает секундомер. Также есть возможность создать секундомер с использование new и запустить, когда понадобится.

5. Методы TimeSpan


Сколько раз вы видели код, подобный нижеследующему, и интересовались, как долго должен длиться «сон»?
Thread.Sleep(50);

Пятьдесят секунд? Миллисекунд? На самом деле это миллисекунды. Но что делать, когда наткнешься на что-то подобное:
void PerformRemoteWork(int timeout) { ... }

Что это за тайм-аут? Секунды? Минуты? Миллисекунды? Все завист от разработчиков! Я видел тайм-ауты и интервалы в BCL и пользовательском коде, записанные и как секунды, и как миллисекунды. Для этого почти всегда лучше использовать TimeSpan, поскольку он делает это гораздо менее двусмысленным способом:
void PerformRemoteWork(TimeSpan timeout) { ... }

Теперь не нужно беспокоиться об единицах, потому что они были указаны при создании объекта TimeSpan:
PerformRemoteWork(new TimeSpan(0, 0, 0, 0, 50));

Устраняется неоднозначность с точки зрения самого метода, но не вызывающего кода. Для вызывающего кода пройдут 50 секунд? Или миллисекунд? Кажется, у нас есть подобная проблема! Некоторую путаницу может внести и то, что в классе TimeSpan есть пять конструкторов и все они неоднозначны:
TimeSpan(); 
TimeSpan(long ticks); 
TimeSpan(int hours, int minutes, int seconds); 
TimeSpan(int days, int hours, int minutes, int seconds); 
TimeSpan(int days, int hours, int minutes, int seconds, int milliseconds);

Используйте статические методы класса TimeSpan. Это позволит избежать неоднозанчности при создании объекта TimeSpan:
PerformRemoteWork(TimeSpan.FromMilliseconds(50));

Теперь двусмысленности нет, и все прекрасно читается! Нет шансов неправильно истолковать параметры. По аналогии используйте следующие методы:
TimeSpan.FromDays(double days); 
TimeSpan.FromHours(double hours);
TimeSpan.FromMinutes(double minutes); 
TimeSpan.FromSeconds(double seconds);


Так что вы можете указывать интервал однозначным образом. Это работает и со статическими полями, предназначенными только для чтения:
public sealed class MessageProducer 
{ 
    private static readonly _defaultTimeout = TimeSpan.FromSeconds(30); 
}


Резюме


Надеюсь, вам понравились эти пять маленьких чудес, и у меня есть что предложить вам на следующей неделе. Я надеюсь, вы нашли хоть одно, о котором вы не знали или забыли, и теперь можете использовать!
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 35

    +31
    Спасибо за чудеса, было интересно. Я всегда использую эти чудеса в моем коде и когда вижу код без чудес то делюсь моими чудесами и с другими.
    Если взять любую книгу про C#, то можно получить 1000 серийную серию статей о чудесах. Но лучше взять самые интересные чудеса, будет еще интереснее.

    Жду чудеса следующей недели.
      –7
      Если честно, статья была рассчитана больше на начинающих. Да и прошу принять во внимание, что статья не моего авторства, это перевод. То, что вы сейчас делаете, равносильно тому, чтобы подбежать к группе первоклассников и сказать им «А вы знаете интегральные исчисления? Нет? Ну вы и л-о-о-хи, это же очевидно».
        +1
        А мне так же кажется что это и тонкий намек на перевод.
          +2
          Да понятно, что статья для новичков и тем более должно было бить очевидным то, что мой предыдущий комментарии относится к оригиналу. Но все же мне интересно и я хочу продолжения. Я сам всегда положительно отношусь к таким статьям, потому что все равно найдешь для себя что нибудь новое а если нет, то не будет лишним просто еще раз напомнить себе очевидные вещи, даже если это о том как работает if...else…
        +23
        Всё равно, что «магазин на диване» посмотрел, честное слово…
        • UFO just landed and posted this here
            0
            Раз на то пошло, можно посмотреть и это.
              0
              Не понимаю смысла это использовать, если, как пишет автор: «Также стоит заметить, что один из минусов данного подхода в том, что страдает отладка – ведь для отладчика, каждая цепочка – это один вызов. Чтобы пройтись по вызовам, приходится использовать F11, при этом попадая в каждый из методов. Поэтому во многих случаях мой метод работы с подобным интерфейсом такой: сначала я пишу его в “обычном” стиле, потом делаю отладку, и только когда я уверен что код работает я переписываю его в “монадическом” стиле.»
                0
                Unit-тестирование решает вашу проблему на ура.
                  0
                  Да ну вся конструкция With(x => y) выглядит каким-то костылем. Шарпу не хватает чего-то типа person?Address?PostCode (из той же статьи), или даже просто person.Address.PostCode с тем же результатом без всяких исключений, как в objective-c.
                    0
                    Вохможно. Просто я в основном использую ямбды в работе с колекциями(в том числе и sql). В остальном по старинке, для меня так понятней, и код не превращатся в лапшу.
                0
                реализовывать в C# монады и не делать поддержки linq query syntax — это круто, конечно.
                0
                п. 2 — это не чудо, а симптом нарушения LSP и ISP.
                  +1
                  Это как вы его там усмотрели?
                    0
                    Явное преобразование типа же.
                      0
                      А в исходном его нет? (ну приводите к интерфейсу, а не к конкретному типу, в там примере это не суть)
                        0
                        Там вообще не должно быть преобразования. Ни в исходном ни в «чудесном».
                          0
                          т.е. любой каст ссылычных типов — это нарушение LSP и ISP?
                            0
                            Как преобразование ссылочного типа нарушает LSP можете пояснить?
                              0
                              Явное преобразование обычно используется для «движения вниз» по иерархии классов. То бишь «уточнения типа и интерфейса». А такое уточнение и нарушает LSP, клиентский код должен зависеть только от базового типа и ничего не знать о нижележащей иерархии.
                                +1
                                Один момент — должен быть корректным при подстановке, а не независимым.
                    +7
                    По-мойму StringUtility, сейчас не модно писать :). Лучше сделать методами расширения.

                    public static class StringExtension
                    { 
                         public static string TrimToNull(this String source) 
                         { 
                             return string.IsNullOrWhiteSpace(source) ? null : source.Trim(); 
                         } 
                    }
                    
                      +2
                      Вот-вот, сам MVP, а пишет как джавист :)
                      0
                      Полезная статья. А можно ссылочку на оригинал? (Желательно бы, конечно, добавить ее в пост).
                        0
                        А можно узнать, за что минус-то?
                        Человек пишет статью, говоря, что это «перевод». Оформляет его как обычную статью, хотя на Хабре есть стиль оформления для перевода (даже с обязательным указанием ссылочки на автора). Ссылки на оригинал нет. Да, с гуглом это не такая проблема, но все же…

                        З.ы. Я вот, например, больше люблю читать статьи на английском. Тем более там, скорее всего, есть весь цикл статей, и не нужно ждать еще неделю-две до еще одного перевода.
                          +9
                          Пойду открою Макконнелла и привнесу неделю чудес на хабр.
                            +3
                            >Эти маленькие советы и рекомендации сделают ваш код более кратким, производительным и обслуживаемым. Наверное, многие из вас знают обо всех или некоторых из них, но игнорируют их, либо просто не знают.

                            Maintainable в данном контексте — поддерживаемый.
                            Предисловие — идиотское. Блоггер прочитал MSDN или Джона Скита, и спешит поделиться базовыми особенностями C#. Статья разве что для завлечения сомневающихся, изучать C#.
                              0
                              п3 при этом вообще ниочём. Инкапсуляция нужна для обработки обращения к полю, а не ради одной только инкапсуляции, иначе это решение ради решения. Свойство с таким объявлением нужно применять в интерфейсах, чтобы указать какие методы нужно реализовать (только get, оба или только set (интересно, кто так делает?)). При этом, оба варианта написания п2 идентичны, и никаких преимуществ в использовании того или другого варианта (конкретно там) я не наблюдаю.
                              0
                              Сколько минут ушло на написание статьи?
                                +1
                                Немного печалит тенденция в последнее время. Такое впечатление, что больше озабоченны тем, чтобы их код выглядел красиво с блекджеком и шлюхами. Про быстродействие и оптимизацию при этом порой забывают.

                                Фабрики, которые создают только один один и всегда одинаковый объект, класс с одним методом, рефакторинг ради рефакторинга уже становятся нормой.

                                Писать, а тем более переводить, то, что можно найти в любой актуальной книге для начинающих немного сомнительная практика.
                                  +2
                                  Вы, надеюсь не из тех, кто разворачивает циклы и пишет unsafe код, чтобы выжать еще 2,38% скорости в операции, которая будет использоваться раз в год? =)

                                  Если серьезно, то согласен, что есть много людей, которые мочат всюду всякие AbstractSingletonFactory, из-за чего говнокод выходит на новый уровень. Что печально, некоторые из-за этого думают, что паттерны обычно не нужны.
                                  +1
                                  Первые 3 пункта вроде как решарпер подскажет.
                                  Про StopWatch не знал, спасибо.

                                  И мне кажется что в последнем блоке кода должно быть так:

                                  private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30);

                                  (нехватает типа переменной _defaultTimeout?)
                                    0
                                    string name = value || string.Empty;

                                    А так нельзя? В AS3 можно.
                                    Учу понемногу C# и по сравнению с AS3 мне кажется самым большим неудобством отсутствие автоматического приведения к Boolean
                                      0
                                      Нет, операндами || могут быть только булевские значения.

                                    Only users with full accounts can post comments. Log in, please.