Мы часто пишем подобные методы:

List<int> GetItems() {
   var items = new List<int>();
   ...
   return items; 
} 

В этом методе мы просто собираем элементы и возвращаем.

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

void GetItems(List<int> items) {
   ... 
} 

Таким образом контроль над списком может быть таким:

var list = GetFromPool();
GetItems(list);
...
ReturnToPool(list);
Read More  

Самый простой пример аллокаций - это замыкание. Тут остановимся подробнее. Где могут скрываться аллокации, например, в такой конструкции: list.Where(x => x > 10).ToArray() Очевидно, что при вызове ToArray будет создан массив. Больше никаких аллокаций в данном примере не будет. Давайте рассмотрим второй пример: 

var a = 10; 
list.Where(x => x > a).ToArray() 

Тут к предыдущей аллокации добавляется еще замыкание с переменной "a". Проблема в том, что с этим мы ничего поделать не можем. Но давайте представим, что мы пишем свой замечательный Linq. Каким образом можно избежать аллокации? Нужно сделать просто передачу параметра:

public static void Where<TClosure, T>(this List<T> list, TClosure closure, System.Func<T, TClosure, bool> where) {
     if (where.Invoke(list[i], closure) == false) ... 
}  

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

Аллокации при боксинге. Боксинг (boxing) - это фактически создание ValueType в куче. Это довольно затратный процесс сам по себе, но самое главное - это тот факт, что когда-нибудь GC об этом вам напомнит. При обратном процессе (unboxing) этого не происходит.

Вообще аллокации в куче плохи тем, что они не очень cache-friedly. Но иногда нам нужно сделать аллокации. Представим ситуацию, когда мы аллоцируем массив с нодами, где у каждой ноды есть еще список нод:

var arr = new Node[1000]; 
for (int i = 0; i < arr.Length; ++i) {
     arr[i] = new Node() {
         nodes = new List<Node>(),
     }; 
} 

Обычно я встречал запись именно такую. Чем она плоха? Тем, что мы аллоцируем первую ноду, и сразу в ноде аллоцируем еще один объект (а может и несколько). Т.е. мало того, что куча в принципе не очень cache-friedly, так мы отказываемся от кэша совсем, создавая объекты таким образом. Как нужно было бы поступит?

var arr = new Node[1000]; for (int i = 0; i < arr.Length; ++i) {
     arr[i] = new Node(); // Создаем ноды, чтобы GC постарался их разместить в памяти последовательно } 
for (int i = 0; i < arr.Length; ++i) {
     arr[i].nodes = new List<Node>(); // Инициализируем данные каждой ноды 
} 

Таким образом, если мы не готовы писать cache-friedly код в целом, то хотя бы частично небольшими изменениями мы можем постараться помочь GC.

Read More