Page 25 - MSDN Magazine, June 2018
P. 25

parameter is similar to passing a group of arguments up the stack for a method call. In other words, return tuples are symmetric with individual parameter lists as far as memory copies are concerned.
If you declared the tuple as a reference type, then it would be necessary to construct the type on the heap and initialize it with the Item values—potentially copying either a value or reference to the heap. Either way, a memory copy operation is required, similar to that of a value type’s memory copy. Furthermore, at some later point in time when the reference tuple is no longer accessible, the garbage collector will need to recover the memory. In other words, a reference tuple still involves memory copying, as well as additional pressure on the garbage collector, making a value type tuple the more efficient option. (In the rare cases that a value tuple isn’t more efficient, you could still resort to the reference type version, Tuple<...>.)
While completely orthogonal to the main topic of the arti- cle, notice the implementation of Equals and GetHashCode in Figure 1. You can see how tuples provide a shortcut for imple- menting Equals and GetHashCode. For more information, see “Using Tuples to Override Equality and GetHashCode.”
Wrapping Up
At first glance it can seem surprising for tuples to be defined as immutable value types. After all, the number of immutable value types found in .NET Core and the .NET Framework is minimal, and there are long-standing programming guidelines that call for value types to be immutable and encapsulated with properties. There’s also the influence of the immutable-by-default approach characteristic to F#, which pressured C# language designers to pro- vide a shorthand to either declare immutable variables or define immutable types. (While no such shorthand is currently under consideration for C# 8.0, read-only structs were added to C#7.2 as a means to verify that a struct was immutable.)
In the end, guidelines are just that, guidelines.
However, when you delve into the details, you see a number of important factors. These include:
• Reference types impose an additional performance impact with garbage collection.
• Tuples are generally ephemeral.
• Tuple items have no foreseeable need for encapsulation
with properties.
• Even tuples that are large (by value type guidelines) don’t
have significant memory copy operations beyond that of a
reference tuple implementation.
In summary, there are plenty of factors that favor a value type
tuple with public fields in spite of the standard guidelines. In the end, guidelines are just that, guidelines. Don’t ignore them, but given sufficient—and I would suggest, explicitly documented— cause, it’s OK to color outside the lines on occasion. msdnmagazine.com
Using Tuples to Override Equality and GetHashCode
In the past, the implementation of Equals and GetHashCode were fairly complex, yet the actual code is generally boilerplate. For Equals, it’s necessary to compare all the contained identifying data structures, while avoiding infinite recursion or null reference exceptions. For GetHashCode, it’s necessary to combine the unique hash code of each of the non-null contained identifying data structures in an exclusive OR operation. With C# 7.0 tuples, this turns out to be quite simple, as is demonstrated in Figure 1.
For Equals, one member can check that the type is the same, while a second member groups each of the identifying members into a tuple and compares them to the target parameter of the same type, like this:
public override bool Equals(object obj) => return (obj is Arc)
&& Equals((Arc)obj);
public bool Equals(Arc arc) =>
return (Radius, StartAngle, SweepAngle).Equals(
(arc.Radius, arc.StartAngle, arc.SweepAngle));
You might argue that the second function could be more readable if each identifying member were explicitly compared instead, but I leave that for the reader to arbitrate. That said, note that internally the tuple (System.ValueTuple<...>) uses EqualityComparer<T>, which relies on the type parameters implementation of IEquatable<T>, which only contains a single Equals<T>(T other) member. Therefore, to correctly override Equals, you need to follow the guideline: “Do implement IEquatable<T> when overriding Equals.” That way your own custom data types will leverage your custom implementation of Equals rather than Object.Equals.
Perhaps the more compelling of the two overloads is GetHashCode and its use of the tuple. Rather than engage in the complex gymnastics of an exclusive OR operation of the non-null identifying members, you can simply instantiate a tuple of all identifying members and return the GetHashCode value for the said tuple, like so:
public override int GetHashCode() =>
return (Radius, StartAngle, SweepAngle).GetHashCode();
Nice!!!
For more information on the guidelines for both defining value types and overriding Equals and GetHashCode, check out chapters 9 and 10 in my Essential C# book: “Essential C# 7.0” (IntelliTect.com/EssentialCSharp), which is expected to be out in May.n
Mark Michaelis is founder of IntelliTect, where he serves as its chief technical architect and trainer. For nearly two decades he has been a Microsoft MVP, and has been a Microsoft Regional Director since 2007. Michaelis serves on sev- eral Microsoft software design review teams, including C#, Microsoft Azure, SharePoint and Visual Studio ALM. He speaks at developer conferences and has written numerous books including his most recent, “Essential C# 6.0 (5th Edition)” (itl.tc/EssentialCSharp). Contact him on Facebook at facebook.com/ Mark.Michaelis, on his blog at IntelliTect.com/Mark, on Twitter: @markmichaelis or via e-mail at mark@IntelliTect.com.
Thanks to the following Microsoft technical experts for reviewing this article: Kevin Bost, Eric Lippert, Mads Torgersen
June 2018 19


































































































   23   24   25   26   27