Calling Methods from a Property

Cags

Contributor
Joined
Feb 19, 2004
Messages
695
Location
Melton Mowbray, England
I recently ran into a problem in my application and it took me awhile to track down the cause. The code below demonstrates the problem. Basically the problem revolves around calling a Method of a Property of a class. Insead of doing as I expected it to, it just didn't do anything. This leads me to believe you cannot call a Method of a Property, but if this is the case I would have expected an error at either compile time or possibly at runtime. I'm quite resigned to the fact this is the way it is and I will probably have to live with it, I was just looking for an explanation as to why this is the case.
C#:
private class Piece
{
	private Rectangle _bounds;
	
	public Rectangle Bounds
	{
		get { return _bounds; }
		set { _bounds = value; }
	}

	public Piece()
	{
		_bounds = new Rectangle(10, 10, 100, 100);
	}
}

private void Test()
{
	Piece myPiece = new Piece();
	System.Diagnostics.Debug.WriteLine("Location = " + myPiece.Bounds.X.ToString() + ", " + myPiece.Bounds.Y.ToString());

	myPiece.Bounds.Offset(displacement);
	System.Diagnostics.Debug.WriteLine("Location = " + myPiece.Bounds.X.ToString() + ", " + myPiece.Bounds.Y.ToString());

	myPiece.Bounds = new Rectangle(myPiece.Bounds.X + displacement.X, myPiece.Bounds.Y + displacement.Y, myPiece.Bounds.Width, myPiece.Bounds.Height);
	System.Diagnostics.Debug.WriteLine("Location = " + myPiece.Bounds.X.ToString() + ", " + myPiece.Bounds.Y.ToString());
}
// This is the output from the console
// Location = 10, 10
// Location = 10, 10
// Location = 12, 12
 
If you examine the actual mechanics of the code you are using you will see the problem. Look at the following line of code:
C#:
myPiece.Bounds.Offset(displacement);
When you call a property getter it will get the value and toss it onto the stack, where you can assign it to something else, call a function on it, whatever. This confuses some people because it gets tricky with value type objects. When you call the Bounds property to get a value, a copy of that value is placed on the stack. You then call the Offset method successfully, but that doesn't offset the bounds of the original rect. Once you have that copy it is a completely separate object from the actual field that backs the Bounds property and the compiler and runtime have no way to reflect the changes back in the myPiece object. Instead it offsets the bounds of a copy on the stack which is promptly discarded since you do nothing else with it.

I do believe that this would cause a compile-time error in VB.
 
This has also been a struggle for me ever since I picked up .Net. With C/C++, pointers, value, and reference types are so in your face that it's impossible not to know exactly what you are dealing with. Sometimes these dumbed down pointers really get in the way in my opinion -- sometimes you never quite know what you are dealing with and rarely are the differences stated so clearly.

Very concise and informative, marble_eater.
 
I changed the code to the more conveniantly read...
C#:
Rectangle rect = pPiece.Bounds;
rect.Offset(displacement);
pPiece.Bounds = rect;
Just so that I've got this clear in my head. Calling a void method of a property getter which pertains to a value type is essentially useless (assuming the void method calls nothing external to the class), because the value object is copied to the stack with no reference to it?

What I mean by this is that calling the .Offset method via the property getter makes a copy of the Rectangle on the stack, but there is no way from that section of code to access the Rectangle on the stack? I mean no easy way, I'm sure theres a complicated solution for accessing data on the stack via reflection or whatever.

Is it just me or does all that sound abit confusing? I hope it makes sense.
 
What everyone should realize is that a property is just a function pair. What would you expect if you typed the following code:
C#:
someForm.get_Bounds().Offset(1, 0);
I wouldn't expect it to modify the actual form bounds. Here it is fairly clear that you will be modifying a copy of a form's bounds. That is what you need to expect from a property.

Or think about it this way: a form class probably doesn't store it's bounds in a RECT at all. It probably has to retrieve them through the Windows API each time you ask the form for it's bounds, so the RECT object that you call the Offset() function on is clearly not attatched in any way to the form (or any .Net object at all) at the point at which you invoke the function. Think of property getters as read-only. Always. Reference-type properties allow you to modify an object, but the reference returned is read-only. Yes, you can write to the returned value, but the value is immediately discarded from the stack so it can't do any good.

One thing that doesn't help the confusion is that in some cases you are allowed to treat properties like fields, such as
C#:
someForm.Width++;
but the fact is that the above code actually implies a property get and then a property set...
C#:
someForm.set_Width(someForm.get_Width() ++);
 
The more I think about this the more confused I get. I understand some of what your saying but not all. I'll sleep on it now and see if it makes more sense in the morning. If not hopefully the morning light will help me to compose my thoughts into some sort of logical order, because after 20mins of trying to form a reply I'm giving up.
 
