3

First of all, what I want to achieve:

I want to extend a value datatype by providing additional properties, especially to validate ranges provided at declaration time. I want the new datatype to be a value type as well.

Compare with Ada:

subtype Day_Number is Integer range 1 .. 31;

Ideal, but obviously not implementable, would be:

Dim DayNumber As Int64 Range 1 To 31

However, I would be happy with:

Dim DayNumber As RangeInt64(1, 31)

It is of no concern, if initialization takes its time. Once ranges are provided, they are considered to be immutable. The datatype from then on is only used to set/get values like with ordinary value types, only that they are subject of being validated against the initially provided range.

My attempt:

Since I cannot inherit from structures in order to expand on them, I tried to incorporate a structure into a structure as a member.

In a module, I have this structure:

Friend Structure SRangeValueType(Of T)
    Private lMinimum As T
    Private lMaximum As T

    Friend Property Minimum As T
        Get
            Return lMinimum
        End Get
        Set(tValue As T)
            lMinimum = tValue
        End Set
    End Property

    Friend Property Maximum As T
        Get
            Return lMaximum
        End Get
        Set(tValue As T)
            lMaximum = tValue
        End Set
    End Property

    Friend Sub New(Minimum As T, Maximum As T)
        lMinimum = Minimum
        lMaximum = Maximum
    End Sub
End Structure

I attempt to use this generic structure as a member of another structure (of concrete type Int64):

Public Structure RangeInt64
    Private Range As SRangeValueType(Of Int64)
End Structure

However, this is not using the constructor with the two arguments.

Say I want to initialize Range (the only member of the structure RangeInt64) with the values 100 and 200 for Minimum and Maximum, resp.

I am not allowed to use something like:

    Private Range As SRangeValueType(Of Int64)(100,200)

What is the correct syntax to provide my values to the generic constructor?

  • 1
    Add `New` so, `Private Range As New SRangeValueType(Of Int64)(100, 200)`... IMHO as well, you need to look at your setters, currently they are not set correctly...For example `lMinimum = Value` should be `lMinimum = tValue`. – Trevor Apr 08 '19 at 13:39
  • If you want to invoke a constructor then you have to explicitly use the `New` keyword. If `RangeInt64` was a class then you could use `Private Range As New SRangeValueType(Of Int64)(100, 200)` but you can't initialise structure fields that way, so you're basically out of luck. If you insist on using a structure then you have to call a method to initialise a field. Quite simply, you should probably be using classes rather than structures. – jmcilhinney Apr 08 '19 at 13:41
  • 1
    @jmcilhinney Why do you say you can't do that with a structure? You can create non-default constructors for structures. Take `Dim x As New Point(1, 2)` for example... – Steven Doggart Apr 08 '19 at 13:43
  • 1
    @StevenDoggart, at no point did I say that you can't declare a constructor for a structure. What I said was that you can't initialise a field of a structure where it is declared. I specifically said that you can't use `Private Range As New SRangeValueType(Of Int64)(100, 200)` in `RangeInt64` because `RangeInt64` is a structure. If `RangeInt64` was a class then that line would be perfectly legal. I'm saying that `RangeInt64` should be a class for that reason and `SRangeValueType` should be a class because it is mutable. – jmcilhinney Apr 08 '19 at 13:47
  • *"I want a value type, not a reference type"*. And I want a million dollars. – jmcilhinney Apr 08 '19 at 13:48
  • @jmcilhinney Ah. I missed the part where he was doing that from within another structure. Sorry :) – Steven Doggart Apr 08 '19 at 14:19
  • @jmcilhinney, turns out that the workarounds and restrictions are too many to insist on a value type, so I converted this into a class, exactly as you tried to convince me right from the start. –  Apr 11 '19 at 07:13

1 Answers1

-1

Normally, if you add a constructor to a structure, you can call it using the New keyword:

Dim x As SRangeValueType(Of Int64)  ' Calls the default, infered, parameter-less constructor
Dim y As New SRangeValueType(Of Int64)(100, 200)  ' Calls the explicitly defined constructor

However, that's not really the problem. The problem is that you are trying to set the default value of a non-shared field in a structure. That is something which is never allowed. All non-shared fields in structures must default to their default value (i.e. Nothing). For instance:

