Vitaliy
Vitaliy

Reputation: 485

Change struct in collection

I have read about boxing in Richter's book and there is one thing I don't understand.
I have successfully changed struct in object. But when I try to change struct in collection, I have a problem.

//my struct
internal struct Point:IChangeBoxedPoint
{
    public Int32 x, y;
    public void Change(Int32 x, Int32 y)
    {
        this.x = x;
        this.y = y;
    }

    public override string ToString()
    {
        return String.Format("{0}, {1}", this.x, this.y);
    }
}

public static void Main()
{
    List<Point> a = new List<Point>();
    Point p = new Point();
    p.Change(1, 1);
    Console.WriteLine(p); //write 1, 1
    object o = p;
    Console.WriteLine(o); //write 1, 1
    ((Point)o).Change(2, 2);
    Console.WriteLine(o); //write 1, 1
    ((IChangeBoxedPoint)o).Change(3, 3);
    Console.WriteLine(o); //write 3, 3
    for (int i = 0; i < 10; i++)
    {
        p.x = p.y = i;
        a.Add(p);
    }

    Console.WriteLine(a[0]); //write 0, 0
    ((IChangeBoxedPoint)a[0]).Change(300,300);
    Console.WriteLine(a[0]); //still writes 0,0
}

Upvotes: 4

Views: 158

Answers (3)

Daniel Imms
Daniel Imms

Reputation: 50149

This is happening because structs are value types like primitives (int, short, etc.). When you box the struct in object it makes a copy of itself that works separately. Here is a simple example that illustrates this.

Point a = new Point();
Console.WriteLine(a); // 0, 0
a.Change(1, 1);
Console.WriteLine(a); // 1, 1
object b = a;
Console.WriteLine(b); // 1, 1
a.Change(2, 2);
Console.WriteLine(a); // 2, 2
Console.WriteLine(b); // 1, 1

class vs struct

If you want to use a reference type you won't run into this issue and your program will just pass around the reference to your single object. Use a class not struct if you want a reference type.

internal class Point : IChangeBoxedPoint

When to use a struct

According to this other question which references MSDN.

Do not define a structure unless the type has all of the following characteristics:

  • It logically represents a single value, similar to primitive types (integer, double, and so on).
  • It has an instance size smaller than 16 bytes.
  • It is immutable.
  • It will not have to be boxed frequently.

In case you didn't know immutable means the object can't change. So you're actually going against good practice by boxing the structs in an interface in an attempt to change them.

Boxing in IChangeBoxedPoint

You can get around your issue by using a List<IChangeBoxedPoint> but this will just change it to use a reference to your value type, my question is why would you bother when you can just change your struct to a class.

List<IChangeBoxedPoint> a = new List<IChangeBoxedPoint>();

Further reading

Upvotes: 3

evgenyl
evgenyl

Reputation: 8107

It seems to be right:

Console.WriteLine(a[0]); //write 0, 0 -

just because your first item received i=0

And for this code,

 ((IChangeBoxedPoint)a[0]).Change(300,300);

Does not actually change first item. It created new instance of boxed object Point, and changed its values.

Try to change it to be

 Point newPoint = new Point();
 newPoint.Change(300,300);
 a[0] = newPoint;
 Console.WriteLine(a[0]); 

Upvotes: 0

Patko
Patko

Reputation: 4423

You should declare your collection as List<IChangeBoxedPoint> instead of List<Point>.

If you declare your list as List<Point> then your values are boxed/unboxed to and from the interface, as others have already stated. But if you have a list of interfaces then values are boxed only when you add them to the list, allowing you to change them as needed.

Upvotes: 3

Related Questions