How to work with type values โ€‹โ€‹in C # using properties?

I have a small class called Tank that has one public element named Location , which is a Rectangle (a struct). When I write:

 Tank t = new Tank(); t.Location.X+=10; 

everything works fine and the tank is moving.

But after I changed the element as a property , I can no longer use this syntax . It does not compile since t.Location now a property (which is a function) and returns a temporary copy of the location (because it is a value type).

The only way I can use Location is now to do the following:

 k = t.Location kX +=10; t.Location = k; 

Is there any workaround that can help me not write this ugly code and use the intuitive syntax a+=10; ?

+7
source share
5 answers

From @Servy
โ€œstructures are immutable,โ€ no, thatโ€™s not so. In most cases, they should be, but they are not inherently immutable. The inherent problem here is that the property returns a copy of the structure, not a reference to the structure. If C # had syntax for returning ref, then that would be possible.

In principle, why this does not work is that the structures are unchanged. Once they are done, it is. For this reason, it is not possible to partially reassign the structure. It would be like trying to swap a leg. You can not. This is part of you, and you came with it!

I think the only thing you are going to do is implement your own X and Y attributes, for example:

 public double LocationX { get { return Location.X; } set { Location = new Rectangle(value,Location.Y); } } 

You obviously need to flip this to Y as well, but that should allow you what you want (but don't expect it to be fast or efficient!)

While this answers your immediate question, I would raise a few points about your model. I would rather not try to update a movement like this. From an OO point of view, your tank is its own object and must control its position. Give him movement instructions, and then update her own position.

eg:

 Tank.MoveRelative(10,0); Tank.MoveAbsolute(100,100); 

this allows you a little more freedom and allows the tank to check any queries made on it based on the logic that you gave it.

+3
source

This problem occurs quite often when you start programming in 2D and 3D space using properties. As a rule, the best solution is to implement the addition between two vector structures or two different structures that will be added logically (in your case, you would implement the addition between a 2D vector and your rectangle to shift its position - you would not add two rectangles together).

Having done this, you can write:

 myTank.Location += new Vector2(10, 0); 

Which, although slightly awkward, allows changes to be made for both components in a single operation. Ideally, the added vector will be a velocity vector that you would use to update the location of your tank.

+2
source

I would suggest a method for moving your tank.

 public class Tank { private Rectangle _location; public int X { get { return _location.X; } } public int Y { get { return _location.Y; } } public Tank(int width, int height /* other params */) { _location = new Rectangle(0, 0, width, height); } public Tank Move(Point offset) { _location.X += offset.X; _location.Y += offset.Y; return this; } } 

Use will be

 var tank = new Tank(1, 1); tank.Move(new Point(1, 1)).Move(new Point(1, 1)); //Tank would have X: 2, Y: 2 

This can be changed to use Vector2 or something else.

+2
source

The main difference is that the property is classified as a function, and the field is classified as a variable. Call a member function.

A workaround is to use a field or backup storage, not a property as you did. The creation of mutable value types should be avoided because the behavior is often surprising , difficult to predict, and / or sometimes completely inconsistent.

Here are some detailed details, relevant sections of the specification, that help describe the behavior you are experiencing.

C # 4.0 section 1.6.7.2

The set accessory corresponds to a method with a single parameter named value and return type.

A get accessor corresponds to a parameterless method with returning the value of the property type.

Now switch to 7.5.5 Member Invocation Function , the corresponding section:

If [member of the function] is a member of the instance function declared in the value type:

If [instance expression] is not classified as a variable, then a temporary local variable of type [instance expression] is created, and vlue [instance expression] is assigned to this variable. [instance expression] is then reclassified as a reference to this temporary variable. A temporary variable is available inside [function member], but not otherwise. Thus, only if [instance expression] is a true variable - is it possible for the caller to observe the changes that the [member of the function] does.

0
source

If a class variable or struct-type provides a field of type value, and this type of value provides its contents in the form of fields, operations on these fields can be performed as effectively as operands by type of environment. If the value type is displayed as a property, then the best thing you can do is generally something like:

 var temp = t.Location; temp.X += 4; t.Location = temp; 

Not very elegant, but relatively clear and not too terribly inefficient. An alternative would be for the tank to expose the AdjustLocation method, something like:

 delegate void ActByRef<T1>(ref T1 p1); void ActOnLocation(ActByRef<Point> proc) { proc(ref _Location); } 

and probably also

 delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2); void ActOnLocation<PT1>(ActByRef<Point, PT1>, ref PT1 param1) { proc(ref _Location, ref param1); } 

These methods assume that the Location property uses a support field named _Location . The code could then do something like:

 // Add 5 to X myTank.ActOnLocation( (ref Point loc) => loc.X += 5 ); 

or

 // Add YSpeed to Y myTank.ActOnLocation( (ref Point loc, ref int param) => loc.Y += param, ref YSpeed); 

Note that in the latter case, neither YSpeed , nor this , nor any other local variable is used in lambda; instead, YSpeed is passed as the ref parameter. Because of this, even if the above code is run a million times, the system will only need to create one delegate, which can then be reused each time.

If the structure was large, the above approach may be faster than the approach using a temporary variable. Although the overhead probably exceeds the cost of copying a small structure, the overhead does not depend on the size of the structure. Structures that are several kilobytes in size can be efficiently used if constructions like the one above are used to avoid the need to make temporary copies.

0
source

All Articles