Reputation: 16718
When attempting to use the Enumerable.Count() extension method from Visual Basic, the following code results in a compile-time error:
Imports System.Linq
Module Module1
Sub Main()
Dim l As New List(Of Foo) From {New Foo("a"), New Foo("b"), New Foo("a")}
Dim i As Integer = l.Count(Function(foo) foo.Bar = "a")
Console.WriteLine(i)
Console.ReadLine()
End Sub
Class Foo
Sub New(ByVal bar As String)
Me.Bar = bar
End Sub
Public Property Bar As String
End Class
End Module
The error produced is:
'Public ReadOnly Property Count As Integer' has no parameters and its return type cannot be indexed.
I'm targeting .NET 4.0, so extension methods should be supported. It's also worth noting that the equivalent code in C# infers the extension method correctly...
Why is the compiler unable to infer the use of Enumerable.Count, given the predicate I'm passing as an argument, and how can I use the extension method instead of the List's Count property?
Upvotes: 10
Views: 1394
Reputation: 1894
To answer your question as to why VB can't do what C# can in this case...
VB lets you access properties with ()
after the name, and also lets you call functions with no parameters by omitting the ()
. Also indexers use rounded brackets, instead of square brackets you have in C#. These are examples of tremendous VB features designed to make programming easier, which actually results in more ambiguous, harder to understand, and bug prone code.
So, in this particular case, VB sees you are accessing Count, and assumes the brackets after it are an indexer to the Count property, rather than arguments to the Count function.
C# sees the rounded brackets, and realises that you aren't accessing the indexer, you must be calling a function, so looks for a function.
Of course, there's room for ambiguity in C# as well. For example, a property with the same name as an extension method, which returns a delegate type will be called in preference to the extension method...
public Action Count { get; set; }
Ah... happy days.
As to how to call the IEnumerable.Count() function, a cast (preferably DirectCast()
) or executing the extension method directly Enumerable.Count(...)
, is far far preferable to creating a whole new array to call count on...!
Upvotes: 2
Reputation: 101122
The VB.Net compiler first tries to look up Count
on the List
instance, and it finds the Count
property. This property is used instead of the extension method since fields and properties always shadow extension methods by name. I don't know where this is stated in the Visual Basic Language spec, but you can read more in this MSDN Magazin article:
Fields and properties always shadow extension methods by name. Figure 4 shows an extension method and public field with the same name and various calls. Though the extension method contains a second argument, the field shadows the extension method by name and all calls using this name result in accessing the field. The various overloaded calls will all compile, but their results at run time may be unexpected since they will bind to the property and use the default property behavior to return a single character or result in a runtime exception. It is important to choose the names of your extension methods so you avoid clashes with properties, fields, and existing instance methods.
So, Count(Function(foo) foo.Bar = "a")
could mean: call the Count
-property with Function(foo) foo.Bar = "a"
, or take the result of the Count
-property and index it with Function(foo) foo.Bar = "a"
, which could be totally valid, since indexed properties in VB.Net can take any parameter.
This works in C# (I guess) because it is easier for the C# compiler to distinguish between method calls and a property access, because unlike VB.Net C# does not allow arbitrary parameters on properties and indexed properties.
To use the extension method, you call it like you would call every other static (shared) method:
Dim i As Integer = Enumerable.Count(l, Function(foo) foo.Bar = "a")
or call Call
on IEnumerable
explicitly:
Dim i As Integer = l.AsEnumerable().Count(Function(foo) foo.Bar = "a")
Upvotes: 7
Reputation: 11773
If the list is converted to an array it works
Dim l As New List(Of Foo) From {New Foo("a"), New Foo("b"), New Foo("a")}
Dim i As Integer = l.ToArray.Count(Function(x) x.Bar = "a")
Upvotes: 1
Reputation: 6155
I'm not sure as to why you aren't getting the overload as an option, but you should be able to cast the list to IEnumerable(Of Foo)
at which point the compiler will no longer allow List(Of Foo).Count
property.
CType(l, IEnumerable(Of Foo)).Count(Function(foo) foo.Bar = "a")
Upvotes: 2