I'll start with the middle part of your article first.
marble_eater said:
Or think about it this way: a form class probably doesn't store it's bounds in a RECT at all. It probably has to retrieve them through the Windows API each time you ask the form for it's bounds, so the RECT object that you call the Offset() function on is clearly not attatched in any way to the form (or any .Net object at all) at the point at which you invoke the function. Think of property getters as read-only.
This I understand and is infact what I was trying to confirm in my previous post.
marble_eater said:
Reference-type properties allow you to modify an object, but the reference returned is read-only. Yes, you can write to the returned value, but the value is immediately discarded from the stack so it can't do any good.
This part unfortunately I don't understand. If I did, it might well effect what I wrote in the rest of this post. I'll carry on regardless in anticipation of some clarifcation. Back to the start of your post.
marble_eater said:
What everyone should realize is that a property is just a function pair.
I understand that perfectly.
marble_eater said:
What would you expect if you typed the following code:
C#:
someForm.get_Bounds().Offset(1, 0);
I wouldn't expect it to modify the actual form bounds. Here it is fairly clear that you will be modifying a copy of a form's bounds. That is what you need to expect from a property.
Call me stupid but moving the form is exactly what I expected to happen. Take the following.
C#:
myForm.Controls.Clear();
// or if you prefer in the java type notation you tried to use as an example
myForm.get_Controls().Clear();
Look at this code and tell me you expect it to perform nothing. In my opinion, if I'm guilty of anything, it's not so much a misunderstanding of the function pair of a property, but rather a failure to recognise the Rectangle object as being a value type. Thus failing to realise it will act differerntly. I must admit untill I actually thought about it, my mind just accepted the Rectangle object as being a class not a stuct.

And finally to the end of your post
marble_eater said:
One thing that doesn't help the confusion is that in some cases you are allowed to treat properties like fields, such as
C#:
someForm.Width++;
but the fact is that the above code actually implies a property get and then a property set...
C#:
someForm.set_Width(someForm.get_Width() ++);
This is exactly the problem. Whilst it may have been niave, I expected the '.Offset()' command to be treated in a similar manner to that of the '++'. This belief was certainly not helped by the apparent differences between how value type and reference type objects work with properties.

I think thats pretty much everything. Whilst my opinions may turn out to be ill-informed or just plain make me look stupid, that is a small price to pay if I actually learn something.
 
You had some confusion about one thing I said:
Me said:
Reference-type properties allow you to modify an object, but the reference returned is read-only. Yes, you can write to the returned value, but the value is immediately discarded from the stack so it can't do any good.

But then you actually demonstrated what I said (hopefully the use of temporary variables demonstrates what is going on better):
C#:
// Originally posted by Cags

myForm.Controls.Clear();
// or if you prefer in the java type notation you tried to use as an example
myForm.get_Controls().Clear();
Pay attention to value-type versus reference-type. A ControlCollection is a reference type. You get a copy of a pointer. When you call a function on the object returned by Controls, there is a level of indirection: you are actually calling a function on data pointed to by a pointer. You aren't modifying the return value, but something pointed at by the return value. If you modified the return value, nothing would happen, for instance:
C#:
Object ControlsVar = this.Controls;
ControlsVar = null;
Note that if you run the preceeding code, the Form's control collection will not be set to null. This is what happens when you modify a copied value. Nothing. But...
C#:
Object ControlsVar = this.Controls;
ControlsVar.Clear();
Here you do modify the Form's ControlCollection because of the indirection. Both the pointer and the copy of a pointer reference the same (uncopied) data so the change is reflected back in the original object.

Apply the same logic with value-types.
C#:
Rectangle r = this.Bounds;
r = new Rectangle(0, 0, 1, 1);
Do you expect this to change the form's bounds? I hope not, because r is not the Form's bounds, it is a copy of the Form's bounds. What happens when you modify a copy? Nothing. Whether you call the function on a copy stored in a temporary variable or on a copy returned directly from a function, nothing happens. Likewise:
C#:
Rectangle r = this.Bounds;
r.Offset(10, 10);
will not modify the Form's bounds, because you are modifying a copy.

To make it clearer, look at an elemental data type...
C#:
int i = SomeControl.Value;
i = 0;
This is marshalled exactly the same way as any value-type. Obviously, changing "i" will not change the control's value. It is a copy of the control's value.
 
I'm not at all happy with my ability to articulate myself and as such feel I probably look quite stupid in parts of this thread, but at the time my assumptions seemed perfectly logical. I do however think that I now understand how it all works. Thankyou for your patience.
 
Back
Top