Перейти к основному содержимому

Лямбды, LINQ, операторы и свои коллекции

Разработчику

VB.NET, после процедур и событий и коллекций. Делегаты и AddressOf кратко — в главе 6.

Лямбды в VB.NET - анонимные функции

Иногда метод нужен один раз — как фильтр списка или обработчик без отдельной Function в модуле. Лямбда — анонимная функция или подпрограмма, которую передают в Where, Sort, AddHandler или в свой делегат.


Синтаксис лямбд

ФормаНазначение
Function(x) x * 2Выражение, возвращает значение
Function(x, y) x + yНесколько параметров
Sub(x) Console.WriteLine(x)Без возврата (действие)
Function(x) … End FunctionМногострочное тело
Imports System

Dim numbers = {3, 1, 4, 1, 5}
Dim doubled = numbers.Select(Function(n) n * 2)

Dim printer As Action(Of String) = Sub(text) Console.WriteLine(text)
printer("готово")

При Option Infer On тип лямбды выводится из контекста (например, из сигнатуры Select).

AddressOf и лямбда

AddressOf ИмяМетодаЛямбда
КогдаУже есть именованный методЛогика короткая и локальная
ЧитаемостьЯвная ссылка на метод формы/классаКомпактно в LINQ
События WinFormsHandles или AddHandler …, AddressOfРедко для UI
' Именованный обработчик — привычно для кнопок
Private Sub OnSave(sender As Object, e As EventArgs)
SaveData()
End Sub

' Лямбда — удобно для LINQ
Dim adults = people.Where(Function(p) p.Age >= 18)

Func и Action

Встроенные обобщённые делегаты из System:

Dim isEven As Func(Of Integer, Boolean) = Function(n) n Mod 2 = 0
Console.WriteLine(isEven(4)) ' True

Dim log As Action(Of String, Integer) = Sub(msg, code)
Console.WriteLine($"{code}: {msg}")
End Sub
log("старт", 0)

Func(Of T1, …, TResult) — последний тип параметра это результат. Action — только входы, без возврата.


LINQ — запросы к коллекциям

Подключите Imports System.Linq. LINQ работает с IEnumerable(Of T) — массивами, List, результатами чтения файлов.

Синтаксис запросов

Dim query = From p In products
Where p.Price > 1000
Order By p.Name
Select p.Name, p.Price

For Each item In query
Console.WriteLine($"{item.Name} — {item.Price}")
Next

Методы-расширения (чаще в промышленном коде)

Dim names = products.Where(Function(p) p.InStock).
OrderBy(Function(p) p.Name).
Select(Function(p) p.Name).
ToList()
МетодРоль
WhereФильтр
SelectПроекция (новая форма элемента)
OrderBy / OrderByDescendingСортировка
First, Single, Any, CountПоиск и агрегаты
ToList, ToArrayМатериализация результата

Пустая коллекция: First бросает исключение, FirstOrDefault вернёт Nothing для ссылочных типов.

Dim first = items.FirstOrDefault(Function(x) x.Id = targetId)
If first IsNot Nothing Then
' найдено
End If

LINQ к XML и Entity Framework используют тот же синтаксис From … Where … Select — см. справочник §14 и §18.


Перегрузка операторов

Класс или структура могут задать поведение +, -, = для своих типов. Оператор объявляют Shared, параметры — ByVal.

Public Structure Vector2D
Public ReadOnly X As Double
Public ReadOnly Y As Double

Public Sub New(x As Double, y As Double)
Me.X = x
Me.Y = y
End Sub

Public Shared Operator +(a As Vector2D, b As Vector2D) As Vector2D
Return New Vector2D(a.X + b.X, a.Y + b.Y)
End Operator

Public Shared Operator -(a As Vector2D, b As Vector2D) As Vector2D
Return New Vector2D(a.X - b.X, a.Y - b.Y)
End Operator
End Structure

Dim v = New Vector2D(1, 0) + New Vector2D(0, 1)

Часто перегружают: +, -, =, <>, неявное CType для преобразований. Не стоит перегружать всё подряд — только там, где тип ведёт себя как «значение» в предметной области (деньги, вектор, дата-диапазон).


Индексатор (свойство по умолчанию)

Индексатор даёт доступ объект(ключ) как у массива или словаря.

Public Class Cache
Private ReadOnly _data As New Dictionary(Of String, String)

Default Public Property Item(key As String) As String
Get
Return _data(key)
End Get
Set(value As String)
_data(key) = value
End Set
End Property
End Class

Dim c As New Cache()
c("user:1") = "Anna"
Console.WriteLine(c("user:1"))

Для чтения без исключения при отсутствии ключа внутри Get используйте TryGetValue.


Своя коллекция и For Each

For Each требует, чтобы тип реализовал IEnumerable(Of T) (или необобщённый IEnumerable). Проще всего обернуть внутренний List(Of T) и отдать перечисление через Iterator и Yield.

Imports System.Collections.Generic

Public Class TagList
Implements IEnumerable(Of String)

Private ReadOnly _items As New List(Of String)

Public Sub Add(tag As String)
If Not String.IsNullOrWhiteSpace(tag) Then
_items.Add(tag.Trim())
End If
End Sub

Public ReadOnly Property Count As Integer
Get
Return _items.Count
End Get
End Property

Public Iterator Function GetEnumerator() As IEnumerator(Of String) _
Implements IEnumerable(Of String).GetEnumerator

For Each tag In _items
Yield tag
Next
End Function
End Class

Использование:

Dim tags As New TagList()
tags.Add("vb")
tags.Add("dotnet")

For Each tag In tags
Console.WriteLine(tag)
Next

Dim upper = tags.Select(Function(t) t.ToUpper()).ToList()

Что происходит под капотом

  1. Компилятор для For Each вызывает GetEnumerator().
  2. Перечислитель возвращает элементы по одному (MoveNext / Current в классической модели; Iterator генерирует это автоматически).
  3. После цикла перечислитель освобождается (Dispose).

Если коллекция обёртка над List, можно не писать Yield, а вернуть _items.GetEnumerator() — меньше кода, та же семантика для For Each.


Перегрузка методов (напоминание)

Отдельно от операторов — несколько методов с одним именем и разными параметрами:

Public Sub Save(path As String)
Save(path, overwrite:=True)
End Sub

Public Sub Save(path As String, overwrite As Boolean)
' запись на диск
End Sub

Компилятор выбирает версию по числу и типам аргументов. Именованные аргументы (overwrite:=False) улучшают читаемость при многих необязательных параметрах.


Типичные ошибки

ОшибкаРешение
Лямбда не компилируется с Option StrictЯвно указать типы параметров: Function(p As Person)
Захват переменной цикла в лямбдеВ For Each скопировать в локальную: Dim x = item перед лямбдой, если откладываете вызов
LINQ «пустой» результатПроверять Any() или FirstOrDefault
Бесконечный IEnumerableToList() материализует; без этого запрос выполняется при каждом обходе

Что дальше


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).