I've been using the System.Collections.Generic namespace an awful lot lately. I really like it, too. I use the List<T> collection all the time. It is so much better than inheriting from the CollectionBase to get a type-safe collection. I won't go into the reasons here, though. This post is about Predicates. If you have a List<T> collection, you'll notice it exposes a Find method that takes a Predicate as an argument. A Predicate is just a delegate that takes an object of type T and returns a boolean. When you call the Find method, it will call your Predicate for each object in its collection until your Predicate returns true. Looks like a nice, clean way to search collections, right?
Well, if you're using C# anyway.
Compare the following VB.NET code and C# code:
VB.NET :
1 Module Module1
2 Sub Main()
3 Dim tests As New List(Of Test)
4 tests.Add(New Test("test"))
5 tests.Add(New Test("test2"))
6 tests.Add(New Test("test3"))
7
8 toSearchFor = "test2"
9 Console.WriteLine(tests.Find(AddressOf FindTest).ID)
10 End Sub
11
12 Private toSearchFor As String = ""
13 Private Function FindTest(ByVal val As Test) As Boolean
14 Return val.ID = toSearchFor
15 End Function
16
17 Private Class Test
18 Public ID As String
19 Public Sub New(ByVal id As String)
20 Me.ID = id
21 End Sub
22 End Class
23 End Module
C# :
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace CSPredicateExample
6 {
7 class Program
8 {
9 static void Main(string[] args)
10 {
11 List<Test> tests = new List<Test>();
12 tests.Add(new Test("test"));
13 tests.Add(new Test("test2"));
14 tests.Add(new Test("test3"));
15
16 string toSearchFor = "test2";
17 Console.WriteLine(tests.Find(delegate(Test val) { return val.ID == toSearchFor; }).ID);
18 }
19
20 class Test
21 {
22 public string ID;
23 public Test(string id)
24 {
25 this.ID = id;
26 }
27 }
28 }
29 }
I've bolded the important part. In case you missed it, it is the toSearchFor variable. The only way for a predicate to find a value is to compare it to another value; however, there is no way to pass this value to the predicate outside of an external variable. In C#, this isn't a problem because of closures. The anonymous delegate (my predicate) is a closure, which means it can access variables in the outside scope. VB.NET does not have this feature (yet!). Because of this limitation in VB.NET, you have to have a class-level variable or global variable to effectively use predicates so that your predicate has something to compare to! It seems to me that the lack of closures effectively cripples the use of predicates in VB.
Because of features like this, I am really looking forward to the next release of VB. Of course, with LINQ, will I even care about predicates then?
I would be very interested to know if anyone has any other suggestions on ways to effectively use the predicate model in VB.
UPDATE: Wow, go
Paul Stovell! Instead of complaining like I did, Paul actually did something to get around this limitation in the current version of VB.NET and created an implementation of
"Almost-anonymous" methods! His method also shows how to use the Widening overloaded operator in VB.NET, which I haven't seen much of. Cool!