Почему предикат неверно интерпретирует значения параметров при вызове в рекурсивной функции

#c# #linq

#c# #linq

Вопрос:

Я пытаюсь создать расширенный элемент управления treeview, наследующий от существующего элемента управления winform TreeView. Создал Load() функцию в классе TreeViewEx. В этой функции источник данных зацикливается в foreach. Затем этот foreach вызывает Where() метод расширения в циклическом источнике данных, передавая ему метод (который принимает в качестве параметра текущий элемент), возвращающий предикат. Этот предикат неверно интерпретирует переданное ему значение параметра. Похоже, что он использует предыдущие значения параметров.

значение аргумента в методе перед возвратом предиката

значение аргумента в методе перед возвратом предиката

значение аргумента, когда отладчик вводит предикат

значение аргумента, когда отладчик вводит предикат

Изначально я думал, что такое поведение связано с тем, что я перебираю Enumerable не список, поэтому я меняю разные перечисляемые на список, но ничего не изменилось. Также пытался установить возвращаемый предикат, но ничего.

Функция загрузки :

 
public Func<T, Func<T, bool>> GetChildrenPredicate { get; set; }
.
.
.
public virtual void Load(List<T> dataSource = null)
{
    try
    {
        if (CreateNode == null)
        {
            OnError?.Invoke(this, new ArgumentNullException("CreateNode"));
            return;
        }
        if (GetParentKey == null)
        {
            OnError?.Invoke(this, new ArgumentNullException("GetParentKey"));
            return;
        }
        if (GetChildrenPredicate == null)
        {
            OnError?.Invoke(this, new ArgumentNullException("GetChildrenPredicate"));
            return;
        }

        var finalDataSource = dataSource ?? DataSource;

        TreeNode node = null;
        BeginUpdate();
        foreach (var item in finalDataSource)
        {
            node = CreateNode(item);
            node.Tag = item;

            if (this.Nodes.Find(node.Name, true).Count() == 0)
            {
                var n = this.Nodes.Find(this.GetParentKey(item), true).FirstOrDefault() as TreeNode;

                if (n == null)
                {
                    this.Nodes.Add(node);
                }
                else
                {
                    n.Nodes.Add(node);
                }

                List<T> children = finalDataSource
                                  .ToList()                                   
                                  .Where(this.GetChildrenPredicate(item))
                                  .ToList(); //this.GetChildrenPredicate is
                                //the property func generating the 
                                //predicate set by a different class

                if (children.Count() > 0)
                {
                    // Recursively call this function for all childRows
                    Load(children);
                }

            }
        }
        EndUpdate();
    }
    catch (Exception ex)
    {
        OnError?.Invoke(this, ex);
    }
}

  

GetChildrenPredicate :

 private Func<ORM.DataModels.Menu, bool> GetChildrenPredicate(ORM.DataModels.Menu arg)
{

    return (ORM.DataModels.Menu m) =>
    (m.Lepere == arg.Codmen) ||
    (m.Lepere == null amp;amp; arg.Codmen == "_"   m.Niveau);
}
  

Ответ №1:

Вы устанавливаете точку останова в строке, которую вы проверяете. Это означает, что этот код еще не был выполнен. Я мог бы отобразить значения предыдущего вызова, кэшированные отладчиком.

Если вы хотите проверить результат возвращаемого выражения, сначала назначьте его временной переменной.

 Func<ORM.DataModels.Menu, bool> predicate =  (ORM.DataModels.Menu m) =>
    (m.Lepere == arg.Codmen) ||
    (m.Lepere == null amp;amp; arg.Codmen == "_"   m.Niveau);
return predicate; // <== Set breakpoint here
  

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

 string codmen = arg.Codmen;
return (ORM.DataModels.Menu m) =>
    (m.Lepere == codmen) ||
    (m.Lepere == null amp;amp; codmen == "_"   m.Niveau);
  

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

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

2. Что вы подразумеваете под «не дает мне никакого результата» ? Обратите внимание, что сам предикат здесь не выполняется, но выражение выполняется для создания предиката. Этот предикат фиксирует значение arg.Codmen и использует его при последующем вызове предиката. То, что возвращает ваш метод, — это не bool а Func<TArg, TReturn> .

3. Извините, но, похоже, вы меня не понимаете. Пожалуйста, проверьте функцию Load() . Именно здесь вызывается метод GetChildrenPredicate , и его результат напрямую используется методом расширения Where() . Возвращаемый при вызове предикат возвращает bool. Когда точка останова вставляется внутри этого предиката, значение аргумента. Codmen используется не ожидаемое захваченное значение предиката

4. спасибо за обновление вашего ответа. Но, к сожалению, ничего не изменилось

5. Что произойдет, если вы напишете, var temp = item; List<T> children = finalDataSource .ToList() .Where(this.GetChildrenPredicate(temp)) .ToList(); используя другой темп?

Ответ №2:

ОК. Я нашел решение. На самом деле я не понял, что finalDataSource было переопределено при каждом вызове Load() . Я был сосредоточен только на странном поведении предиката. просто пришлось использовать глобальное свойство источника данных, определенное в классе.

 List<T> children = this.DataSource.Where(this.GetChildrenPredicate(item)); //<= changed local variable finalDataSource to the defined property this.DataSource