Вызов действия С# меняет `это`

TL;DR

Если C# Action, ссылающийся на метод класса, по-прежнему имеет ссылку на исходный Target, то почему this изменяется, когда я вызываю Action и устанавливаю точки останова в этом методе?

Больше контекста

У меня есть следующая настройка в проекте .NET:


// MyClass.cs
public class MyClass
{
    private float _someValue = -1f;
    private int _instanceId = 12345;

    public void Method()
    {
        Updater.Instance.RegisterUpdate(_instanceId, doStuff);
    }
    private void doStuff(float deltaTime)
    {
        // Do stuff with _someValue
    }
}

// Updater.cs
public class Updater
{
    public static Updater Instance = new();

    private readonly List<Action<float>> _updateActions = new();

    public void RegisterUpdate(int instanceId, Action<float> updateAction)
    {
        // Validation...
        _updateActions.Add(updateAction);
    }
    public void Update()
    {
        foreach (Action<float> action in _updateActions)
            action.Invoke(Time.deltaTime);
    }
}

По сути, Updater оборачивает список зарегистрированных Action и вызывает их все при вызове Update.

В Visual Studio я могу установить точки останова в MyClass.doStuff(), и они срабатывают при вызове Updater.Update(). К сожалению, во время паузы в точке останова я не получаю никакого значения при наведении курсора на значение _someValue, и это поле даже не упоминается в окне «Локальные», а когда я ввожу _someValue в окне Immediate, я получаю «Идентификатор _someValue не в размахе». Кроме того, значение this в окне Locals имеет тип Updater, а не MyClass.

И тем не менее, все работает нормально. Условная логика внутри doStuff, которая работает с _someValue, по-прежнему работает, как и ожидалось, когда я выполняю шаг, я просто не могу проверять значения во время шага, что затрудняет отладку. Кроме того, когда я поднимаюсь по стеку вызовов к Updater.Update и проверяю текущий элемент _updateActions, я вижу, что его свойство Target правильно установлено на экземпляр MyClass. Так что же здесь происходит? Попытка понять, что такое this, кажется дерьмом JavaScript, а не C#.

Пока вы находитесь внутри функций класса, this указывает на окружающий класс функции. Когда вы вызываете Updater.update, то this имеет тип Updater. Пока внутри doStuff, this будет MyClass.

knittl 19.02.2023 19:00

@knittl Я тоже этого ожидал. Но я говорю, что, находясь внутри doStuff, this на самом деле относится к Updater

Rabadash8820 19.02.2023 19:07

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

NineBerry 19.02.2023 19:12

@NineBerry Я пытался указать MWE выше в своем вопросе; единственное, что я не учел, это код для вызова Updater.Update, но это может быть где угодно. Технически были некоторые синтаксические ошибки, но я их просто отредактировал. Возможно, стоит упомянуть, что этот код выполняется в скриптах Unity, поэтому в моем проекте метод Updater.Update на самом деле вызывается движком Unity, а не каким-либо моим кодом.

Rabadash8820 19.02.2023 19:28

Предоставленный код не компилируется, потому что все еще есть синтаксические ошибки, которые предполагают, что вы не скопировали его из тестового проекта, а придумали на ходу. Следуйте инструкциям по созданию минимального воспроизводимого примера.

NineBerry 19.02.2023 19:30

@NineBerry Вы правы, я придумал это на ходу; фактический код немного сложнее и зависит от движка Unity, поэтому вместо того, чтобы сокращать кучу кода, я просто создал этот MWE. Основная логика осталась прежней. Я только что отредактировал свой ответ, чтобы исправить оставшиеся ошибки компилятора. Единственное, чего сейчас не хватает, так это кода для вызова Updater.Update, который опять же может быть чем угодно (модульный тест, статический Main метод, сообщение Unity Update или что-то еще).

Rabadash8820 19.02.2023 19:38

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

NineBerry 19.02.2023 19:40

Опубликованный код не воспроизводит проблему imgur.com/a/FM4qub2

NineBerry 19.02.2023 19:47

@NineBerry При дальнейшем расследовании похоже, что проблема в том, что в моем реальном коде метод doStuff является локальным методом, определенным внутри Method. Когда я делаю doStuff полный метод (как в моем фрагменте выше), тогда this имеет правильный MyClass тип, как вы заметили. Кроме того, я могу воспроизвести это поведение только в компонентах Unity, а не в ванильных классах C#, так что это может быть результатом компилятора Mono, который Unity использует на сервере. Спасибо, что побудили меня копнуть глубже.

Rabadash8820 19.02.2023 20:41

FWIW, учитывая мою оценку репутации на этом сайте, я думаю, можно предположить, что я понимаю, что такое минимальный рабочий пример, и пытался его предоставить. Очевидно, в этом случае я плохо справился, но я быстро отредактировал свой ответ на основе приведенных выше комментариев и теперь занялся реальной проблемой. Продолжать отрицать мой вопрос без объяснения причин крайне грубо и обескураживающе.

Rabadash8820 19.02.2023 20:50
Запуск PHP на IIS без использования программы установки веб-платформы
Запуск PHP на IIS без использования программы установки веб-платформы
Установщик веб-платформы, предлагаемый компанией Microsoft, перестанет работать 31 декабря 2022 года. Его закрытие привело к тому, что мы не можем...
Оптимизация React Context шаг за шагом в 4 примерах
Оптимизация React Context шаг за шагом в 4 примерах
При использовании компонентов React в сочетании с Context вы можете оптимизировать рендеринг, обернув ваш компонент React в React.memo сразу после...
Библиотека для работы с мороженым
Библиотека для работы с мороженым
Лично я попрощался с операторами print() в python. Без шуток.
Настройка шаблона Metronic с помощью Webpack и Gulp
Настройка шаблона Metronic с помощью Webpack и Gulp
Я пишу эту статью, чтобы поделиться тем, как настроить макет Metronic с помощью Sass, поскольку Metronic предоставляет так много документации, и они...
Уроки CSS 6
Уроки CSS 6
Здравствуйте дорогие читатели, я Ферди Сефа Дюзгюн, сегодня мы продолжим с вами уроки css. Сегодня мы снова продолжим с так называемых классов.
Что такое Css? Для чего он используется?
Что такое Css? Для чего он используется?
CSS, или "Каскадные таблицы стилей", - это язык стилей, используемый в веб-страницах. CSS является одним из основных инструментов веб-разработки...
2
10
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Оказывается, проблема заключалась в том, что в моем реальном коде не было отражено в минимальном рабочем примере в моем вопросе. То есть в моем реальном коде doStuff была локальной функцией внутри Method. Если doStuff — это отдельный метод, как в моем вопросе, то this относится к правильному типу MyClass. Как ни странно, я могу воспроизвести this-изменяющееся поведение локальных функций только в компонентах Unity, а не в ванильных классах C#, так что это может быть результатом компилятора Mono, который Unity использует на бэкэнде.

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

Другие вопросы по теме