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

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 в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
10
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

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

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

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