Появился ооочень странный наполовину плавающий баг с мусорными строками.
Всю архитектуру описывать очень долго, поэтому напишу вкратце, но если будет надо, обновлю вопрос с детальным описанием.
В кратце так:
создал систему для локализации игры. Есть класс Localization со статическими строковыми полями только для чтения, и есть файл xml со строками. Данный клас, с помощью стандартного XmlTextReader и рефлексии, заполняет нужные поля соответствующими значениями.
using System;
using System.Reflection;
using UnityEngine;
using System.Xml;
using System.IO;
public sealed class Localization
{
const string PATH_TO_LOCALIZATION_FILES = @"TextAssets\Localization\";
public static SystemLanguage CurrentLanguage { get; private set; }
#region strings;
static public readonly string downloading;
static public readonly string play_game;
static public readonly string settings;
static public readonly string squad;
//тут еще куча static public readonly string полей
#endregion;
///
var res = true;
if (newLanguage == CurrentLanguage)
res = false;
else
CurrentLanguage = newLanguage;
return res;
}
///
switch (language)
{
case SystemLanguage.Russian:
languageFileName = "RU";
newLanguage = SystemLanguage.Russian;
break;
default:
//english
languageFileName = "EN";
newLanguage = SystemLanguage.English;
break;
}
TextAsset localisationFile = Resources.Load
SetString(name, value);
}
}
}
}
///
if (field == null)
throw new Exception(string.Format("Поля строкового ресурса с именем \"{0}\" не существует.", name));
field.SetValue(null, value);
}
///
if (field == null)
throw new Exception(string.Format("Поля строкового ресурса с именем \"{0}\" не существует.", name));
return field.GetValue(null) as string;
}
}
Когда будет нужно, обращаемся к нужному полю класса и берем нужное значение. Если же строку нужно задать не с кода, а с редактора Unity, то передаем нужное имя поля, а в коде вызываем метод Localozation.GetString(string name)
Теперь самое интересное
При первом запуске уровня все работает нормально. Но при перезапуске уровня случается вот такая дичь:
Запускаем отладку и видим, что с классом локализации и значениями полей все нормально:
Пропускаем шаг отладки и видим, что, на самом деле, было передано "мусорное значение" строки.
Причем, это случается не просто при перезапуске уровня, но именно при перезапуске уровня, если на этот предмет уже кликали перед перезапуском уровня.
Дальше больше
Если вытаскивать значение полей с помощью рефлексии, то все нормально. А если обращаться к полям напрямую (через имя класса и имя поля), то там оказываются мусорные значения.
using System.Text;
using UnityEngine;
public class TestGarbageStrings : MonoBehaviour
{
public void DoTestFields()
{
StringBuilder builder = new StringBuilder();
builder.Append("
" + Localization.downloading);
builder.Append("
" + Localization.play_game);
builder.Append("
" + Localization.settings);
builder.Append("
" + Localization.squad);
//тут еще куча обращений к другим полям
DialogBox.Instance
.AddCancelButton("Ясно")
.SetText(builder.ToString(), true)
.SetSize(800, 600)
.Show();
}
public void DoTestReflection()
{
StringBuilder builder = new StringBuilder();
var fields = typeof(Localization).GetFields(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
foreach (var f in fields)
{
builder.Append("
");
builder.Append(f.GetValue(null));
}
DialogBox.Instance
.AddCancelButton("Ясно")
.SetText(builder.ToString(), true)
.SetSize(800, 600)
.Show();
}
}
Если обращаться к полям на прямую(мусорные значения):
Если брать значения с помощью рефлексии(правильные значения):
Подскажите пожалуйста, в чем может быть проблема. Хотя бы малейшие предположения. Возможно, кто то сталкивался с подобным (но такого и врагу не пожелаешь).
Ответ
Модификатор readonly подразумевает, что поле не будет меняться после создания объекта (или после вызова статического конструктора).
В связке со static это предположение разрешает JIT-у кэшировать значения в тех местах, где вы обращаетесь к полю. Или вообще кэшировать значения полей где-то глобально после первого обращения к ним. И даже убирать из кода if-ы, проверяющие значения полей - вобщем, JIT считает такие поля константами времени выполнения.
При просмотре через отладчик вы видите реальное значение поля. При вытягивании через reflection - тоже. А реально выполняется код, в который JIT вписал константный старый адрес. Потому что вы ему это явно разрешили.
Соответствующие оптимизации для static readonly были в основном CLR со времен 3.5, в Core - появились 2 года назад - Treat readonly fields as JIT time constants. В mono (не уверен, на чем сейчас Unity работает) они скорее всего тоже есть.
Уберите readonly и все заработает. Скорее всего, т.к. минимального кода для воспроизведения проблемы в вопросе нет.
Комментариев нет:
Отправить комментарий