I'm building an app that uses measurements of physical properties. Initially, I used a whole bunch of variables of type 'double' and kept track of the values in Hungarian notation or in my head:
Public Class ResultData
Public Property position_in_mm_vs_time_in_ms as Double(,)
Public Property initial_position_in_mm as Double
Public Property final_position_in_mm as Double
Public Property duration_in_ms as Double
Public Property speed_in_mm_per_s as Double
End Class
but as I added more items and more properties this quickly became a mess, with conversion factors sprinkled about, no way of knowing if a value was in meters or millamps, no way of knowing what the proper abbreviation for an item was without hard-coding and manual tracing, and the idea of adding an option to input and output data in SI or Imperial units was terrifying.
I realized that this problem was a typing problem, and that I could use a class with a type and a value to improve this arrangement:
Namespace Measure
Public Class Value(Of GenericUnits)
Public Property Value As Double
End Class
Public Class ValuePoint(Of XUnits, YUnits)
Public X As Value(Of XUnits)
Public Y As Value(Of YUnits)
Public Sub New(x As Value(Of XUnits), y As Value(Of YUnits))
Me.X = x
Me.Y = y
End Sub
End Class
Public Class Units
Public Interface GenericUnits
ReadOnly Property Abbreviation As String
' Additional properties, operators, and conversion functions
End Interface
' Additional unit types
End Class
End Namespace
so my declaration became:
Public Class ResultData
Public Property PositionData as List(of ValuePoint(of Units.Seconds, Units.Millimeters))
Public Property InitialPosition as Value(of Units.Millimeters)
Public Property FinalPosition as Value(of Units.Millimeters)
Public Property Duration as Value(of Units.Milliseconds)
Public Property Speed as Value(of Units.MillimetersPerSecond)
End Class
which is really nice and clean. Now I want to use the properties and conversions defined by my operators, but I can't:
Dim result As New ResultData()
Dim msg As New System.Text.StringBuilder()
msg.AppendLine("Speed units are abbreviated as: ")
msg.AppendLine(result.Speed.GetType().ToString() & "?")
msg.AppendLine(result.Speed.GetType().GenericTypeArguments(0).ToString() & "?")
' Produces error "Abbreviation is not a member of System.Type"
' Casting produces conversion error
'msg.AppendLine(result.Speed.GetType().GenericTypeArguments(0).Abbreviation & "?")
' Produces:
' Speed units are abbreviated as:
' Measure.Value`1[Measure.Units+MillimetersPerSecond]
' Measure.Units+MillimetersPerSecond
MsgBox(msg.ToString())
How can I access the properties and methods of my type declaration?
I have found that my declaration Value(Of GenericUnits)
doesn't actually reference the interface called GenericUnits
, and instead produces a generic type; I might as well call it Value(Of T)
. I think this might be related to my problem.
You need to constrain your generic to derive from your interface:
Public Class Measurement(Of T as IGenericUnits) 'class name was Value before edit
Public Property Value As Double
Public Property UOM As IGenericUnits 'UOM is a common abbreviation for unit-of-measure
End Class
See here for addition info on generics and constraints: http://msdn.microsoft.com/en-us/library/w256ka79.aspx
Edit:
Usage would be as follows:
msg.AppendLine(result.Speed.UOM.Abbreviation)
A couple of recommendations:
Rename GenericUnits
to IGenericUnits
to follow recommended naming conventions
Find a better name than Value" for your measurement class.
Value is used all over the place and you are bound to run into a naming conflict eventually. Perhaps
Measurement or
MeasuredValue` instead.
I'd mostly skip the generics here. The whole point of what you want to do is create type-safe containers for each of your units: millimeters, milliamps, etc. You want to make it impossible to use a millimeter in a calculation that expects a milliamp. If you have collections that are generic Values, this mistake can still happen.
What I would do is still define a common interface for your measures. But including the Value should be part of the interface. I'd still implement a specific type for things like milliamp and millimeter. These types might also have implicit or explicit conversions built in. But then I would use the existing generic collections and types already included with .Net, rather than building any new ones. More important, when using the types, I would ask for the final, concrete types and implement the interface, and avoid asking for the interface itself:
Public Interface IUnit
ReadOnly Property Abbreviation As String
Property Value As Double
End Interface
Public Class MilliAmps Implements IUnit
Public ReadOnly Property Abbreviation As String Implements IUnit.Abbreviation
Get
Return "ma"
End Get
End Property
Public Property Value As Double Implements IUnit.Value
End Class
Public Class Millimeters Implements IUnit
Public ReadOnly Property Abbreviation As String Implements IUnit.Abbreviation
Get
Return "mm"
End Get
End Property
Public Property Value As Double Implements IUnit.Value
End Class
Public Class Seconds ...
Public Class MillimetersPerSecond ...
Public Class ResultData
Public Property PositionData As List(Of Tuple(Of Seconds, Millimeters))
Public Property InitialPosition As Millimeters
Public Property FinalPosition As Millimeters
Public Property Duration As Milliseconds
Public Property Speed As MillimetersPerSecond
End Class
I might even be tempted to use a full abstract base class, rather than an interface, because it would allow me to avoid re-implementing the same value property over and over again.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.