dotnet / vblang

The home for design of the Visual Basic .NET programming language and runtime library.
290 stars 66 forks source link

VBnet's pain in the "code-hole"? #521

Open AdamSpeight2008 opened 4 years ago

AdamSpeight2008 commented 4 years ago

There are always areas of Visual Basic (.net) that are a pain in the "code-hole" to write or debug. Let's create post of them, so potential contributors can work on solving them for you.

What are yours? And the mythical code wizard, could fix by doing what instead.

tverweij commented 4 years ago

@everyone - leaving here now, you can reach me on twitter.

VBAndCs commented 4 years ago

I want a clever way to do myList(myList.Count - 1). I am not sure that myList.Last is equivilant, since IEnumerable doesn't use index to het elements, but iterates through the whole sequence! So, I want a syntax that yields exactly myList(myList.Count - 1) Noting that I don't like C# range in this matter. It confuses me. So, any suggestions?

pricerc commented 4 years ago

I want a clever way to do myList(myList.Count - 1). I am not sure that myList.Last is equivilant, since IEnumerable doesn't use index to het elements, but iterates through the whole sequence! So, I want a syntax that yields exactly myList(myList.Count - 1) Noting that I don't like C# range in this matter. It confuses me. So, any suggestions?

I think the official line is that if your using myList(myList.Count -1), then you're doing it wrong and you should be using a for each loop.

But I agree with your sentiment. List should provide a LastIndex or UpperBound property. But that's also not really a VB problem, it's a library problem.

VBAndCs commented 4 years ago

@pricerc In my use case, I only care about first and last items, as the list contains the xml nodes, and I want first to be sure that the root (the last element in the list, is my "vbxml' tag to provide html5 auto complete in XML literals:

Dim parents = token.GetAncestors(Of XmlElementSyntax)( )
If parents.Count = 0 OrElse GetStartTagName(parents(parents.Count - 1)) <> VbXmlRootName Then
                    Return Nothing
End If
pricerc commented 4 years ago

@VBAndCs That's fine.

I think your point about Last() is valid though; IEnumerable should be able to just skip to that last one if the underlying list supports it.

VBAndCs commented 4 years ago

But that's also not really a VB problem, it's a library problem.

Yes it is. C# introduces range indexers to address this. So, in C# you can use parents(^1) instead of parents(parents.Count - 1). If such a syntax comes to VB, I would prefer to use # to refer the NUMBER of elements in the collection (as ^ used in arithmetic operations), so we can subtract numbers from # to get the desired index: parents(# - 1) . In my opinion parents(# - 2) is more readable than C#'s parents(^2).

VBAndCs commented 4 years ago

I think your point about Last() is valid though; IEnumerable should be able to just skip to that last one if the underlying list supports it.

It is not easy to do, since IEnumerable is unaware of the source of the sequence. Defanging indexes in the language is easier.

Happypig375 commented 4 years ago

A Count keyword will be better than #.

Nukepayload2 commented 4 years ago

@VBAndCs I solved this problem by writing new extension functions.

Imports System.Runtime.CompilerServices

Module RangeExtensions
    <Extension>
    Function Last(Of T)(list As IReadOnlyList(Of T)) As T
        Return list.ItemFromEnd(1)
    End Function

    <Extension>
    Function ItemFromEnd(Of T)(list As IReadOnlyList(Of T), index As Integer) As T
        Return list(list.Count - index)
    End Function
End Module

Usage:

Module Program
    Sub Main()
        Dim items = {1, 2, 3, 4, 5}
        Console.WriteLine("Data")
        Console.WriteLine(String.Join(", ", items))
        Console.WriteLine("The last item")
        Console.WriteLine(items.Last)
        Console.WriteLine("The item 2 from end")
        Console.WriteLine(items.ItemFromEnd(2))
    End Sub
End Module

Output

Data
1, 2, 3, 4, 5
The last item
5
The item 2 from end
4
paul1956 commented 4 years ago

@pricerc I looked through many examples in my code and the reason I to use For i = vs. For Each is in many cases I need i, usually because I want the last 2 entries or I need i like to print "{i + 1} of {Last +1}". In the first case ^ or # in VB would be great. If VB used # it might be possible to use it within the for loop to solve the second issue. @Nukepayload2 I love the extension but my issue is that list.Count is constantly being recalculated. I don't know if the C# implementation caches this value but it should be able to since lists can't be modified within the loop.

 <Extension>
    Function ItemFromEnd(Of T)(list As IReadOnlyList(Of T), index As Integer) As T
        Return list(list.Count - index)
    End Function
pricerc commented 4 years ago

@paul1956 That's why I referred to the official line - because real life is never like the theory.

Like database theory purists say that you should not have null values. In practice, they're invaluable.

AdamSpeight2008 commented 4 years ago

In an IReadOnlyCollection(Of T) .Count is a property and isn't calculated on the fly (in most use cases). ICollection(Of T) requires an indexer, so .First and .Last would be O(1) most of the time (eg Array / List).

With a little bit of thought It is possible write and extension method on IEnumerable(Of T) to return the first and last items in a single pass (assuming it isn't an infinite enumerable source)

  <Extension>
    Function FirstAndLast(Of T)(source As IEnumerable(Of T), Optional allowSame As Boolean = True) As (First As T, Last As T)?
        Dim result As (First As T, Last As T)? = Nothing
        Dim isFirst = True
        For Each x In source
            If isFirst Then
                result = (x, If(allowSame, x, Nothing))
                isFirst = False
            Else
                result = (result.Value.First, x)
            End If
        Next
        Return result
    End Function
RevensofT commented 4 years ago

A block of pre processor so out of place, I understand it hasn't specify font color on keyword in old version but now it should be same level tab space as other line in code block.

image