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]
- There is some fixed set of readable members (fields or properties) which expose its entire state
- Given any set of desired values for those members, one can create an instance with those values (no combinations of values are forbidden).
- 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.