【问题标题】:Recursion, can't make nested self object null, why is it so?递归,不能让嵌套的self对象为null,为什么会这样?
【发布时间】:2021-05-02 12:00:27
【问题描述】:

我正在尝试更新递归代码中的 self 对象列表,但它没有这样做,但是通过更改值来更新对象时工作正常!

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            Node main = new Node();
            main.Sequence = "1";
            main.Id = 1;

            main.ChildNodes.Add(new Node()
            {
                Sequence = "1.1",
                Id = 2
            });
            main.ChildNodes.Add(new Node()
            {
                Sequence = "1.2",
                Id = 3
            });
            main.ChildNodes.Add(new Node()
            {
                Sequence = "1.3",
                Id = 4
            });
            Console.WriteLine("Before :");
            PrintNode(main);
            Console.WriteLine();
            MakeNull(main);

            Console.WriteLine("After :");
            PrintNode(main);

            void MakeNull(Node node)
            {
                foreach (var Child in node.ChildNodes)
                {
                    MakeNull(Child);
                }
                node.ChildNodes.RemoveAll(p => p == null);
                if (node.ChildNodes.Count == 0)
                {
                    node.Id = 5;//this is working 
                    node = null;//this is not, why ?
                }
            }

            Console.ReadLine();

        }
        static void PrintNode(Node node)
        {
            Console.WriteLine("Id:" + node.Id + " Sequence:" + node.Sequence);
            for (int i = 0; i < node.ChildNodes.Count; i++)
            {
                PrintNode(node.ChildNodes[i]);
            }
        }


    }
    public class Node
    {
        public int Id { get; set; }
        public string Sequence { get; set; }
        public List<Node> ChildNodes { get; set; }
        public Node()
        {
            ChildNodes = new List<Node>();
        }

    }
}

为什么不让整个对象为空?

【问题讨论】:

  • 如果您随后将该对象设置为空,那么设置对象属性的意义何在??
  • 你想创建一个IDisposable-like 对象吗?
  • 将变量设置为 null 只会影响该局部变量(在这种情况下是一个参数)。它对该功能的外部完全没有任何作用。也许您想查看ref 参数?
  • @Liam 设置属性仅用于演示目的,实际上我需要将其设为 null 但不能
  • @Alejandro 虽然我正在更新它的属性,但它工作正常,但是当它变为 null 时它不会工作,为什么会这样?

标签: c# .net recursion


【解决方案1】:

要了解发生了什么,了解类是引用类型至关重要。即,这种类型的变量包含null 或对对象的引用。它们不包含对象本身或存储在该对象中的值。

这与int 之类的值类型不同。这种类型的变量实际上包含它所代表的数字。


void MakeNull(Node node) 中,参数node 保存传递给它的引用的副本(因为它是按值传递的,即没有refinout 关键字)。您只是将此副本设置为null。如果要将调用者的变量设置为null,则必须通过引用传递:

void MakeNull(ref Node node)
{
    ...
    node = null;
    ...
}

调用它

MakeNull(ref main);

MakeNull(ref Child);

现在,node 不是调用者变量的副本,而是它的别名。例如,当你打电话时

MakeNull(ref main);

...node 只是main 的另一个名称。设置node = null; 现在可以有效地设置main = null;

那为什么它对node.Id = 5; 有效呢?因为node 持有对Node 对象的引用,因此,该对象的Id 被更改。请注意,在您的实现中,您拥有对 nodeNode 对象的引用的副本,而不是对象本身的副本。

另请参阅我对这个 SO 问题的回答:Setting a type reference type to null doesn't affect copied type?

【讨论】:

    【解决方案2】:

    当您将函数参数声明为引用类型时,该值将通过引用传递给函数。这意味着当您将方法参数声明为Node 类型时,您将获得一个指向与您传递给该方法的内存位置相同的指针的副本,您实际上并没有传递对象本身。

            void MakeNull(Node node) // <-- This is a new variable that points to the same memory location as the variable that was passed in
            {
                foreach (var Child in node.ChildNodes)
                {
                    MakeNull(Child);
                }
                node.ChildNodes.RemoveAll(p => p == null);
                if (node.ChildNodes.Count == 0)
                {
                    node.Id = 5;
                    node = null;
                }
            }
    

    变量node 的范围仅限于方法,因为方法参数是声明它的位置。当您在方法内将其设置为null 时,您只是在销毁指向该方法insidenode 内存位置的指针,而是您传入的那个 该方法保持不变,因为它在您调用该方法时已被复制。

    为什么不让整个对象为空?

    简而言之,因为 object 不能设置为 null。只有对象指针(变量)可以。您正在处理 2 个 Node 类型的独立对象指针,一个在方法内部,一个在方法外部。

    将方法内部的node 变量设置为null 只会破坏您在方法开头创建的指针。当您引用内存位置并将其 Id 属性更新为 5 时,您使用的是对象本身,而不是对象指针。

    【讨论】:

      【解决方案3】:

      因为在您的方法 MakeNull(Node node) 中,节点成为该范围内的引用,其中包含来自调用函数的“主”对象。在函数中将节点设置为 null 只会产生在该方法中将节点引用设置为 null 的效果。对“main”的引用丢失(被 null 覆盖)。

      这并不意味着 MakeNull(Node node) 方法之外的“main”变为空。

      设置 ID 确实有效,因为您在使用 null 覆盖节点引用之前设置了 ID。此时node仍然引用'main',我们可以得到对Id的setter的引用,然后设置它的值。

      如果你想使列表中的项目为空,你必须这样做:main[i] = null。

      其他人指出使用 ref 关键字应该可以解决问题,但这仅适用于 MakeNull(main)。不适用于子项。通过您正在执行的操作,您可能希望看到一个“空”子项列表(例如,调用 ChildItem[1] 然后希望看到“空”)。但事实并非如此。

      请参阅下面的代码 sn-p 以了解将会发生的情况:

                  List<Bla> a = new List<Bla>();
      
                  a.Add(new Bla());
                  a.Add(new Bla());
                  a.Add(new Bla());
      
                  void MakeNull(ref Bla a) { a = null; }
      
                  var a1 = a[1];
      
                  MakeNull(ref a1);
                  // MakeNull(ref a[1]);              // not allowed by compiler
                  // MakeNull(ref a.ElementAt(1));    // not allowed by compiler
      
                  Console.WriteLine(a1); // prints nothing (null)
                  Console.WriteLine(a[1]); // prints "Core.App.Bla"
      
      

      您可以通过使用引用实际索引器方法的 lambda 操作或集合上的另一个设置器方法来解决此问题:

                  void MakeNullAction(Action<Bla> setter) { setter(null); }
      
                  MakeNullAction((value) => a[1] = value);
      
                  Console.WriteLine(a[1]); // prints nothing (null)
      
      

      【讨论】:

      • 使 null 是 MakeNull() 函数的工作,并且在此节点中引用主对象.. 这是工作 node.Id = 5;但这不是 node = null;为什么会这样?
      • 我编辑了我的帖子以更简要地解释它
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-13
      • 1970-01-01
      • 2012-01-11
      • 1970-01-01
      • 2012-02-12
      • 2022-01-13
      • 2012-08-02
      相关资源
      最近更新 更多