Can I modify pointer to an Object which is passed into a class?

  • Thread starter Thread starter TZ
  • Start date Start date
T

TZ

I have two classes, UIHousehold and WIZEditHousehold (both are
winforms).

UIHousehold creates and shows the WIZEditHousehold (WIZEditHousehold
can't be modal, so UIHousehold doesn't know when WIZEditHousehold
closes).

A reference to a buffer within UIHousehold is supplied (passed by REF)
to the constructor of WIZEditHousehold - that constructor saves the
reference to the buffer in a member variable.

OnClose, the WIZEditHousehold attempts to change the reference to the
buffer. That change is not working (the changes are never seen in
UIHousehold).

Here's pseudocode:

class WIZEditHousehold
{
BufferClass _fromUIHousehold;
BufferClass _changedInWizard;
WIZEditHousehold(ref BufferClass bufferFromUIHousehold)
{
_fromUIHousehold = bufferFromUIHousehold;
}

OnClose()
{
_fromUIHousehold = _changedInWizard;
}
}

OnClose, _fromUIHousehold values change in WIZEditHousehold, but these
values are NOT changed in UIHousehold's instance of BufferClass.

In summary, my problem is that I want to change the BUFFER POINTER, but
C# is treating _fromUIHousehold as a Pass By Value reference, not a
true reference (modifiable pointer).

I can accomplish this task via interfaces, upgrading BufferClass, or
events, but my proposed solution is much easier to code for the
multitudes of wizards and buffers I'm developing. Are there any ways
to allow me to treat _fromUIHousehold as a modifiable pointer to
UIHousehold's buffer?
 
Yes, this code snipped will display exactly the behaviour you describe.
Here's a blow-by-blow.

BufferClass _fromUIHousehold;
BufferClass _changedInWizard;
WIZEditHousehold(ref BufferClass bufferFromUIHousehold)
{
_fromUIHousehold = bufferFromUIHousehold;
}

Let's say, for the sake of argument, that the storage for the
BufferClass object is at location 0xbcbc in memory, and that the memory
for the reference to the buffer, which forms part of UIHousehold, is at
location 0xefef.

When you call WIZEditHousehold, the argument value on the stack will be
0xefef... a reference to the location where the point to the
BufferClass object (0xbcbc) is stored.

Then you will copy the reference to the BufferClass object into a
private member called _fromUIHousehold. So, you follow the pointer on
the stack to location 0xefef, and copy what's there, 0xbcbc, into
_fromUIHousehold. Now your WIZEditHousehold constructor exits. At this
point, WIZEditHousehold has lost track of the location of the
BufferClass reference pointer (0xefef). All it has, in the local,
private member _fromUIHousehold, is the address of the BufferClass
object itself, 0xbcbc.

Now, when you close the form, you do this:

OnClose()
{
_fromUIHousehold = _changedInWizard;
}

So, you assign a reference to some other BufferClass object to the
_fromUIHousehold member. However, the form is about to be disposed, so
the assignment does very little.

The source of your confusion is this: bufferFromUIHousehold is a
reference item. If you say

bufferFromUIHousehold = xxxx;

then the object reference back in UIHousehold will change. However,

_fromUIHousehold

is just a normal object reference like any other. Just because you
assigned to it from a "ref" parameter doesn't make it magically become
a reference to a reference: the "ref" parameter is _dereferenced_
during the assignment, so that a reference to a BufferClass ends up in
_fromUIHousehold, not a reference to a reference to a BufferClass,
which is as it should be.

In fact, there's really no way, using safe C#, to do what you want to
do: store a reference to a reference away and then use it later.

Fortunately, there's a way to get the effect you want, even if the
mechanism is different: use events. UIHousehold subscribe to
WIZEditHousehold's "Closing" event. When the ..._Closing() event
handler in UIHousehold is called, do this:

private void WIZEdit_Closing(object sender,
System.ComponentModel.CancelEventArgs e)
{
WIZEditHousehold wiz = (WIZEditHousehold)sender;
this.myBuffer = wiz.Buffer;
}

then make a "Buffer" property in WIZEditHousehold that allows
UIHousehold to get its buffer whenever it wants (in this case, when the
form is closing).
 
Bruce,

Thanks for the explanation of what's going on behind the scenes.

Since C# doesn't really allow me to use modifiable pointers, I think
I'll use an interface. Events would work, but are more problematic
from a maintenance standpoint - if another class ever needed to use
WIZEditHousehold, the developer would need to know to subscribe to the
event. If instead I have a single constuctor to WIZEditHousehold which
takes an IHouseholdContainer, all future users of WIZEditHousehold
could not create the wizard without supplying the interface.

OnClose will use the IHouseholdContainer.SetHouseholdInfo to change the
caller's Household.

