简体   繁体   中英

Testing that generic type implements an interface and using that interface

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:

  1. Rename GenericUnits to IGenericUnits to follow recommended naming conventions

  2. 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM