14

I'm not at all new to programming, but there seems to be a hole in my understanding of C# structs.

Can anyone explain why the following code prints out the following?

Dist1: 0, Dist2: 0

struct Distance
{
    public void SetFeet(int feet) { Value = feet; }
    public void SetMiles(float miles) { Value = (int)(miles * 5280f); }
    public int GetFeet() { return Value; }
    public float GetMiles() { return Value / 5280f; }
    private int Value;
}

class Distances
{
    public Distance Dist1 { get; set; }
    public Distance Dist2 { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Distances distances = new Distances();
        distances.Dist1.SetFeet(1000);
        distances.Dist2.SetFeet(2000);

        Console.WriteLine("Dist1: {0}, Dist2: {1}",
            distances.Dist1.GetMiles(),
            distances.Dist2.GetMiles());

        Console.ReadLine();
    }
}
Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • 6
    This is _exactly_ why it isn't a good idea to have mutable structs. – Jeff Mercado Aug 27 '11 at 05:00
  • @Jeff: Fine, but then how could you implement code to assign a value to this struct when there are multiple ways to interpret the arguments? (In my case, I guess I could assign an `int` or `float` and have them interpreted as feet and miles, respectively, but my real code has more ways of interpreting the underlying value.) – Jonathan Wood Aug 27 '11 at 05:35
  • Make it immutable. As in, don't make it possible to change values stored in your struct. Look at the public interfaces of existing value types in the framework and how they handle "changing values." See: `DateTime`, `String`, `KeyValuePair`. I could add an answer if Yahia's isn't satisfactory. – Jeff Mercado Aug 27 '11 at 05:42

3 Answers3

13

Getters and setters -- how properties are accessed -- still function like methods in this regard. That is,

distances.Dist1.SetFeet(1000);

is "equivalent" to

distances.GetDist1().SetFeet(1000);

The "copy" of the structure (value) is made when it is returned from the getter (or passed to the setter). If Dist1 were a member variable this would not be the case and would work "as expected".

Happy coding.

10

struct are value types - so when you are accessing distances.Dist1.SetFeet you basically are accessing a copy... see for example at MSDN http://msdn.microsoft.com/en-us/library/aa288471%28v=vs.71%29.aspx

[EDIT after comment]
On the other hand, if you do distances.Dist1 = new Distance ().SetFeet (1000); AND change the return of SetFeet from void to Distance it should work. Alternatively make Distance a class.

For a reference on how to build structs in a way that they work as expected see the DateTime struct in the framework - http://msdn.microsoft.com/en-us/library/system.datetime.aspx
[/EDIT after comment]

Torantula
  • 177
  • 1
  • 13
Yahia
  • 69,653
  • 9
  • 115
  • 144
  • 1
    I know that structs are value types. But I'm not seeing how `distances.Dist1.SetFeet()` is a copy. Where is the copy made? Can you elaborate? – Jonathan Wood Aug 27 '11 at 04:59
  • 1
    just added a link to MSDN with examples similar to yours, accessing `distances.Dist1' gives you a value (not a reference)... this is a copy of the field Dist1 - you then call SetFeet on that copy... change the struct to a class and see immediately that it works (because then you get a reference) – Yahia Aug 27 '11 at 05:02
  • Okay, I got it now. Thanks. Seems kind of awkward--I can't use your suggested code because `SetFeet()` does not return a value that can be assigned to Dist1. – Jonathan Wood Aug 27 '11 at 05:30
  • added reference to DateTime as an example of struct implementation and corrected the assignment (change return type from void to Distance for all "modifying method" of the struct)... – Yahia Aug 27 '11 at 05:31
-1

Properties are treated differently to variables, just remove { get; set; } from your Distance declaration and the code works fine.

struct Distance
{
    public void SetFeet(int feet) { Value = feet; }
    public void SetMiles(float miles) { Value = (int)(miles * 5280f); }
    public int GetFeet() { return Value; }
    public float GetMiles() { return Value / 5280f; }
    private int Value;
}

class Distances
{
    public Distance Dist1;//here
    public Distance Dist2;//and here
}

class Program
{
    static void Main(string[] args)
    {
        Distances distances = new Distances();
        distances.Dist1.SetFeet(1000);
        distances.Dist2.SetFeet(2000);

        Console.WriteLine("Dist1: {0}, Dist2: {1}", distances.Dist1.GetMiles(),
            distances.Dist2.GetMiles());

        Console.ReadLine();
    }
}
critic
  • 309
  • 2
  • 4