Домашняя страница Написание компактного и эффективного кода в C#
Публикация
Отменить

Написание компактного и эффективного кода в C#

Языковые средства не стоят на месте и сегодня C# позволяет писать многие вещи более компактно, чем это выглядело бы в C++. Для этого используются делегаты, лямбда функции и алгоритмы пакетной обработки данных. В статье будут рассмотрены наиболее типичные задачи и их решения, которые позволят сделать ваш код более компактным, понятным и эффективным.

Базовые средства создания компактного кода:

  1. Использование директивы using (как аналог typedef в С++) для создания нового типа:
1
using Sequence = System.Collections.Generic.List<Graphics.ImageRect>;
  1. Использования наследования для спецификации шаблона:
1
2
3
4
5
public class Pair< T, U >{...}
public class PairI : Pair< Int32, Int32 >
{
    public PairI( Int32 first, Int32 second ) : base( first, second ) {}
}
  1. Использование параметра var как типа переменной. Переменная объявленная так определит свой тип не явно, на этапе компиляции из анализа кода. Не стоит злоупотреблять этим параметром, т.к. это может сильно ухудшить читабельность кода. Однако он очень хорошо подходит для использования для определения переменной цикла:
1
var v1 = someClass;
  1. При получение элемента из Dictionary нужно пользоваться функцией TryGetValue, а не ContainsKey. Это во-первый почти в два раза быстрее, во вторых не потребуется дальнейшее извлечение элемента:

Нужно писать:

1
2
3
Image img = null;
if( images.TryGetValue( name, out img ) )
    return img;

Вместо:

1
2
if( images.ContainsKey( name ) )
    return images[ name ];

Компактная инициализация массива:

Часто можно создать массивы при помощи списка инициализации, вместо цикла.

1
2
LogType[ ] logTypes = { LogType.Fatal, LogType.Error, LogType.Warning };
Int32[ , ] arr = new Int32[ 2, 3 ] { {1,2,3}, {4,5,6} };

Так же можно компактно инициализировать структуры:

1
2
3
4
5
6
7
8
9
10
11
struct Desc
{
    public String    name;
    public Int32    val;
}

Desc[] arr = new Desc[]
{
    new Desc(){ name = "name1", val = 1 },
    new Desc(){ name = "name2", val = 2 }
};

Компактная обработка массива:

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

1
2
3
4
list.ForEach( item => item.DoSomething() ); // у всех элементов вызвать функцию DoSomething
SomeClass item = list.Find( item => item.Name == name ); // поиск элемента по имени
List<SomeClass> items = list.FindAll( item => item.Value > 10 ); // поиск всех элементов, удовлетворяющих условию
bool res = list.TrueForAll( item => item.IsValid ); // проверка коллекции на удовлетворение некому условию

Использование модификатора var обычно может сильно ухудшить читаемость кода. Однако, при итерировании некой коллекции со сложным типом объектов использование этого модификатора может сильно сократить объем кода.

1
Dictionary< String, SomeClass > dict ...;

Лучше писать:

1
foreach( var pair in dict )

Вместо:

1
foreach( KeyValuePair< String, SomeClass > pair in dict )

Проверка валидности:

При работе со строками:

Нужно писать:

1
if( String.IsNullOrEmpty( name ) )

Вместо:

1
if( null == name || name.Length == 0 )

Если нужно проверить объект на принадлежность определенному типу, а затем работать с ним, то лучше использовать конструкцию as вместо as+is и тем более не использовать явного преобразования к типу.

Это в два раза быстрее:

1
2
SomeClass sc = obj as SomeClass;
if( null != sc ){ /* do something */ }

чем это:

1
2
3
4
5
if( obj is SomeClass )
{
    SomeClass sc = obj as SomeClass;
    /* do something */
}

Анонимные делегаты и лямбда функции:

Вот несколько вариантов передачи делегата некоторому классу. Эти варианты делают одно и тоже и следует выбирать наиболее подходящий для каждой конкретной ситуации:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1 вариант: реализовать функцию и передать ее в класс делегатом
void ActionFunc()
{
    // do something
}
obj.RegisterCallback( ActionFunc );

// 2 вариант: реализовать функцию в виде анонимного делегата.
// Удобно для маленького кода, чтобы было видно, что делается в месте вызова.
obj.RegisterCallback( delegate()
{
    // do something
} );

// 3 вариант: лямбда функция удобно для очень маленького кода (1-2 выражения)
obj.RegisterCallback( () => { /*do something*/} );

Использование деструктора объектов:

В C# нет деструктора, как в С++. Однако есть функция Dispose() интерфейса IDisposable, которую можно воспользоваться для упрощения кода. Допустим нам нужно обрамить некоторый код вызовами функций Start и Stop, которые что-либо делают. Например эти функции считают время, прошедшее между вызовами для профелирования кода. Выглядит это так:

1
2
3
4
5
6
7
8
9
10
11
12
void DoSomething()
{
    Start();
    //... some computations
    if( .. )
    {
        Stop();
        return;
    }
    // ... some computations
    Stop();
}

Конечно можно в каждом месте писать эти функции, однако следить за этим становится довольно проблематично. Особенно если учесть, что в коде могут быть операции return, при которых нужно так же вызвать функцию Stop. Для решения этой задачи мы создадим вспомогательный класс и код станет выглядеть так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void DoSomething()
{
    using( new Starter() )
    {
        //... some computations
        if( .. ) return;
        //... some computations
    }
}

// а вот вспомогательный класс
private class Starter : IDisposable
{
    public Starter( Object obj )
    {
        Start();
    }
    public void Dispose()
    {
        Stop();
    }
}

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

Публикация защищена лицензией CC BY 4.0 .

Манифест слову "Сделано!"

11 уравнений, которые должен знать каждый уважающий себя инженер!