Вы, наверное, слышали и встречали такую ​​штуку, а некоторые даже писали.

 экземпляр.Метод1().Метод2()

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

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

Как? Ну это довольно просто:

 недействительный метод() {}

Меняем на

 T Method() { верните это; }

Где T может быть текущим типом, может быть интерфейсом (как в оригинале было и задумано, но структуру все портируют).

Зачем? Лаконичность кода и легкость восприятия.

Напишите в комментариях, где вы используете подход Fluent-интерфейса.

Читать далее  

Есть такой мемчик, где люди растут на 2 типа и вот это все. Ну там, где у человека выключено использование ворнингов и там, где включено.

Я отношусь к тем, у кого они включены, и вообще не люблю когда в логах мусор.

Так вот, в шарпе есть такая штука:

 #pragma предупреждение отключить
#pragma предупреждение восстановить

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

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives#pragma-warning

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

Поддерживайте ваш код в чистоте, без лишних журналов и предупреждений.

Читать далее  

Это приведение к единичному размеру, при этом направление сохраняется. Обычно мы используем для этого v.normalized или v.Normalize(). Второй вариант будет немного быстрее первого, т.к. мы не создаем копию вектора, а изменяем существующий.

Но как оно работает внутри? Как нетрудно догадаться из определения нормализации, чтобы привести вектор к единичному - нам нужна его длина, а длина вектора - это корень по теореме Пифагора. Поэтому если вы по какой-то причине уже получили длину вектора, то просто поделите (ну поделите, ага https://t.me/unsafecsharp/150):

var inv_length = 1f / length;
v.x *= inv_length;
v.y *= inv_length;

И получите нормализованный вектор.

А то я часто встречаю примерно такой код в хот частях:

var length = v.magnitude;
var n = v.normalized;

Хотя на деле проще было бы просто получить длину один раз и использовать ее дважды (ну или сделать свой метод для этого).

Читать далее  

Я уже делал пост про FixUpdate (https://t.me/unsafecsharp/103), но не написал про разницу между fixDeltaTime и deltaTime.

В методе Update deltaTime будет измеряться время, прошедшее с прошлого кадра, при этом фиксированноеDeltaTime будет равно фиксированной величине.

А вот в фиксированном обновлении фиксированногоDeltaTime сохраняется, а вот deltaTime будет соответствовать фиксированномуDeltaTime. Другими словами, можно использовать deltaTime внутри фиксированного обновления.

Читать далее  

Мы часто используем этот метод для динамического создания атласа, когда, например, загружаем аватарки игроков, о которых мы заранее не знаем. Или мы формируем атлас для боя, когда игроки могут выбрать какие-нибудь скины юнитов, а нам все еще нужен 1 ничья ;)

Читать далее  

установите комбайнить меши, которые располагаются на GameObject. Довольно удобно не ползать по иерархии и не собирать их там.

Есть способ комбинировать меши и без этого: mesh.CombineMeshes.

Но пандемия переходит к уже структурам, в которых базируются матрицы и мешки.

Мы часто используем такое для 3D, когда создаём одну мешку и вместо 1к объектов отображаем всего один. Естественно, нужно понимать, что батчить нужно по-материально, т.е. какой-то код с этой логикой нужно будет все же написать.

Читать далее  

Если вы не используете параметр <span>out</span>, по которому возвращается значение, то можете писать просто _:

Method(out int notUsed);
Method(out _);
Читать далее  

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

Я часто встречаю вот такой код:

 а += б/2ф;

И мне все время такое хочется, напиши такой код:

 а += б * 0,5f;

Также можно заменить любые другие константы по тому же принципу. Но что делать, если у нас разделение не на константу, а на x? Да все просто, делаем y = 1f / x и используем уже y.

Читать далее  

Условие всегда ленивое и хочет побыстрее выйти. Если v1 будет правдой, то что там дальше его не будет интересовать:

 if (v1 == true || v2 == true) {...}

Таким образом, если у нас есть такой код:

 вар v1 = CalcV1();
вар v2 = CalcV2();
if (v1 == true || v2 == true) {...}

Выглядит хоть и симпатично, но совершенно непроизводительно.

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

 if (CalcV1() == true || CalcV2() == true) {...}

Естественно нужно понимать, что CalcV2 не будет возвращаться, если CalcV1 вернет true, поэтому не нужно это расчитывать. Но я надеюсь, что вы это :) знаете

Читать далее  

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

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

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

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

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

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

var list = GetFromPool();
GetItems(list);
...
ReturnToPool(list);
Читать далее  

Допустим, у нас есть простой метод для перебора чего-либо:

class Node {
   public int value;
   public Node[] children; 
}  

Node FindRecursively(Node root, int value) {
   if (root.value == value) return root;
   for (int i = 0; i < root.children.Length; ++i) {
     var node = FindRecursively(root.children[i], value);
     if (node != null) return node;
   }
   return null; 
} 

Мы видим, что если на вход передать ноду графа и значение, то мы в итоге найдем нужную ноду, либо вернем null.

Чем плох такой вариант? Дело в том, что каждый вход в метод FindRecursively будет создавать стек под этот метод, т.е. чем больше мы используем в методе всяких переменных, тем быстрее закончится место в нашем стеке, если представить, что граф у нас довольно большой.

Что же делать?

Давайте избавимся от рекурсивного вызова метода. Самый простой вариант это сделать - использовать Stack или Queue:

Node Find(Node root, int value) {
   var queue = new Queue<Node>();
   queue.Enqueue(root);
   while (queue.Count > 0) {
     var node = queue.Dequeue();
     if (node.value == value) return node;
     for (int i = 0; i < node.children.Length; ++i) {
       queue.Enqueue(node.children[i]);
     }
   }
   return null; 
} 

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


Читать далее  

Допустим, что у нас есть отсортированный массив чисел:

1 2 6 8 56 234 745 998 1010

Как нам определить, что в этом массиве есть какое-то число?

Самый простой вариант - пройти линейно от первого элемента до последнего. Таким образом мы получаем O(n) (Если кто не видел пост https://t.me/unsafecsharp/97).

Но как это сделать быстрее?

На этом месте те, кто не знает, может остановиться и подумать

На самом деле существует небольшой лайфхак: если вы видите что-нибудь отсортированное (массив это или матрица - не важно) и задача - поиск, то сложность всегда будет log(n). В теории можно придумать алгоритм, который будет еще умнее, но сложность от этого не изменится.

Итак, для начала нам нужно взять центральный индекс, то есть length / 2. После чего у нас массив разделится на два подмассива, при этом слева элементы будут всегда меньше искомого, а справа - больше.

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

Ищем 2:

[1 2 6 8 56 234 745 998 1010] 
[1 2 6 8 56]
[1 2 6]

Т.е. мы за 3 итерации нашли число. Вот это отбрасывание половины и есть log(n).

Читать далее