vblang icon indicating copy to clipboard operation
vblang copied to clipboard

VB doesn't need a Range but a Ranger!

Open VBAndCs opened this issue 4 years ago • 4 comments

I don't like C# ranges, and I will never use them. Their roles are confusing, and I see no benefit of having a new syntax for such a trivial thing! I just wrote a Ranger class to work as a range wrapper for lists:

Public Class Ranger(Of T)
    Public ReadOnly Property List As IList(Of T)

    Private Const ErrMsg1 = "abs(index) can't be > the count of items."

    Private Const ErrMsg2 = "index can't be >= the count of  items."

    Public Sub New(list As IList(Of T))
        Me.List = list
    End Sub

    Default Public Overloads Property Item(index As Integer) As T
        Get
            Return List(FixIndex(index))
        End Get

        Set
            List(FixIndex(index)) = Value
        End Set
    End Property

    Default Public Overloads Property Item(range As Range) As T()
        Get
            Return Item(range.Start, range.End)
        End Get
        Set
            Item(range.Start, range.End) = Value
        End Set
    End Property

    Default Public Overloads Property Item(start As Integer, [end] As Integer) As T()
        Get
            fixRange(start, [end])
            Dim L = [end] - start
            Dim a(L) As T
            For i = 0 To L
                a(i) = List(start + i)
            Next
            Return a
        End Get

        Set
            fixRange(start, [end])

            Dim L = Value.Length - 1
            For i = 0 To [end] - start
                If i > L Then
                    List(start + i) = Nothing
                Else
                    List(start + i) = Value(i)
                End If
            Next
        End Set
    End Property

    Private Sub fixRange(ByRef start As Integer, ByRef [end] As Integer)
        start = FixIndex(start)
        [end] = FixIndex([end])
        If start > [end] Then
            Dim temp = start
            start = [end]
            [end] = temp
        End If
    End Sub

    Private Function FixIndex(index As Integer) As Integer
        If index < 0 Then index = List.Count + index
        If index < 0 Then
            Throw New IndexOutOfRangeException(ErrMsg1)
        ElseIf index < List.Count Then
            Return index
        Else
            Throw New IndexOutOfRangeException(ErrMsg2)
        End If

    End Function
End Class

Public Structure Range
    Public ReadOnly Property Start As Integer

    Public ReadOnly Property [End] As Integer

    Public Sub New(start As Integer, [end] As Integer)
        Me.Start = start
        Me.End = [end]
    End Sub

    Public Shared Widening Operator CType(range As (Start As Integer, [End] As Integer)) As Range
        Return New Range(range.Start, range.End)
    End Operator

End Structure

So, we can do this:

        Dim myList As New List(Of Integer) From {1, 2, 3, 4}
        Dim r As New Ranger(Of Integer)(myList)
        Dim range = r(1, -2)
        r(-4, -2) = {5, 6}
        Console.WriteLine(r(1)) ' 5
        Console.WriteLine(r(-1)) ' 4
        Console.WriteLine(r(4)) ' Error
        Console.WriteLine(r(-5)) ' Error

I prefer r(-1) over r(^1) and r(1, 2) over r(1..3). C# came up with the most unnatural syntax and made it even more confusing by that strange +1 end role!!

Of course it will be be better if .NET Core updates its lists to have this as a built in, or we can provide a new NuGet with such Ranged Lists, so, we don't have to use a wrapper a one more line of code. Either way, I can live without any ranges. I survived without them for a quarter of a century :) .

VBAndCs avatar Apr 09 '21 14:04 VBAndCs

Very nice it would be useful to have for Arrays also. Is there a way to Inherit from IList so you don't have to create the list first?

paul1956 avatar Apr 09 '21 15:04 paul1956

It works for arrays (was my first test), because Array implements the IList.

Is there a way to Inherit from IList so you don't have to create the list first?

In my first implementation, I inherited the List(Of T), so, it is doable, but this will exclude arrays and other types of IList types. A wrapper is a cheap generic solution, and if you don't need the List, you can create it on as an argument:

       Dim r As New Ranger(Of Integer)({1, 2, 3, 4})

VBAndCs avatar Apr 09 '21 15:04 VBAndCs

In fact, I hope that VB can infere the generic type param if there is no non-generic version of the class, so we can just write:

   Dim r As New Ranger({1, 2, 3, 4})

VBAndCs avatar Apr 09 '21 15:04 VBAndCs

In #556, I asked for allowing [] for arrays and indexer. After writing the Ranger, I am thinking that [ ] can differ from ( ) in that it always negative index and supports ranges. VB can just lower the [] to an instance of the Ranger class. So, we can keep the ( ) indexer intact not to break anything nor confusing anyone.

VBAndCs avatar Apr 09 '21 16:04 VBAndCs