如何更新按值传递的引用类型的引用

How to update reference of reference-type passed by value

提问人:Nick W. 提问时间:5/9/2019 更新时间:5/9/2019 访问量:1198

问:

我正在使用一个按值在类之间传递对象的框架,我需要更新对其中一个对象的引用。我可以很好地更改原始对象的属性值,但我似乎无法弄清楚如何更改对全新对象的引用。在后一个类中,我从 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();
    }
C# 变量赋 值传递 引用

评论

0赞 Fleshy 5/9/2019
您需要克隆对象,否则您仍然指向相同的内存地址。实现 IClonable。
0赞 Nick W. 5/9/2019
@Fleshy,我希望更新原始对象的引用,我不认为克隆是解决方案。
0赞 Fleshy 5/9/2019
我误解了这个问题。
0赞 Eric Lippert 5/10/2019
我认为您对 C# 中的含义有误解;这是令人困惑的。它与对象没有任何关系。 在 C# 中更好地理解为“变量的别名”。当你有一个函数并调用它时,这意味着 和 都是同一个变量的名称;它们是彼此的别名。既然您知道了“别名变量”的含义,那么您的程序的含义是否更清楚?refrefFoo(ref T y)Foo(ref x)xyref
0赞 Eric Lippert 5/10/2019
特别是,ur 的行为与 没有区别,因为 从不写入 ref'd 变量。如果从未写入变量,则传递变量的值和创建变量的别名在逻辑上是一回事。确保你明白这一点。SwapPersonTwoSwapPersonOne

答:

2赞 Scott Hannen 5/9/2019 #1

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

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

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

oldPerson并通过引用传递给本地函数,但通过值传递。_newPersonnewPersonSwapPersonFour

另外,只有更新,所以现在两者都引用相同的.oldPersonoldPerson_newPersonPerson

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

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将该字段替换为对 的引用。它实际上并没有交换任何东西,因为当它更新时,它并没有更新。完成后,它们都是对同一 .(你是想交换它们吗?这可能是问题所在。_personnewPerson_personnewPersonPerson

就其价值而言,我会删除本地函数,因为它实际上没有任何作用。它只接受该方法执行的一件事,并将其嵌套在一个额外的函数中。你可以用它替换它并得到相同的结果 - 它只是更容易阅读。(事实上,额外的代码可能更容易错过任何交换。我不了解你,但不需要太多让我感到困惑。

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

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

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

评论

0赞 Nick W. 5/9/2019
在外部函数中使用仍然有相同的结果: public void SwapPersonFive(ref Person newPerson) { LocalSwap(ref person, ref newPerson); void LocalSwap(ref Person oldPerson, ref Person _newPerson) { oldPerson = _newPerson;ref
0赞 Scott Hannen 5/10/2019
另一个小细节:在字段前面加上 ,而不是变量或方法参数,这是一种常见的约定。这样,当有人看到时,他们就知道这是一个字段,而不是一个局部变量。它很小,但随着时间的推移,我们依靠这样的小东西来帮助我们阅读代码。我建议尝试免费的 Resharper 演示。即使你不买它,在几周内,它也会指出这些事情并提出更正建议,几次后,惯例就变成了习惯。_name_name
0赞 Nick W. 5/10/2019
Swap 对于方法名称来说是一个糟糕的选择——我实际上希望替换引用的对象并丢弃以前的值。我相信您的示例按预期工作,因为它是类的一个字段,并且该类中的方法引用了它。我可能在描述场景方面做得很差,我的示例代码也不是那么好。我有一个对象通过值从类 A 传递到类 B(我无法控制这一点)。然后,在类 B 中,我需要替换传入的对象,并将该更改反映在类 A 中。_personUnitTest1