jbrodriguez
jbrodriguez

Reputation: 41

C# Reference parameter passing

i have a piece of code that isn't working, i'd appreciate any help you guys can provide me

the code below is generating an exception ... but i'd think it shouldn't, unless i'm misinterpreting the ref semantics.

EDIT: thanks for all the answer ... i know i'm instatiating a new Queue object in the One.Produce method ... but this is what i actually want to do, i would like for Main._queue to hold a reference to One._queue. Is that possible at all ?

using System;
using System.Collections.Generic;

namespace ConsoleApplication2
{
    class One
    {
        Queue<string> _queue;

        public One(ref Queue<string> queue)
        {
            // should be assigning by reference
            _queue = queue;
        }

        public void Produce()
        {
            _queue = new Queue<string>();
            _queue.Enqueue("one");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Queue<string> _queue = new Queue<string>();

            One one = new One(ref _queue);
            one.Produce();

            // this generates an exception, since _queue is empty
            // i'd have thought _queue would have one item, "one"
            string value = _queue.Dequeue();
        }
    }
}

Upvotes: 4

Views: 4503

Answers (8)

Grungondola
Grungondola

Reputation: 678

Why don't you simply do the following:

public void Produce()
{
    _queue.Clear();
    _queue.Enqueue("one");
}

Upvotes: 0

jpbochi
jpbochi

Reputation: 4406

The Problem

The problem is that the ref keyword will only affect the method it was declared. So, you would be able to change Main's queue in One's constructor, but not in the Produce method.

In your code, One is creating a second reference to the same queue object. The Produce method is simply changing this second reference.

In other words, when you make a call to a method passing a parameter by reference, the called method will be able to change the referenced object, but only at this call site. After the call returns, whatever object is referenced by the _queue variable at the Main method will remain there.

One other consideration. The assignment at One's constructor (where you commented "should be assigning by reference") works in exactly the same way as it would work if the ref keyword wasn't there. Only if the assignment was inverted to queue = _queue; then the ref keyword would make a difference.


Solution A - Holder<T> class

One solution that might make sense would be to create a Holder class like this:

public class Holder<T> {
    public T { get; set; }
}

Then, the One class would work on a Holder<Queue<string>> object. The Main method would have to work on Holder<Queue<string>> too. This way, One could change the queue instance and it would be reflected in Main.

Ok, ok. I know this solution is very awkward, but the requirements for your question seems awkward to me too. Is it a rhetorical question? Or are you trying to solve a bigger problem here? If it's the latter, I'm curious to know what is this bigger problem.


Solution B - pointers

Another solution would be to use pointers. It's perfectly possible to be done in C#, but not at all recommended. If you are interested, I can try to write a solution using pointers.


Solution C - Single position array

Your own answer gave me the idea for a third possible solution. It's intrinsically very similar to the other two solutions. Instead of explaining it in English, let me show it in C#.

class One
{
    Queue<string>[] queueArray;

    public One(Queue<string>[] queueArray)
    {
        if (queueArray == null) throw new ArgumentNullException("queueArray");
        if (queueArray.Length != 1) throw new ArgumentException("queueArray must have one and only one item");
        this.queueArray = queueArray;
    }

    public void Produce()
    {
        queueArray[0] = new Queue<string>();
        queueArray[0].Enqueue("one");
    }
}

class Program
{
    static void Main(string[] args)
    {
        var queueArray = new Queue<string>[] { new Queue<string>() };

        One one = new One(queueArray);
        one.Produce();

        string value = queueArray[0].Dequeue(); //this line sets "one" to value
    }
}

Upvotes: 0

jason
jason

Reputation: 241779

The problem is that in the function Produce you are instantiating a new Queue and assigning this to the private member One._queue; this overwrites that assignment that you made to One._queue in the constructor. However, the local _queue refers to a different Queue<string> on which you never enqueued.

You can fix this by removing your first line of Produce to obtain the following:

public void Produce() {
        _queue.Enqueue("one");
}

By the way, you don't need to pass _queue by reference into the constructor to achieve what you are trying to achieve. That is, the following will work too

public One(Queue<string> queue) {
    _queue = queue;
}

Passing by reference would be used when you want the local _queue in main to refer to a different instance of Queue<string> after the One constructor executes.

Upvotes: 3

Andrew Hare
Andrew Hare

Reputation: 351698

Remove this line from your Produce method:

_queue = new Queue<string>();

This is wiping out the reference to the existing Queue<String> that you passing in by reference with a new reference to a new Queue<String>.

Upvotes: 0

Rubens Farias
Rubens Farias

Reputation: 57996

Inside your Produce() method, remove this line:

_queue = new Queue<string>();

There you're creating a new queue instance.

Upvotes: 1

NeilDurant
NeilDurant

Reputation: 2132

General approach for this kind of problem....step through the code with the debugger and see what line causes the exception to be thrown.

Upvotes: -1

C. Ross
C. Ross

Reputation: 31868

Try the following:

    public One(Queue<string> queue)
    {
        // should be assigning by reference
        _queue = queue;
    }

    public void Produce()
    {
        _queue.Enqueue("one");
    }

Upvotes: 1

Nestor
Nestor

Reputation: 13990

one.Produce() replaces the _queue with a new "empty" Queue(). That's why string value = _queue.Dequeue() is throwing an error (b/c the _queue)

Upvotes: 0

Related Questions