Public Structure RangeInt64
    Private x As Integer = 5  ' Error: Initializers on structure members are valid only for 'Shared' members and constants
    Private y As New StringBuilder()  ' Error: Non-shared members in a structure cannot be declared 'New'
End Structure

And, as you may already know, you cannot override the default, inferred, parameter-less constructor on a structure either:

Public Structure RangeInt64
    Public Sub New()  ' Error: Structures cannot declare a non-shared 'Sub New' with no parameters
        x = 5
        y = New StringBuilder()
    End Sub

    Private x As Integer
    Private y As StringBuilder
End Structure

As such, you are stuck. By design, when the default constructor is used, all fields in the structure must always default to Nothing. However, if you really, really need it to be a structure, and you can't just convert it to a class, and you really need to change it's default value, you could theoretically fake it into working by using a property to wrap the field:

Public Structure RangeInt64
    Private _Range As SRangeValueType(Of Int64)
    Private _RangeInitialized As Boolean
    Private Property Range As SRangeValueType(Of Int64)
        Get
            If Not _RangeInitialized Then
                _Range = New SRangeValueType(Of Int64)(100, 200)
                _RangeInitialized = True
            End If
            Return _Range
        End Get
        Set(value As SRangeValueType(Of Int64))
            _Range = value
        End Set
    End Property
End Structure

It should go without saying, though, that it's pretty gross and should be avoided if possible.

Update

Now that you've provided more details about what you are trying to accomplish, I think I may have a better solution for you. You're right that structures do not support inheritance, but what they do support is interfaces. So, if all you need is a bunch of range types, one per value-type, all having the same minimum and maximum properties, but all returning different predetermined values, then you could do something like this:

Private Interface IRangeValueType(Of T)
    ReadOnly Property Minimum As T
    ReadOnly Property Maximum As T
End Interface

Private Structure RangeInt64
    Implements IRangeValueType(Of Int64)

    Public ReadOnly Property Minimum As Long Implements IRangeValueType(Of Int64).Minimum
        Get
            Return 100
        End Get
    End Property

    Public ReadOnly Property Maximum As Long Implements IRangeValueType(Of Int64).Maximum
        Get
            Return 200
        End Get
    End Property
End Structure
Steven Doggart
  • 43,358
  • 8
  • 68
  • 105
  • 1
    You forgot to set `_RangeInitialized` in your getter. Also, the property should be `Public`. – jmcilhinney Apr 08 '19 at 14:21
  • Oops. Fixed. I didn't make it public because it wasn't public in the example given in the question. – Steven Doggart Apr 08 '19 at 14:29
  • *"it wasn't public in the example given in the question"*. Good point, although it seems odd that that would be the case. I imagine that there's more to the code that we're not being shown, which seems to strengthen the case for using classes. If that field really is only being used internally then setting it after creation of the object isn't a problem anyway. – jmcilhinney Apr 08 '19 at 14:34
  • @jmcilhinney, I updated my question with the motivation for asking it, perhaps there is some other approach using a value type for my intention. - On another note, why was this answer downvoted? The explanations and the provision of a work-around seem meaningful.. –  Apr 09 '19 at 08:03
  • 1
    @Herb I updated my answer, in response to the updates to your question. As far as why my answer was down-voted, I assume it's just because someone thought my ugly workaround was too ugly and that I shouldn't have even mentioned it. The way that workaround is implemented is certainly unexpected behavior, which is almost always inadvisable, and it's a little less efficient on every read, but I don't believe there's anything inherently dangerous about it, otherwise I wouldn't have shared it. – Steven Doggart Apr 09 '19 at 12:15
  • 1
    The rule of thumb is to [never make structs that are mutable](https://stackoverflow.com/questions/441309/why-are-mutable-structs-evil). However, in the case of my workaround, since an uninitialized struct and an an initialized struct would be indistinguishable to consuming code, it would never cause unintended results, so it's kind of an exception to the rule. Regardless, I knew the workaround would make people uneasy, but I figured my answer as a whole would still be useful to you, so I chose not to delete it. – Steven Doggart Apr 09 '19 at 12:19