Разница в поведении во время выполнения между локальной переменной и полем класса

#c# #.net #heap-memory #stack-memory

Вопрос:

Я пытаюсь написать рекурсивный метод для определения числа всех возможных способов перемещения от верхнего левого до нижнего правого угла сетки m на n (без движения вверх).

Ключ переменной должен содержать текущие значения m и n, но если я попытаюсь использовать поле класса, эта переменная будет вести себя так, как будто она отстает на один шаг или что-то в этом роде.

Следующий код завершится необработанным исключением:

Элемент с тем же ключом уже добавлен. Ключ: 1,1

Это очень похоже, скажем, когда ваши начальные m и n равны 3 и 2 соответственно, функция (1, 2) попытается добавить свое значение в словарь с помощью ключа «1,1».:

 class Finder
{
    public Finder()
    {
        memo = new(new List<KeyValuePair<string, ulong>>
        { new KeyValuePair<string, ulong>("1,1", 1) });
    }

    private Dictionary<string, ulong> memo;
    private string key;
    private ulong value;

    public ulong FindPaths(int m, int n)
    {
        key = String.Format("{0},{1}", (m < n ? m : n), (m > n ? m : n));

        if (memo.ContainsKey(key))
        {
            return memo[key];
        }

        if (m == 0 || n == 0)
        {
            return 0;
        }

        value = FindPaths(m - 1, n)   FindPaths(m, n - 1);
        memo.Add(key, value);
        return value;
    }
}
 

И когда я изменяю предварительно вычисленный ключ в Add методе на то же выражение, которое вычисляет его в первую очередь, проблема исчезает:

 memo.Add(String.Format("{0},{1}", (m < n ? m : n), (m > n ? m : n)), value);
 

И теперь, если мы заменим поле класса локальной переменной, все будет работать так, как должно:

 class Finder
{
    public Finder()
    {
        memo = new(new List<KeyValuePair<string, ulong>>
        { new KeyValuePair<string, ulong>("1,1", 1) });
    }

    private Dictionary<string, ulong> memo;
    private ulong value;

    public ulong FindPaths(int m, int n)
    {
        string key;
        key = String.Format("{0},{1}", (m < n ? m : n), (m > n ? m : n));
        
        if (memo.ContainsKey(key))
        {
            return memo[key];
        }

        if (m == 0 || n == 0)
        {
            return 0;
        }

        value = FindPaths(m - 1, n)   FindPaths(m, n - 1);
        memo.Add(key, value);

        return value;
    }
}
 

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

Пожалуйста, помогите мне прийти к более глубокому пониманию этой темы.

Комментарии:

1. Рекурсия с полем изменяет значение ключа , искажая заметку. Вызов Add(ключ, значение). Обязательно протестируйте на ранней стадии с небольшими случаями, чтобы вы могли видеть, что это идет не так при однократном выполнении с отладчиком.

Ответ №1:

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

если переменная является локальной в вашей функции, то при каждом вызове функции это новое хранилище и новое содержимое переменной.

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