0

As part of my code I have created the following struct:

struct ItemInformation
{
    public int ItemNumber;
    public double HighPrice, LowPrice, MedianPrice;

    public ItemInformation(int number, double highP, double lowP) : this()
    {
        this.ItemNumber = number;
        this.HighPrice = highP;
        this.LowPrice = lowP;
        this.MedianPrice = CalculateMedian(highP, lowP);
    }

    private double CalculateMedian(double high, double low)
    {
        return 0.5 * (high + low);
    }
}

I know the struct contains four pieces of data but I want to be able to only specify three as the fourth one, MedianPrice, is completely determined by the high and low prices. So I want to use

ItemInformation jacket;

as well as

ItemInformation jacket = new ItemInformation(1234, 145.70, 73.20)

to create instances of the structure, but I cannot seem to get this to work. All the items in the struct are set correctly except for the MedianPrice. This may be a mutability issue but I am not really changing values here. I am simply setting one value based on the other two and I want to create instances of the struct using only three inputs. Does anyone have any suggestions as to how I can do this? Any advice would be greatly appreciated. Thank you.

Mike Zboray
  • 39,828
  • 3
  • 90
  • 122
PBrenek
  • 561
  • 1
  • 8
  • 24
  • You're setting `this.Median`, but you should be setting `this.MedianPrice`. Maybe it's a typo in the question though as you said it compiled. What does the value get set to? – JoshVarty Jan 12 '13 at 20:02
  • Set this.Median = 0.0; Then call CalculateMedian. (edit: didn't notice the typo which obviously could be the problem). – Inisheer Jan 12 '13 at 20:02
  • 2
    Your code works for me. I don't see anything wrong (except the typo) – Petar Ivanov Jan 12 '13 at 20:03
  • Maybe you should make the fields _readonly_ to ensure your struct is immutable. Consider removing the `Median` field. You could use a `get`-only property for the `Median` since that value is entirely determined by the other fields. – Jeppe Stig Nielsen Jan 12 '13 at 20:06
  • actual it was a typo in the code posted here. The actual code states MedianPrice. The issue is that if I use ItemInformation jacket = new ItemInformation(1, 10, 5) and print jacket.MedianPrice the value is 0 rather than the median price 7.5 – PBrenek Jan 12 '13 at 20:07
  • @Inisheer He's already chaining the parameterless constructor (`: this()`), so all fiels are initialized to zero by that. – Jeppe Stig Nielsen Jan 12 '13 at 20:07
  • @PBrenek: are you sure that apart from this one typo there are no other differences between this post and your code? I tried it and it just works for me. Posting the actual execution part could help - maybe it's the useage that's broken or maybe you're printing the value of the instance initialized by default – Joanna Derks Jan 12 '13 at 20:20
  • I have edited your code to fix what you indicated was a typo. However, your sample does not exhibit the behavior you are describing. Can you provide a short sample that actually exhibits the unexpected behavior? – Mike Zboray Jan 12 '13 at 20:22

4 Answers4

3

Structures in .net should generally conform to one of two styles, which I'll call "transparent" and "opaque". Note that the MSDN guidelines seem to have been written by someone who thinks everything behave like a class object, and that failure to behave like a class object is a defect; consequently, they ignore the fact that transparent structures can have useful semantics in their own right, and that in many cases factors which would make an opaque structure be inefficient may make classes even more inefficient.

A transparent structure is one in which all fields are exposed, and the state of the struct is nothing more nor less than the combination of values in its fields. A transparent struct may have read-only "convenience properties" that are computed based upon the value of its fields (e.g. a transparent Vector3d struct with fields DX, DY, and DZ might have a Length property which is computed based upon those fields) but it should be clear that such properties do not form part of the state of the structure, but rather a convenient shorthand for performing computations on the fields. For example, vec.Length would be as an alternative to SomeLibrary.Distance3d(vec.DX, vec.DY, vec.DZ).

A variable of a transparent structure type allocates one variable for each field; each field may if desired be accessed as its own separate variable. Passing a transparent structure type by value costs about the same as passing all of its fields individually (in some cases, it may be more efficient; in other cases, less). Passing a transparent structure type by ref has the same fixed cost as passing an object reference, regardless of the number of fields.

A struct should in many cases be transparent if all of the following criteria are met by present version and will also be met by all conceivable future versions [meaning a struct which didn't meet the criteria would not be considered a compatible replacement]

  1. There is some fixed set of readable members (fields or properties) which expose its entire state
  2. Given any set of desired values for those members, one can create an instance with those values (no combinations of values are forbidden).
  3. The default value of the struct should be to have all those members set to the default values of their respective types.

If a struct meets the above criteria, exposing its fields publicly will not allow outside code to do anything which it could not do otherwise, except that it might permit operations which would otherwise be slow to be performed quickly, and might allow thread-safe operations which would otherwise require locking to performed safely using atomic primitives instead.

Not all structures meet the above criteria, of course, but performance can often be improved--sometimes considerably--if those that do meet the criteria are made transparent. Indeed, much of the reason structs are often considered to be only marginally faster than classes is that their performance is hobbled by needlessly making them opaque.

An opaque structure is one whose state semantically represents something other than the values in its fields. For example, a Decimal with a magnitude of 1234 and an exponent of 2 might represent the numerical quantity 12.34. It's not possible to change just one aspect of an opaque structure except by computing a new value for the whole thing. Opaque structures may attempt to enforce invariants upon their fields, but it's important to note that because of the way structure assignment works, it will in many cases be possible for code which abuses threading (but doesn't have any special execution privileges) to abuse threads so as to generate struct instances which violate the structs' invariants.

Microsoft's guidelines are pretty good when applied to opaque structures. Such structures should generally refrain from exposing property setters. Types like Point and Rectangle do not represent departures from that rule. Instead they represent types which should not have been opaque structures in the first place--they should have been transparent structures. There are some cases where opaque structures that expose property setters can offer semantics which are not achievable via any other means, but such structures have some annoying pitfalls that generally make them less desirable than would be other means of achieving the same ends.

With regard to your proposed structure, I would suggest either making it a transparent structure with three fields, and have MedianPrice be a property that computes the median of those three fields each time it's called, or else an opaque structure with four fields, which would compute MedianPrice in its constructor. My decision would be based upon the relative frequency of reading MedianPrice, compared to the relative frequency with which code would want to modify one of the prices without modifying the others. If the former would be much more often, opaque struct. If the latter, transparent struct. Note that if one starts out using a transparent struct and code is written to take advantage of it, it may be difficult to adapt the code to deal with an opaque struct. By contrast, code which is written to work with an opaque struct will also work with a transparent struct, but will not be as efficient as it otherwise could be.

Note that the difference in efficiency between transparent structures and other types grows with struct size. Suppose, for example, that one needs to store the coordinates of many 3d triangles. One could define a transparent struct Triangle3d containing three fields of type Point3d (each of which would hold three numbers X,Y,Z), and store all the triangles in a Triangle3d[]. Given such definitions, one could easily modify a single coordinate of a single point in any triangle without having to touch or even read any of the others. By contrast, if one used an opaque structure or immutable class type, changing even a single property would require copying all the information from the old instance to a new one. The more fields the type has, the greater the cost of using an opaque or immutable type. If one used a mutable class type, modifications would be cheap, but there would be no nice way a method which held the aforementioned array to make the coordinates of a triangle available to a caller without exposing the underlying storage object, other than by hand-copying all the field values to a new immutable object. Thus, contrary to Microsoft's guidelines, for many usage patterns, the more fields a type has, the greater the advantage to its being a transparent struct rather than a class.

Incidentally, some people would argue that exposed-field structs violate encapsulation. I would argue the contrary. A type like KeyValuePair<TKey,TValue> is supposed to hold a field of type TKey, and another of type TValue. Since the two fields may be freely read, they may be assigned any combination of values that would be valid for their respective types, and there isn't really anything useful which future versions of the type might conceivably do which a transparent struct wouldn't be able to do just as well, the so-called "encapsulation" does nothing but make code less efficient while adding zero value.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • WOW!!! THANK you very much! This is extremely well and clearly explained. I have opted for the transparent structure for the reasons you cite above. I felt it was the right way to go but could not justify it in the clear and concise manner that you have. Thank you for making this clear to me. Incidentally, where can I learn this type of underlying information about C#? – PBrenek Jan 14 '13 at 18:28
  • @PBrenek: I'm not sure what source I'd recommend. A lot of writers seem to take MSDN's guidelines as gospel, I'm afraid, even though they are only appropriate for certain kinds of structs and are grossly inappropriate for others. I would guess the problem is that there was an edict that no Framework types should expose storage locations, notwithstanding that a better rule should have been to say that the only data types which should directly expose storage locations are those whose semantics are sufficiently constrained that such exposure would not constrain them further. Note that... – supercat Jan 14 '13 at 19:37
  • ...such a criterion would be met by relatively few classes, but by many structs. Classes which use properties can usefully send notifications when their properties are changed, but structs generally cannot do so. Many classes are inheritable, which means that even if there's nothing useful for a base-class property to do except read/write the backing field, a derived class might have it do something useful. I'd posit that class fields should be private or protected unless there's a good reason to make them otherwise, but fields of data-holder structs should be generally public. – supercat Jan 14 '13 at 19:58
2

You should make the struct immutable by making all instance fields readonly. Also, the MedianPrice could be a property, and it needs no data in the struct value:

struct ItemInformation
{
  public readonly int ItemNumber;
  public readonly double HighPrice, LowPrice;  // no MedianPrice here

  public ItemInformation(int number, double highP, double lowP) // no need to chain this()
  {
    this.ItemNumber = number;
    this.HighPrice = highP;
    this.LowPrice = lowP;
  }

  public double MedianPrice
  {
    get
    {
      return 0.5 * (HighPrice + LowPrice);
    }
  }
}
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • I tried this. Got errors stating that cannot aassign to to a read only field. So took away the readonly and it works great. Thank you very much. Incidentally, how do I make this immutable? – PBrenek Jan 12 '13 at 20:22
  • @PBrenek - At what point did you get the error? How are you using this struct ? Are you maybe creating a blank instance and then assigning the fields directly rather than using the constructor? That might shed some light on your original issue – Joanna Derks Jan 12 '13 at 20:26
  • @PBrenek Don't remove `readonly`. If you need to assign to `ItemNumber`, `HighPrice`, or `LowPrice` later, you're "mutating" an already created struct value. That's not something most people would recommend. Maybe you could use a class instead? Or you could create a new struct instance. So instead of saying `jacket.HighPrice = 119.95;` you could say `jacket = new ItemInformation(jacket.ItemNumber, 119.95, jacker.LowPrice);`. That would create a new struct value almost like the old value, only with the `HighPrice` changed. But it wouldn't mutate an existing struct value. – Jeppe Stig Nielsen Jan 12 '13 at 20:30
  • The error occurred on compilation. I am actually using it in two ways: ItemInformation newItem; and ItemInformation jacket = new ItemInformation (0, Double.MinValue, Double.MinValue); . Then later in code I assign jacket.ItemNumber = 1234; and so on and if I want to create new item I do newItem = jacket; – PBrenek Jan 12 '13 at 20:33
  • @PBrenek See [Why are mutable structs evil?](http://stackoverflow.com/questions/441309/) I think you should make your struct immutable. If you really like to have a mutable type, consider using a `class` type instead of a `struct` type. – Jeppe Stig Nielsen Jan 12 '13 at 20:42
  • I certainly will look at the posted reference, and yes, this really ought to be a struct rather than a class. There is another issue here. The struct needs to be assigned multiple times and I am not sure what creating new instance of a struct with different values will do to the performance of the code. I am thinking of using your suggestion of jacket = new ItemInformation(jaket.Number, 119.95, jacket.LowPrice). – PBrenek Jan 12 '13 at 20:49
  • @PBrenek I don't think you should worry about performance. All the things we discuss here will be really fast. If you think the syntax `jacket = new ItemInformation(jacket.ItemNumber, 119.95, jacker.LowPrice);` is ugly, you could make a method `ChangeHighPrice` with a parameter `double newHighPrice`, and move the ugly code into that. – Jeppe Stig Nielsen Jan 12 '13 at 20:58
  • I understand. Thank you. I would like to use a similar type of struct in a different application. In that case the struct may need to get reassigned many, many times per second. Would this approach create a performance issue in the case of many reassignments per second? – PBrenek Jan 12 '13 at 21:03
  • @PBrenek: See my answer. Given what you've described of you're type, it's possible that it should be an opaque struct (what others call an "immutable struct", notwithstanding the fact that all non-trivial structs are mutable), but perhaps a transparent struct would be better. In any case, you should certainly be familiar with both styles of struct, since there are usage scenarios where each can be vastly superior to any other kind of object. Note that the common hatred for "mutable structs" is mainly a hatred of structs which can be asked to *modify themselves*, as distinct from... – supercat Jan 12 '13 at 23:02
  • ...structs which expose their fields to external modification. Asking a struct which is stored in a non-writable location to modify itself will cause the compiler to create a temporary copy of the struct, have it modify itself, and then abandon it. Probably not what was intended, but there's no mechanism to request warnings in such cases. On the other hand, code which attempts to modify a struct field won't compile unless it would actually work, so that argument against having structs modify themselves in no way implies that structs shouldn't have exposed fields. – supercat Jan 12 '13 at 23:06
  • Thank you for the clarification. I was in fact trying to use the struct in a manner in which the fields of the struct were being altered rather than the struct itself. – PBrenek Jan 14 '13 at 17:11
0

I think the problem might be, that you're refering to:

this.Median

and not

this.MedianPrice
clean_coding
  • 1,156
  • 1
  • 9
  • 14
0

At first

ItemInformation jacket;

does not mean than jacket variable is created. You can't operate with it.

Try this:

public ItemInformation(int number = 0, double highP = 0, double lowP = 0)
{
    ItemNumber = number;
    HighPrice = highP;
    LowPrice = lowP;
    MedianPrice = 0;
    MedianPrice = CalculateMedian(highP, lowP);
}

usage:

ItemInformation jacket1 = new ItemInformation();
ItemInformation jacket2 = new ItemInformation(1234, 145.70, 73.20);
Alexander Balte
  • 900
  • 5
  • 11
  • tThanks for the suggestion. I tried it. I cannot do the int number = 0, etc part as it creates errors in compilation and the issue still remains. the median is being set to 0 rather than being calculated correctly. – PBrenek Jan 12 '13 at 20:15
  • No special effort is needed to get usage like `new ItemInformation()` because this is a **struct**. For a struct `S`, the syntax `new S()` in C# is like `default(S)` and creates a value of the struct where all instance fields are zero/false/null. – Jeppe Stig Nielsen Jan 12 '13 at 20:20
  • @Jeppe Stig Nielsen, thank you, then optional parameters are not needed. – Alexander Balte Jan 12 '13 at 20:25
  • The first declaration creates a new instance of `jacket`, with its field values initially undefined. If one says `jacket.ItemNumber = 23; jacket.HighPrice = 12; jacket.LowPrice = 18; jacket.MedianPrice = 15;`, one will then have an instance of `jacket` which may be passed around or used as one sees fit. – supercat Jan 13 '13 at 04:11