Nick W.
Nick W.

Reputation: 191

How to update reference of reference-type passed by value

I'm working with a framework that passes objects between classes by value and I need to update the reference to one of those objects. I can change property values of the original object just fine but I can't seem to figure out how to change the reference to an entirely new object. In one of the latter classes I'm retrieving a rather complex object from an API, hence my desire to just update a reference rather than attempt a deep copy.

I've tried calling SwapPerson{One,Two,Three,Four} in my code sample with no hint of success. The output is always:

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

I'm hoping there's a simple solution that I'm overlooking due to the late hour, so any input is greatly appreciated.

    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();
    }

Upvotes: 0

Views: 2142

Answers (1)

Scott Hannen
Scott Hannen

Reputation: 29207

You're using the ref keyword within the inner local function, but not in the outer function. Also, if the intention is actually to swap the references, the method doesn't do that as-is.

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

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

oldPerson and _newPerson are passed to the local function by reference, but newPerson is passed to SwapPersonFour by value.

Also, only oldPerson is updated, so now both oldPerson and _newPerson refer to the same Person.

If you want to update the reference passed to SwapPersonFour you must also pass that argument by reference using the ref keyword.

public void SwapPersonFour(ref Person newPerson)

The comment mentioned that it didn't work, so I put together a hasty unit test to see if I was missing something. (I miss stuff all the time, which is why I write unit tests.)

[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 replaces the _person field with a reference to newPerson. It's not actually swapping anything because while it's updating _person it's not updating newPerson. When it's done they are both references to the same Person. (Did you mean to swap them? That could be the problem.)

For what it's worth, I would remove the local function because it doesn't actually do anything. It just takes the one thing that the method does and nests it in an extra function. You could replace it with this and get the same result - it's just easier to read. (In fact, the extra code might have made it easier to miss that nothing was getting swapped. I don't know about you, but it doesn't take much to confuse me.)

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

If you actually want to swap them then you could do this:

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

Upvotes: 2

Related Questions