By the way, my orginal design used Interfaces, then I thought of this
much more elegant solution. More elegant, but WRONG! :)
 
In fact, there's really no way, using safe C#, to do what you want to
do: store a reference to a reference away and then use it later.

Oh but there is :-))

Start off by creating a class that can hold a reference to a field. Or
rather it needs to hold a reference to both the field and the object that
holds the field :

///////////////////////////
class RefHolder
{
object fieldOwner = null;

FieldInfo fi = null;

public RefHolder(object fieldOwner, string fieldname)
{
//assume we are getting a field, but this could use PropertyInfo
instead
Type t = fieldOwner.GetType();
fi = t.GetField(fieldname, BindingFlags.Instance |
BindingFlags.NonPublic);
this.fieldOwner = fieldOwner;
}

public object Field
{
get { return fi.GetValue(fieldOwner); }
set { fi.SetValue(fieldOwner, value); }
}
}

Next create your Buffer class :

class TestBuffer
{
private string name;

public TestBuffer(string name)
{
this.name = name;
}

public string Name
{
get { return name; }
}
}

Now create your class that holds the original buffer and that is going to
create the WIZEdit :

class UIHousehold
{
// set up a buffer
private TestBuffer test = new TestBuffer("Joanna");

public void CreateWIZEdit()
{
// create a RefHolder for your buffer by passing 'this' plus name of
field that holds buffer
RefHolder rh = new RefHolder(this, "test");

// create WIZEdit passing ref to buffer
WIZEditHousehold weh = new WIZEditHousehold(rh);

// simulate closing the form; this would normally be called from
inside the WIZEdit
weh.ChangeBuffer();
}

// provide a property so that we can see the old/new values of the
'buffer'
public string BufferContents
{
get { return test.Name; }
}
}

Here is the WIZEdit 'form' that is passed a 'buffer' and will change the
buffer when it closes

class WIZEditHousehold
{
private RefHolder buffer = null;

public object Buffer
{
get { return buffer.Field; }
set { buffer.Field = value; }
}

public WIZEditHousehold(RefHolder buffer)
{
this.buffer = buffer;
}

// method to simulate form closing
public void ChangeBuffer()
{
Buffer = new TestBuffer("TZ");
}
}

Finally , a quick test program :

class Program
{
static void Main(string[] args)
{
UIHousehold uih = new UIHousehold();

Console.WriteLine(uih.BufferContents);

uih.CreateWIZEdit();

Console.WriteLine(uih.BufferContents);
}
}
//////////////////////////////////

Joanna
 
By the way, my orginal design used Interfaces, then I thought of this
much more elegant solution. More elegant, but WRONG! :)

Not totally; see my post replying to Bruce :-))

Joanna
 
Very clever. However, isn't this a rather long row to hoe for
inter-form communication? (A good thing to keep in the tookit, though,
for other situations. :)

Correct me if I'm wrong (as you've seen, I often am :), but the usual
paradigm for communication between Forms in .NET is events. TZ, other
developers will be expecting to see your forms sending events to each
other to notify each other that important things have happened (such as
closing, the user doing some action, etc.) This is the standard method
of communication within WinForms applications.

I would hesistate to come up with a whole new way of solving the
problem of communicating a result from a form... just because other
developers trying to use your class will wonder why you just didn't do
it the "usual way", and will have to spend that bit of extra time
trying to figure out how your way works. The "usual way," to my
understanding, is that forms expose properties from which anyone else
can read important information about the form (including anything the
user may have indicated by his/her actions). Then, the form either:

1. Simply returns, if invoked by ShowDialog(). The calling object then
fetches results from properties of the form before calling its
Dispose() method.
2. Relies on interested parties to subscribe to the Closing event, and
fetch results from properties of the form before it closes and disposes
itself.

On the contrary, I find your method of passing information to the
constructor so that the form can store its result in an outside object
to be problematic in other ways (not just because it's "not the usual
way"). What if two outside objects want to know what the user did?
Right now, they can both just subscribe to Closing and poll the
property when their event handlers are called.
 
Very clever. However, isn't this a rather long row to hoe for
inter-form communication? (A good thing to keep in the tookit, though,
for other situations. :)

Yes, the end result is certainly the wrong way to do things; I just took up
the challenge of achieving the means to the end :-))

I will write this up in an article and put it on my blog. Does anyone have a
good legitimate scenario where this would come in useful?
Correct me if I'm wrong (as you've seen, I often am :), but the usual
paradigm for communication between Forms in .NET is events. TZ, other
developers will be expecting to see your forms sending events to each
other to notify each other that important things have happened (such as
closing, the user doing some action, etc.) This is the standard method
of communication within WinForms applications.

<snip>

Your comments are correct and very useful and I hope that TZ takes note and
modifies his model.

Joanna
 
Back
Top