﻿# Что нового в Unity 6? Обзор нововведений и ошибок в исходном коде

Наконец, состоялся релиз Unity 6\! Разработчики называют эту версию самой стабильной версией за всё время существования движка\. Почему бы нам не убедиться в этом с помощью статического анализатора кода? А заодно проведём краткий обзор основных фич и улучшений, привнесённых обновлением\.

![1172_Unity6_ru/image1.png](https://import.viva64.com/docx/blog/1172_Unity6_ru/image1.png)

## Введение

Unity 6 — это глобальное обновление, нацеленное на улучшение производительности и стабильности движка, но далеко не ограничивающееся этим\. Улучшения затронули самые разные аспекты: инструментарий мультиплеера, систему освещения, рендеринг, постобработку, XR\-инструментарий, некоторые визуальные эффекты и пр\. Отдельно хочу отметить, что была добавлена система для интеграции нейросетей\.

Хотя preview\-версия Unity 6 уже была доступна с 15 мая 2024 года, возможно вы, как и я, лишь недавно заинтересовались что же особенного в этом обновлении\. Поэтому в первой части статьи я хочу провести краткий обзор наиболее значительных, на мой взгляд, улучшений, но не буду сильно углубляться в детали\. Вместо этого я поделюсь ссылками, по которым вы сможете получить больше информации\.

Во второй части статьи вы сможете испытать свои навыки анализа кода и попробовать отыскать потенциальные ошибки в нескольких фрагментах кода, найденных статическим анализатором в Open Source части движка\. Такая тренировка — хорошая возможность научиться на чужих ошибках и минимизировать вероятность их допущения в собственном коде\.

Приятного чтения\!

## Что нового в Unity 6?

Итак, давайте узнаем, что же команда Unity приготовила для нас на этот раз\.

![](https://import.viva64.com/docx/blog/1172_Unity6_ru/image2.mp4)

### Интеграция ИИ на основе искусственных нейронных сетей

Добавлен фреймворк [Sentis](https://docs.unity3d.com/Packages/com.unity.sentis@2.0/manual/index.html)\. Он позволяет интегрировать предварительно обученные модели нейронных сетей в среду выполнения и использовать их для реализации таких функций как распознавание речи и объектов, создания интеллектуальных NPC и т\. д\.

### Расширение инструментария для создания многопользовательских проектов

* Новый пакет [Multiplayer Center](https://docs.unity3d.com/6000.0/Documentation/Manual/multiplayer-center.html)\. Представляет отправную точку для создания многопользовательской игры\. Он рекомендует пакеты для мультиплеера в зависимости от заданных параметров игры и предоставляет доступ к соответствующим примерам и руководствам\.
* Добавлен [Multiplayer Play Mode](https://docs-multiplayer.unity3d.com/mppm/current/about/)\. Позволяет моделировать до 4\-х игроков прямо из редактора Unity\. 
* В пакет Netcode for GameObjects добавлена поддержка [Distributed authority topologies](https://docs-multiplayer.unity3d.com/netcode/current/terms-concepts/distributed-authority/)\. А в пакет Multiplayer Tools добавлен новый инструмент [Network scene visualization](https://docs-multiplayer.unity3d.com/tools/current/netscenevis/), который, помимо прочего, должен помочь в отладке проектов, использующих новую топологию\.
* Добавлен [пакет](https://docs.unity3d.com/Manual/dedicated-server.html) для поддержки разработки игр и приложений на платформе выделенного сервера\.

![1172_Unity6_ru/image4.png](https://import.viva64.com/docx/blog/1172_Unity6_ru/image4.png)

Рисунок N1 – Multiplayer Play Mode на практике

### Оптимизация нагрузки на CPU

* Новая система рендеринга [GPU Resident Drawer](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.0/manual/gpu-resident-drawer.html)\. Эффективность этой системы напрямую зависит от среднего количества экземпляров объектов в сцене\. Чем больше в сцене экземпляров одних и тех же объектов \(например, растительности, созданной с помощью _SpeedTree_\) — тем больше польза\. Данная система имеет ряд технических ограничений\. Например, она оптимизирует рендеринг только экземпляров объектов со статическими сетками\. Следовательно, она не будет работать в случае экземпляров систем частиц и прочих эффектов\.
* Новая функция [GPU Occlusion Culling](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.0/manual/gpu-culling.html)\. Данная функция выполняет исключение из рендеринга полностью затенённых объектов на GPU вместо CPU\.

### Оптимизация нагрузки на GPU

* Новая функция [Foveated Rendering](https://docs.unity3d.com/2022.3/Documentation/Manual/xr-foveated-rendering.html)\. Уменьшает нагрузку графического процессора в XR\-проектах за счёт снижения детализации в периферийном зрении пользователя\. Имеет два режима:
  * **Fixed foveated** — рендеринг центральной области дисплея для каждого глаза с максимальным разрешением и снижение разрешения на периферии;
  * **Gaze\-based foveated** — в этом режиме используется отслеживание глаз для определения области экрана с максимальным разрешением\.
* Уменьшение нагрузки на GPU за счёт внедрения автоматического объединения этапов рендеринга в Render Graph\. Также GPU можно оптимизировать с помощью создания собственных этапов и проходов между ними\. Стоит также отметить, что теперь [Render Graph доступен не только в HDRP, но и в URP](https://docs.unity3d.com/6000.0/Documentation/Manual/urp/render-graph-introduction.html)\.

### Улучшение качества рендеринга среды

* Улучшена визуализация неба для заката и восхода солнца в HDRP\. Добавлены эффекты озонового слоя и рассеивания в атмосфере в дополнение к туману на больших расстояниях;
* Улучшена визуализация воды \(в HDRP\) посредством добавления эффекта подводного объёмного тумана\.

![1172_Unity6_ru/image5.png](https://import.viva64.com/docx/blog/1172_Unity6_ru/image5.png)

В левой части изображения эффект озонового слоя не используется в отличии от правой\.



![](https://import.viva64.com/docx/blog/1172_Unity6_ru/image6.mp4)

Эффект подводного тумана

### Прочие улучшения

* Были внесены важные улучшения в XR\-инструментарий;
* Редактор Unity 6, в отличии от предыдущих версий, теперь работает на устройствах Windows на базе Arm;
* Улучшено окно сборки, были добавлены новые профили сборки;
* Были внесены улучшения, связанные с освещением\. В частности, был добавлен [новый способ создания глобального освещения](https://docs.unity3d.com/6000.0/Documentation/Manual/urp/probevolumes.html);
* Исправлены основные известные проблемы, а также внесены некоторые улучшения в Shader Graph\.

Перечисленные здесь изменения показались мне основными, но это вовсе не полный список\. Более полный список улучшений, а также их подробное описание вы можете найти [здесь](https://docs.unity3d.com/6000.0/Documentation/Manual/UnityManual.html)\.

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

## Разбор новых ошибок в коде движка

Итак, вооружившись анализатором кода PVS\-Studio, я исследовал C\#\-часть наиболее актуальной на момент написания статьи версии движка \([6000\.0\.21f1](https://github.com/Unity-Technologies/UnityCsReference/releases/tag/6000.0.21f1)\) и нашёл несколько интересных как потенциальных, так и явных ошибок\. Однако вместо того, чтобы просто описать вам их я бы хотел предложить вам испытать свои силы в роли статического анализатора кода\. Попробуйте отыскать по одной ошибке в приведённых фрагментах кода и придумать способы их исправления\. Под каждым из фрагментов есть полный ответ, с помощью которого вы можете проверить своё предположение\. 

Да начнутся ~~голодные игры~~ испытания и пусть программистская смекалка всегда будет с вами\! Кстати, будет круто, если вы поделитесь результатами в комментариях :\)

### Первое испытание

```cpp
public bool propagationHasStopped { get; }
public bool immediatePropagationHasStopped { get; }
public bool defaultHasBeenPrevented { get; }
public EventDebuggerCallTrace(IPanel panel, EventBase evt, 
                              int cbHashCode, string cbName,
                              bool propagationHasStopped,
                              bool immediatePropagationHasStopped,
                              long duration,
                              IEventHandler mouseCapture): base(....)
{
  this.callbackHashCode = cbHashCode;
  this.callbackName = cbName;
  this.propagationHasStopped = propagationHasStopped;
  this.immediatePropagationHasStopped = immediatePropagationHasStopped;
  this.defaultHasBeenPrevented = defaultHasBeenPrevented;
}
```

<details>
   <summary>Ответ спрятан здесь</summary>

Сообщение анализатора:



[V3005](https://pvs-studio.ru/ru/docs/warnings/v3005/)\. The 'this\.defaultHasBeenPrevented' variable is assigned to itself\. EventDebuggerTrace\.cs 42\.



По какой\-то причине при инициализации свойство _defaultHasBeenPrevented_ присваивается само себе\. Так как у свойства отсутствует сеттер, его значение больше никогда не изменится и всегда будет равно значению по умолчанию — _false_\.



Возможное исправление: добавить в конструктор новый параметр для инициализации свойства\.


</details>


Слишком просто? Будем считать это разминкой, посмотрим, как вы справитесь дальше\!



### Второе испытание

```cpp
public void ParsingPhase(....)
{
  ....
  SpriteCharacter sprite = 
    (SpriteCharacter)textInfo.textElementInfo[m_CharacterCount]
                             .textElement;

  m_CurrentSpriteAsset = sprite.textAsset as SpriteAsset;
  m_SpriteIndex = (int)sprite.glyphIndex;

  if (sprite == null)
    continue;

  if (charCode == '<')
    charCode = 57344 + (uint)m_SpriteIndex;
  else
    m_SpriteColor = Color.white;
}
```

<details>
   <summary>Ответ спрятан здесь</summary>

Сообщение анализатора:



[V3095](https://pvs-studio.ru/ru/docs/warnings/v3095/)\. The 'sprite' object was used before it was verified against null\. Check lines: 310, 312\. TextGeneratorParsing\.cs 310\.



Переменная _sprite_ разыменовывается прямо перед проверкой на _null_:

```cpp
m_CurrentSpriteAsset = sprite.textAsset as SpriteAsset;
m_SpriteIndex = (int)sprite.glyphIndex;

if (sprite == null)
  continue;
```

В случае если _sprite_ действительно окажется равным _null_, это неизбежно приведёт к исключению\. Вероятно, при добавлении строк c разыменованием в метод не обратили внимание на важность порядка выполнения операций\. Таким образом, избежать проблемы можно, если перенести разыменования под проверку на _null_:



```cpp
if (sprite == null)
  continue;

m_CurrentSpriteAsset = sprite.textAsset as SpriteAsset;
m_SpriteIndex = (int)sprite.glyphIndex;
```




</details>


### Третье испытание

```cpp
private static void CompileBackgroundPosition(....)
{
  ....
  else if (valCount == 2)
  {
    if (((val1.handle.valueType == StyleValueType.Dimension) ||
         (val1.handle.valueType == StyleValueType.Float)) &&

        ((val1.handle.valueType == StyleValueType.Dimension) || 
         (val1.handle.valueType == StyleValueType.Float)))
    {
      .... = new BackgroundPosition(...., val1.sheet
                                              .ReadDimension(val1.handle)
                                              .ToLength());

      .... = new BackgroundPosition(...., val2.sheet
                                              .ReadDimension(val2.handle)
                                              .ToLength());
    }
    else if ((val1.handle.valueType == StyleValueType.Enum)) &&
             (val2.handle.valueType == StyleValueType.Enum)
    ....
  {
   
 }
```

<details>
   <summary>Ответ спрятан здесь</summary>

Сообщение анализатора:



[V3001](https://pvs-studio.ru/ru/docs/warnings/v3001/)\. There are identical sub\-expressions to the left and to the right of the '&&' operator\. StyleSheetApplicator\.cs 169\.



Анализатор подсказывает, что в одном из условий через оператор _&&_ выполняются два одинаковых выражения:

```cpp
if (((val1.handle.valueType == StyleValueType.Dimension) ||
     (val1.handle.valueType == StyleValueType.Float)) &&

    ((val1.handle.valueType == StyleValueType.Dimension) || 
     (val1.handle.valueType == StyleValueType.Float)))
```

Здесь явно допущена ошибка\. Более того очевидно и её исправление\. Обратите внимание на остальной код\. В нём повсеместно выполняются аналогичные операции сначала с использованием _val1_, а потом с использованием _val2_\. Например, следующее за рассматриваемым условие, состоит из двух аналогичных проверок:

```cpp
else if ((val1.handle.valueType == StyleValueType.Enum)) &&
         (val2.handle.valueType == StyleValueType.Enum)
```

Разница между ними заключается лишь в том, что в первой используется значение _val1_, а во второй — _val2_\. 

Таким образом, исправление ошибки, вероятнее всего, будет заключаться в замене всех _val1_ на _val2_ в повторяющемся выражении некорректного условия:

```cpp
if (((val1.handle.valueType == StyleValueType.Dimension) ||
     (val1.handle.valueType == StyleValueType.Float)) &&

    ((val2.handle.valueType == StyleValueType.Dimension) || 
     (val2.handle.valueType == StyleValueType.Float)))
```


</details>


### Четвёртое испытание

```cpp
public partial class BuildPlayerWindow : EditorWindow
{
  ....
  internal static event Action<BuildProfile> 
                        drawingMultiplayerBuildOptions;
  ....
}

internal static class EditorMultiplayerManager
{
  ....
  public static event Action<NamedBuildTarget> drawingMultiplayerBuildOptions
  {
    add => BuildPlayerWindow.drawingMultiplayerBuildOptions += 
                                             (profile) => ....;
    remove => BuildPlayerWindow.drawingMultiplayerBuildOptions -= 
                                                (profile) => ....;
  }
  ....
}
```

<details>
   <summary>Ответ спрятан здесь</summary>

Сообщение анализатора:



[V3084](https://pvs-studio.ru/ru/docs/warnings/v3084/)\. Anonymous function is used to unsubscribe from 'drawingMultiplayerBuildOptions' event\. No handlers will be unsubscribed, as a separate delegate instance is created for each anonymous function declaration\. EditorMultiplayerManager\.bindings\.cs 48\.



Простая, но распространённая ошибка\. Здесь для подписки на событие и отписки от него используются анонимные функции\. Независимо от того, насколько визуально они похожи, эти функции всё равно будут разными объектами\. В результате подписка на событие выполнится корректно, а вот отписка уже не сработает\.



Один из вариантов решения — вместо анонимной функции реализовать полноценный метод и использовать его для подписки/отписки на/от события\.




</details>


### Пятое испытание

```cpp
public void DoRenderPreview(Rect previewRect, GUIStyle background)
{
  ....
  Matrix4x4 shadowMatrix;
  RenderTexture shadowMap = RenderPreviewShadowmap(....);

  if (previewUtility.lights[0].intensity != kDefaultIntensity ||  
      previewUtility.lights[0].intensity != kDefaultIntensity)
  {
    SetupPreviewLightingAndFx(probe);
  }

  float tempZoomFactor = (is2D ? 1.0f : m_ZoomFactor);
  previewUtility.camera.orthographic = is2D;
  if (is2D)
    previewUtility.camera.orthographicSize = 2.0f * m_ZoomFactor;
  ....
}

private void SetupPreviewLightingAndFx(SphericalHarmonicsL2 probe)
{
  previewUtility.lights[0].intensity = kDefaultIntensity;
  previewUtility.lights[0].transform.rotation = ....;
  previewUtility.lights[1].intensity = kDefaultIntensity;
  ....
}
```

<details>
   <summary>Ответ спрятан здесь</summary>

Сообщение анализатора:



[V3001](https://pvs-studio.ru/ru/docs/warnings/v3001/)\. There are identical sub\-expressions 'previewUtility\.lights\[0\]\.intensity \!\= kDefaultIntensity' to the left and to the right of the '\|\|' operator\. AvatarPreview\.cs 721\.



Условие первого _if_\-оператора в методе _DoRenderPreview_ состоит из двух одинаковых подвыражений\. Это может быть как ошибкой, так и просто избыточным кодом, оставленным по невнимательности\.



На наличие именно ошибки здесь намекает реализация метода _SetupPreviewLightingAndFx_, в которой используется не только _previewUtility\.lights\[0\]_, но и _previewUtility\.lights\[1\]_\.



Таким образом, исправление ошибки может выглядеть следующим образом:

```cpp

if (previewUtility.lights[0].intensity != kDefaultIntensity ||  
    previewUtility.lights[1].intensity != kDefaultIntensity)
{
  ....
}
```


</details>


### Шестое испытание

```cpp
void UpdateInfo()
{
  ....

  var infoLine3_format = "<color=\"white\">CurrentElement:" +
                         " Visible:{0}" +
                         " Enable:{1}" +
                         " EnableInHierarchy:{2}" +
                         " YogaNodeDirty:{3}";

  m_InfoLine3.text = string.Format(infoLine3_format,
                                   m_LastDrawElement.visible,
                                   m_LastDrawElement.enable,
                                   m_LastDrawElement.enabledInHierarchy,
                                   m_LastDrawElement.isDirty);

  var infoLine4_format = "<color=\"white\">" +
                         "Count of ZeroSize Element:{0} {1}%" +
                         " Count of Out of Root Element:{0} {1}%";

  m_InfoLine4.text = string.Format(infoLine4_format,
                                   countOfZeroSizeElement,
                                   100.0f * countOfZeroSizeElement / count,
                                   outOfRootVE,
                                   100.0f * outOfRootVE / count);
  ....
}
```

<details>
   <summary>Ответ спрятан здесь</summary>

Сообщение анализатора:



[V3025](https://pvs-studio.ru/ru/docs/warnings/v3025/)\. Incorrect format\. A different number of format items is expected while calling 'Format' function\. Arguments not used: 3rd, 4th\. UILayoutDebugger\.cs 179\.



Обратите внимание на второй _string\.Format\(\.\.\.\.\)_\. В его строке формата \(первый аргумент\) имеется 4 слота для вставки\. Значений для вставки \(2\-5 аргумент\) также передаётся 4\. Проблема в том, что слоты содержат только номера 0 и 1\. В результате в них будут вставлены только первое и второе значение, тогда как остальные два не будут использованы вовсе\.



Исправленная строка формата выглядит следующим образом:



```cpp
var infoLine4_format = "<color=\"white\">" +
                       "Count of ZeroSize Element:{0} {1}%" +
                       " Count of Out of Root Element:{2} {3}%";
```




</details>


### Седьмое испытание

```cpp
protected static bool IsFinite(float f)
{
  if (   f == Mathf.Infinity 
      || f == Mathf.NegativeInfinity 
      || f == float.NaN)
  {
    return false;
  }

  return true;
}
```

<details>
   <summary>Ответ спрятан здесь</summary>

Сообщение анализатора:



[V3076](https://pvs-studio.ru/ru/docs/warnings/v3076/)\. Comparison of 'f' with 'float\.NaN' is meaningless\. Use 'float\.IsNaN\(\)' method instead\. PhysicsDebugWindowQueries\.cs 87\.



Ошибка здесь связана с не очень очевидным поведением\. Сравнение двух значений равных _NaN_ всегда ложно\. Поэтому, как и советует анализатор, вместо выражения _f \=\= float\.NaN_ здесь следует использовать _float\.IsNaN\(f\)_\.




</details>


### Восьмое испытание

```cpp
public readonly struct SearchField : IEquatable<SearchField>
{
  ....
  public override bool Equals(object other)
  {
    return other is SearchIndexEntry l && Equals(l);
  }

  public bool Equals(SearchField other)
  {
    return string.Equals(name, other.name, StringComparison.Ordinal);
  }
}
```

<details>
   <summary>Ответ спрятан здесь</summary>

Сообщение анализатора:



[V3197](https://pvs-studio.ru/ru/docs/warnings/v3197/)\. The compared value inside the 'Object\.Equals' override is converted to the 'SearchIndexEntry' type instead of 'SearchField' that contains the override\. SearchItem\.cs 634\.



Как сказано в сообщении анализатора, в первом методе _Equals_ параметр _other_ ошибочно приводится к типу _SearchIndexEntry_ вместо _SearchField_\. Из\-за чего при последующем вызове _Equals\(l\)_ будет вызвана та же самая перегрузка метода\. Если же вдруг окажется, что _other_ действительно имеет тип _SearchIndexEntry_, произойдёт зацикливание кода\. Это приведёт к _StackOverflowException_\.


</details>


### Девятое испытание

```cpp
private void DrawRenderTargetToolbar()
{
  float blackMinLevel = ....;
  float blackLevel = ....;
  float whiteLevel = ....;
  EditorGUILayout.MinMaxSlider(....);
  float whiteMaxLevel = ....;

  if (blackMinLevel < whiteMaxLevel && whiteMaxLevel > blackMinLevel)
  {
    m_RTBlackMinLevel = blackMinLevel;
    m_RTWhiteMaxLevel = whiteMaxLevel;

    m_RTBlackLevel = Mathf.Clamp(blackLevel, 
                                 m_RTBlackMinLevel, 
                                 whiteLevel);

    m_RTWhiteLevel = Mathf.Clamp(whiteLevel, 
                                 blackLevel,
                                 m_RTWhiteMaxLevel);
  }
}
```

<details>
   <summary>Ответ спрятан здесь</summary>

Сообщение анализатора:



[V3001](https://pvs-studio.ru/ru/docs/warnings/v3001/)\. There are identical sub\-expressions 'blackMinLevel < whiteMaxLevel' to the left and to the right of the '&&' operator\. FrameDebuggerEventDetailsView\.cs 364\.



Снова перед нами _if_\-оператор, условие которого состоит из двух, по сути, одинаковых выражений, которые отличаются друг от друга лишь порядком операндов\. Вероятно, вместо _blackMinLevel_ во втором выражении должно быть что\-то другое\. Рассмотрев окружающий код, наиболее логичным вариантом кажется _whiteLevel_\. Таким образом, исправленное условие могло бы выглядеть следующим образом:



```cpp
if (blackMinLevel < whiteMaxLevel && whiteMaxLevel > whiteLevel)
{
  ....
}
```




</details>


### Десятое испытание

```cpp
internal static IEnumerable<Sample> FindByPackage(PackageInfo package, ....)
{
  if (string.IsNullOrEmpty(package?.upmReserved) && 
      string.IsNullOrEmpty(package.resolvedPath))
  {
    return Enumerable.Empty<Sample>();
  }
  try
  {
    IEnumerable<IDictionary<string, object>> samples = null;
    var upmReserved = upmCache.ParseUpmReserved(package);
    if (upmReserved != null)
        samples = upmReserved.GetList<....>("samples");
    ....
  }
  ....
}
```

<details>
   <summary>Ответ спрятан здесь</summary>

Сообщение анализатора:



[V3042](https://pvs-studio.ru/ru/docs/warnings/v3042/)\. Possible NullReferenceException\. The '?\.' and '\.' operators are used for accessing members of the 'package' object\. PackageSample\.cs 102\.



Проверкой в начале метода разработчик попытался обработать сразу несколько случаев:

* когда _package\.upmReserved_ равен _null_ или пустой строке;
* когда _package\.resolvedPath_ равен _null_ или пустой строке;
* когда _package_ равен _null_\.



Однако в большинстве случаев она будет работать некорректно:

* если _package_ равен _null_, то _NullReferenceException_ будет выброшен уже при разыменовании во втором подвыражении самой проверки;
* если либо _package\.upmReserved_, либо _package\.resolvedPath_ будет равен _null_ или пустой строке \(но не оба сразу\) выхода из метода, вопреки ожиданиям, не произойдёт\.



Возможно, разработчик по невнимательности использовал оператор _&&_ вместо \|\|\. Таким образом, исправленная версия проверки может выглядеть так:



```cpp
if (string.IsNullOrEmpty(package?.upmReserved) || 
    string.IsNullOrEmpty(package.resolvedPath))
{
  return Enumerable.Empty<Sample>();
}
```


</details>


### Одиннадцатое испытание

```cpp
[RequiredByNativeCode]
internal static void InvalidateAll()
{
  lock (s_Instances)
  {
    foreach (var kvp in s_Instances)
    {
      WeakReference wr = kvp.Value;
      if (wr.IsAlive)
        (wr.Target as TextGenerator).Invalidate();
     }
   }
}
```

<details>
   <summary>Ответ спрятан здесь</summary>

Сообщение анализатора:



[V3145](https://pvs-studio.ru/ru/docs/warnings/v3145/)\. Unsafe dereference of a WeakReference target\. The object could have been garbage collected between checking 'IsAlive' and accessing the 'Target' property\. TextGenerator\.cs 140\.

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

1. Не все знакомы с понятием слабой ссылки \(_WeakReference_\)\. Объект, на который ссылается такая ссылка, в любой момент может быть удален сборщиком мусора, несмотря на наличие слабой ссылки\.
1. Свойство _WeakReference\.IsAlive_ позволяет проверить существует ли ещё объект, на который ссылается слабая ссылка\. Но в данном коде не учитывается, что этот самый объект может быть отчищен уже после прохождения проверки, перед разыменованием\. В результате всё равно может быть выброшен _NullReferenceException_\.
1. Можно было бы предположить, что оператор _lock_ может защитить объект от сборки мусора\. Однако после воспроизведения такого кейса выяснилось, что это не так\.



Так как же защититься в данном случае? Чтобы наверняка избежать исключения _NullReferenceException_, следует создать сильную ссылку на объект\. В таком случае сборщик мусора уже не сможет его удалить, пока ссылка остается актуальной\. Проще говоря, нужно создать простую локальную переменную, ссылающуюся на этот объект, и дальше работать уже с ней\. Таким образом, безопасная версия метода может выглядеть так:

```cpp

[RequiredByNativeCode]
internal static void InvalidateAll()
{
  lock (s_Instances)
  {
    foreach (var kvp in s_Instances)
    {
      WeakReference wr = kvp.Value;
      var target = wr.Target;

      If (target != null)
        (target as TextGenerator).Invalidate();     
     }
   }
}
```


</details>


## Заключение

Ну как, справились? Не устали? Что же, пришло время отдохнуть, ведь статья подходит к концу\. Надеюсь, она показалась вам не только полезной, но и увлекательной\.

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

Инструмент, с помощью которого были найдены потенциальные проблемы, описанные в статье, вы можете бесплатно попробовать самостоятельно, запросив триал на [сайте PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/try-free/)\.

Кстати, с помощью анализатора кода PVS\-Studio можно проанализировать не только сам Unity, но и игры, реализованные с помощью Unity\. Больше информации об этом вы сможете найти в [документации](https://pvs-studio.ru/ru/docs/manual/6607/)\.

До встречи в следующих статьях\!