Note that there are some explanatory texts on larger screens.

plurals
  1. POPattern for Creating a Simple and Efficient Value type
    primarykey
    data
    text
    <p><strong>Motivation:</strong></p> <p>In reading Mark Seemann’s blog on <a href="http://blog.ploeh.dk/2011/05/26/CodeSmellAutomaticProperty.aspx" rel="noreferrer">Code Smell: Automatic Property</a> he says near the end:</p> <blockquote> <p>The bottom line is that automatic properties are rarely appropriate. In fact, they are only appropriate when the type of the property is a value type and all conceivable values are allowed.</p> </blockquote> <p>He gives <code>int Temperature</code> as an example of a bad smell and suggests the best fix is unit specific value type like Celsius. So I decided to try writing a custom Celsius value type that encapsulates all the bounds checking and type conversion logic as an exercise in being more <a href="http://en.wikipedia.org/wiki/Solid_%28object-oriented_design%29" rel="noreferrer">SOLID</a>.</p> <p><strong>Basic requirements:</strong></p> <ol> <li>Impossible to have an invalid value</li> <li>Encapsulates conversion operations</li> <li>Effient coping (equivalent to the int its replacing) </li> <li>As intuitive to use as possible (trying for the semantics of an int)</li> </ol> <p><strong>Implementation:</strong></p> <pre><code>[System.Diagnostics.DebuggerDisplay("{m_value}")] public struct Celsius // : IComparable, IFormattable, etc... { private int m_value; public static readonly Celsius MinValue = new Celsius() { m_value = -273 }; // absolute zero public static readonly Celsius MaxValue = new Celsius() { m_value = int.MaxValue }; private Celsius(int temp) { if (temp &lt; Celsius.MinValue) throw new ArgumentOutOfRangeException("temp", "Value cannot be less then Celsius.MinValue (absolute zero)"); if (temp &gt; Celsius.MaxValue) throw new ArgumentOutOfRangeException("temp", "Value cannot be more then Celsius.MaxValue"); m_value = temp; } public static implicit operator Celsius(int temp) { return new Celsius(temp); } public static implicit operator int(Celsius c) { return c.m_value; } // operators for other numeric types... public override string ToString() { return m_value.ToString(); } // override Equals, HashCode, etc... } </code></pre> <p><strong>Tests:</strong></p> <pre><code>[TestClass] public class TestCelsius { [TestMethod] public void QuickTest() { Celsius c = 41; Celsius c2 = c; int temp = c2; Assert.AreEqual(41, temp); Assert.AreEqual("41", c.ToString()); } [TestMethod] public void OutOfRangeTest() { try { Celsius c = -300; Assert.Fail("Should not be able to assign -300"); } catch (ArgumentOutOfRangeException) { // pass } catch (Exception) { Assert.Fail("Threw wrong exception"); } } } </code></pre> <p><strong>Questions:</strong></p> <ul> <li><strong>Is there a way to make MinValue/MaxValue const instead of readonly?</strong> Looking at the BCL I like how the meta data definition of <a href="http://msdn.microsoft.com/en-us/library/system.int32.aspx" rel="noreferrer">int</a> clearly states MaxValue and MinValue as compile time constants. How can I mimic that? I don’t see a way to create a Celsius object without either calling the constructor or exposing the implementation detail that Celsius stores an int.</li> <li><strong>Am I missing any usability features?</strong></li> <li><strong>Is there a better pattern for creating a custom single field value type?</strong></li> </ul>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload