【问题标题】:How to update reference of reference-type passed by value如何更新按值传递的引用类型的引用
【发布时间】:2019-09-27 04:33:00
【问题描述】:

我正在使用一个按值在类之间传递对象的框架,我需要更新对其中一个对象的引用。我可以很好地更改原始对象的属性值,但我似乎无法弄清楚如何更改对全新对象的引用。在后面的一个类中,我从 API 中检索一个相当复杂的对象,因此我希望只更新引用而不是尝试深度复制。

我尝试在我的代码示例中调用SwapPerson{One,Two,Three,Four},但没有任何成功的迹象。输出总是:

Main.person: Groucho Marx is 128 years old!
Main.person: Groucho Marx is 129 years old!
Main.person: Groucho Marx is 129 years old!

我希望有一个简单的解决方案,由于时间较晚,我忽略了,因此非常感谢任何输入。

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
        public override string ToString()
        {
            return $"{FirstName} {LastName} is {Age} years old!";
        }
    }
    public class Foo
    {
        private Person person;

        public Foo(Person person)
        {
            this.person = person;
        }

        public void SetAge(int age)
        {
            person.Age = age;
        }

        public void SwapPersonOne(Person newPerson)
        {
            person = newPerson;
        }

        public void SwapPersonTwo(ref Person newPerson)
        {
            person = newPerson;
        }

        public void SwapPersonThree(Person newPerson)
        {
            LocalSwap(ref person);

            void LocalSwap(ref Person oldPerson)
            {
                oldPerson = newPerson;
            }
        }

        public void SwapPersonFour(Person newPerson)
        {
            LocalSwap(ref person, ref newPerson);

            void LocalSwap(ref Person oldPerson, ref Person _newPerson)
            {
                oldPerson = _newPerson;
            }
        }
    }
    static void Main(string[] args)
    {
        Person person = new Person { FirstName = "Groucho", LastName = "Marx", Age = 128 };

        Console.WriteLine($"{nameof(Main)}.{nameof(person)}: {person}");

        var foo = new Foo(person);

        foo.SetAge(129);

        Console.WriteLine($"{nameof(Main)}.{nameof(person)}: {person}");

        var charlie = new Person { FirstName = "Charlie", LastName = "Chaplin", Age = 130 };

        //foo.SwapPersonOne(charlie);
        //foo.SwapPersonTwo(ref charlie);
        //foo.SwapPersonThree(charlie);
        foo.SwapPersonFour(charlie);

        Console.WriteLine($"{nameof(Main)}.{nameof(person)}: {person}");

        Console.ReadLine();
    }

【问题讨论】:

  • 你需要克隆对象,否则你仍然指向同一个内存地址。实现 IClonable。
  • @Fleshy,我正在寻找更新原始对象的引用,我不认为克隆是解决方案。
  • 我误解了这个问题。
  • 我认为你对ref在C#中的含义有误解;这令人困惑。它与对对象的引用 没有任何关系。 ref 在 C# 中被更好地理解为“变量的别名”。当您有一个函数Foo(ref T y) 并调用它Foo(ref x) 时,这意味着xy同一个变量的两个名称;它们是彼此的别名。既然知道ref的意思是“给变量起别名”,是不是你程序的意思就更清楚了?
  • 特别是,您的SwapPersonTwoSwapPersonOne 的行为无法区分,因为您从不写入引用变量。如果变量从不写入,则传递变量的值和创建变量的别名在逻辑上是相同的。确保你明白这一点。

标签: c# pass-by-reference variable-assignment pass-by-value ref


【解决方案1】:

您在内部本地函数中使用ref 关键字,但不在外部函数中。此外,如果意图实际上是交换引用,则该方法不会按原样执行此操作。

    public void SwapPersonFour(Person newPerson)
    {
        LocalSwap(ref person, ref newPerson);

        void LocalSwap(ref Person oldPerson, ref Person _newPerson)
        {
            oldPerson = _newPerson;
        }
    }

oldPerson_newPerson 通过引用传递给本地函数,但 newPerson 通过值传递给 SwapPersonFour

另外,只有oldPerson 被更新,所以现在oldPerson_newPerson 指的是同一个Person

如果要更新传递给 SwapPersonFour 的引用,还必须使用 ref 关键字通过引用传递该参数。

public void SwapPersonFour(ref Person newPerson)

评论提到它不起作用,所以我匆忙进行了单元测试,看看我是否遗漏了什么。 (我总是想念一些东西,这就是我编写单元测试的原因。)

[TestClass]
public class UnitTest1
{
    private Person _person;

    [TestMethod]
    public void TestSwappingPerson()
    {
        _person = new Person { FirstName = "Scott" };
        var newPerson = new Person() { FirstName = "Bob" };
        SwapPersonFour(ref newPerson);
        Assert.AreEqual("Bob", _person.FirstName);
    }

    public void SwapPersonFour(ref Person newPerson)
    {
        LocalSwap(ref _person, ref newPerson);

        void LocalSwap(ref Person oldPerson, ref Person localNewPerson)
        {
            oldPerson = localNewPerson;
        }
    }
}

SwapPersonFour_person 字段替换为对newPerson 的引用。它实际上并没有交换任何东西,因为它在更新_person 时并没有更新newPerson。完成后,它们都是对同一个Person 的引用。 (您的意思是交换它们吗?这可能是问题所在。)

对于它的价值,我会删除本地函数,因为它实际上并没有做任何事情。它只接受该方法所做的一件事并将其嵌套在一个额外的函数中。您可以用它替换它并获得相同的结果 - 它更易于阅读。 (事实上​​,额外的代码可能更容易错过没有被交换的内容。我不了解你,但不会让我感到困惑。)

public void SwapPersonFour(ref Person newPerson)
{
    _person = newPerson;
}

如果你真的想交换它们,那么你可以这样做:

public void SwapPersonFour(ref Person newPerson)
{
    var temp = _person;
    _person = newPerson;
    newPerson = temp;
}

【讨论】:

  • 在外部函数中使用ref仍然有相同的结果: public void SwapPersonFive(ref Person newPerson) { LocalSwap(ref person, ref newPerson);无效LocalSwap(参考人oldPerson,参考人_newPerson){oldPerson = _newPerson; } }
  • 另一个小细节:在字段前加上_name 是一个常见的约定,但不是变量或方法参数。这样当有人看到_name 时,他们就知道这是一个字段,而不是局部变量。它很小,但随着时间的推移,我们依靠类似的小东西来帮助我们阅读代码。我建议尝试免费的 Resharper 演示。即使你不买它,它也会在几周内指出这些事情并提出更正建议,几次之后,这种惯例就会成为习惯。
  • Swap 对于方法名称来说是一个糟糕的选择——我实际上是在寻找替换引用的对象并丢弃以前的值。我相信您的示例按预期工作,因为 _personUnitTest1 类的一个字段,并且它被该类中的方法引用。我可能在描述场景方面做得很差,而且我的示例代码也不是很好。我有一个对象按值从 A 类传递到 B 类(我无法控制它)。然后,在 B 类中,我需要替换传入的对象并将该更改反映在 A 类中。
猜你喜欢
  • 1970-01-01
  • 2023-04-09
  • 1970-01-01
  • 2011-05-08
  • 2013-04-12
  • 2012-11-16
  • 2014-03-03
  • 2013-06-26
  • 2012-01-16
相关资源
最近更新 更多