Page 22 - MSDN Magazine, June 2018
P. 22
Arc structure. Any existing compiled code that invoked the fields would break at runtime, as would any code (even if recompiled) that passes the field as a by ref parameter.
Given the guideline that fields should not be public or protected, it’s worth noting that properties, especially with default values, became easier to define than explicit fields encapsulated by prop- erties, thanks to support in C# 6.0 for property initializers. For example, this code:
public double SweepAngle { get; set; } = 180;
is simpler than this:
private double _SweepAngle = 180; public double SweepAngle {
get { return _SweepAngle; } set { _SweepAngle = value; }
}
The property initializer support is important because, without it, an automatically implemented property that needs initialization would need an accompanying constructor. As a result, the guide- line: “Consider automatically implemented properties over fields” (even private fields) makes sense, both because the code is more concise and because you can no longer modify fields from outside
Figure 1 Defining an Arc
their containing property. All this favors yet another guideline, “Avoid accessing fields from outside their containing properties,” which emphasizes the earlier-described data encapsulation prin- ciple even from other class members.
At this point lets return to the C# 7.0 tuple type ValueTuple<...>.
Despite the guideline about exposed fields, ValueTuple<T1, T2>,
for example, is defined as follows:
public struct ValueTuple<T1, T2>
: IComparable<ValueTuple<T1, T2>>, ...
{
// ...
}
public T1 Item1;
public T2 Item2;
What makes ValueTuple<...> special? Unlike most data struc- tures, the C# 7.0 tuple, henceforth referred to as tuple, was not about the whole object (such as a Person or CardDeck object). Rather, it was about the individual parts grouped arbitrarily for transporta- tion purposes, so they could be returned from a method without the bother of using out or ref parameters. Mads Torgersen uses the analogy of a bunch of people who happen to be on the same bus— where the bus is like a tuple and the people are like the items in the tuple. The Items are grouped together in a return tuple parameter because they are all destined to return to the caller, not because they necessarily have any other association to each other. In fact, it’s likely that the caller will then retrieve the values from the tuple and work with them individually rather than as a unit.
By leveraging public fields rather than properties, the Arc definition lacks the most basic of object-oriented design principles—encapsulation.
The importance of the individual items rather than the whole makes the concept of encapsulation less compelling. Given that items in a tuple can be wholly unrelated to each other, there’s often no need to encapsulate them in such a manner that changing Item1, for example, might affect Item2. (By contrast, changing the Arc length would require a change in one or both of the angles so encapsulation is a must.) Furthermore, there are no invalid values for the items stored within a tuple. Any validation would be enforced in the data type of the item itself, not in the assignment of one of the Item properties on the tuple.
For this reason, properties on the tuple don’t provide any value, and there’s no conceivable future value they could provide. In short, if you’re going to define a type whose data is mutable with no need for validation, you may as well use fields. Another reason you might want to leverage properties is to have varying accessibility between the getter and the setter. However, assuming mutability is acceptable, you aren’t going to take advantage of properties with
public struct Arc {
public Arc (double radius, double startAngle, double sweepAngle) {
Radius = radius; StartAngle = startAngle; SweepAngle = sweepAngle;
}
public double Radius; public double StartAngle; public double SweepAngle;
public double Length {
get {
return Math.Abs(StartAngle - SweepAngle) / 360 * 2 * Math.PI * Radius;
} }
public void Rotate(double degrees) {
StartAngle += degrees;
SweepAngle += degrees; }
// Override object.Equals
public override bool Equals(object obj) {
return (obj is Arc) && Equals((Arc)obj);
}
// Implemented IEquitable<T>
public bool Equals(Arc arc) {
return (Radius, StartAngle, SweepAngle).Equals( (arc.Radius, arc.StartAngle, arc.SweepAngle));
}
// Override object.GetHashCode public override int GetHashCode() =>
return (Radius, StartAngle, SweepAngle).GetHashCode();
public static bool operator ==(Arc lhs, Arc rhs) => lhs.Equals(rhs);
public static bool operator !=(Arc lhs, Arc rhs) => !lhs.Equals(rhs);
}
16 msdn magazine
.NET Framework