Bagikan melalui


Gambaran umum LINQ

Language-Integrated Query (LINQ) menyediakan kemampuan kueri tingkat bahasa, dan fungsi urutan yang lebih tinggi API ke C# dan Visual Basic, yang memungkinkan Anda menulis kode deklaratif ekspresif.

Sintaks kueri tingkat bahasa

Ini adalah sintaks kueri tingkat bahasa:

var linqExperts = from p in programmers
                  where p.IsNewToLINQ
                  select new LINQExpert(p);
Dim linqExperts = From p in programmers
                  Where p.IsNewToLINQ
                  Select New LINQExpert(p)

Ini adalah contoh yang sama menggunakan API IEnumerable<T>:

var linqExperts = programmers.Where(p => p.IsNewToLINQ)
                             .Select(p => new LINQExpert(p));
Dim linqExperts = programmers.Where(Function(p) p.IsNewToLINQ).
                             Select(Function(p) New LINQExpert(p))

LINQ bersifat ekspresif

Bayangkan Anda memiliki daftar hewan peliharaan, tetapi ingin mengubahnya menjadi dictionary di mana Anda dapat mengakses hewan peliharaan langsung berdasarkan nilai RFID.

Ini adalah kode imperatif tradisional:

var petLookup = new Dictionary<int, Pet>();

foreach (var pet in pets)
{
    petLookup.Add(pet.RFID, pet);
}
Dim petLookup = New Dictionary(Of Integer, Pet)()

For Each pet in pets
    petLookup.Add(pet.RFID, pet)
Next

Niat di balik kode bukan untuk membuat Dictionary<int, Pet> baru dan menambahkannya melalui perulangan, itu adalah untuk mengonversi daftar yang ada menjadi kamus! LINQ mempertahankan niat sedangkan kode imperatif tidak.

Ini adalah ekspresi LINQ yang setara:

var petLookup = pets.ToDictionary(pet => pet.RFID);
Dim petLookup = pets.ToDictionary(Function(pet) pet.RFID)

Kode yang menggunakan LINQ sangat berharga karena meratakan bidang bermain antara niat dan kode saat penalaran sebagai programmer. Bonus lain adalah keberringkasan kode. Bayangkan mengurangi sebagian besar basis kode sebesar 1/3 seperti yang dilakukan di atas. Kesepakatan yang manis, kan?

Penyedia LINQ menyederhanakan akses data

Untuk banyak perangkat lunak, semuanya berfokus pada penanganan data dari berbagai sumber (Database, JSON, XML, dan sebagainya). Seringkali ini melibatkan pembelajaran API baru untuk setiap sumber data, yang dapat mengganggu. LINQ menyederhanakan ini dengan mengabstraksi elemen umum akses data ke dalam sintaks kueri yang terlihat sama terlepas dari sumber data mana yang Anda pilih.

Ini menemukan semua elemen XML dengan nilai atribut tertentu:

public static IEnumerable<XElement> FindAllElementsWithAttribute(XElement documentRoot, string elementName,
                                           string attributeName, string value)
{
    return from el in documentRoot.Elements(elementName)
           where (string)el.Element(attributeName) == value
           select el;
}
Public Shared Function FindAllElementsWithAttribute(documentRoot As XElement, elementName As String,
                                           attributeName As String, value As String) As IEnumerable(Of XElement)
    Return From el In documentRoot.Elements(elementName)
           Where el.Element(attributeName).ToString() = value
           Select el
End Function

Menulis kode untuk melintasi dokumen XML secara manual untuk melakukan tugas ini akan jauh lebih menantang.

Berinteraksi dengan XML bukanlah satu-satunya hal yang dapat Anda lakukan dengan Penyedia LINQ. Linq ke SQL adalah Pemeta Object-Relational (ORM) yang cukup telanjang untuk Database Server MSSQL. Pustaka Json.NET menyediakan traversal dokumen JSON yang efisien melalui LINQ. Selain itu, jika tidak ada pustaka yang melakukan apa yang Anda butuhkan, Anda juga dapat menulis Penyedia LINQ Anda sendiri!

Alasan untuk menggunakan sintaks kueri

Mengapa menggunakan sintaks kueri? Ini adalah pertanyaan yang sering muncul. Bagaimanapun, kode berikut:

var filteredItems = myItems.Where(item => item.Foo);
Dim filteredItems = myItems.Where(Function(item) item.Foo)

jauh lebih ringkas daripada ini:

var filteredItems = from item in myItems
                    where item.Foo
                    select item;
Dim filteredItems = From item In myItems
                    Where item.Foo
                    Select item

Bukankah sintaks API hanya cara yang lebih ringkas untuk melakukan sintaks kueri?

Tidak. Sintaks kueri memungkinkan penggunaan klausa let, yang memungkinkan Anda mendefinisikan dan mengikat variabel dalam cakupan ekspresi, serta menggunakannya dalam bagian ekspresi selanjutnya. Mereprodurasi kode yang sama hanya dengan sintaks API yang dapat dilakukan, tetapi kemungkinan besar akan menyebabkan kode yang sulit dibaca.

Jadi ini memunculkan pertanyaan, haruskah Anda hanya menggunakan sintaks kueri?

Jawaban atas pertanyaan ini adalah ya jika:

  • Basis kode Anda yang sudah ada sudah menggunakan sintaks kueri.
  • Anda perlu mencakup variabel dalam kueri Anda karena kompleksitas.
  • Anda lebih suka sintaks kueri dan tidak akan mengalihkan perhatian dari basis kode Anda.

Jawaban atas pertanyaan ini adalah tidak jika...

  • Basis kode yang ada sudah menggunakan sintaks API
  • Anda tidak perlu mencakup variabel dalam kueri Anda
  • Anda lebih suka sintaks API dan tidak akan mengalihkan perhatian dari basis kode Anda

LINQ Esensial

Contoh berikut adalah demonstrasi cepat dari beberapa potongan penting LINQ. Ini sama sekali tidak komprehensif, karena LINQ menyediakan lebih banyak fungsionalitas daripada apa yang ditampilkan di sini.

Roti dan mentega - Where, Select, dan Aggregate

// Filtering a list.
var germanShepherds = dogs.Where(dog => dog.Breed == DogBreed.GermanShepherd);

// Using the query syntax.
var queryGermanShepherds = from dog in dogs
                          where dog.Breed == DogBreed.GermanShepherd
                          select dog;

// Mapping a list from type A to type B.
var cats = dogs.Select(dog => dog.TurnIntoACat());

// Using the query syntax.
var queryCats = from dog in dogs
                select dog.TurnIntoACat();

// Summing the lengths of a set of strings.
int seed = 0;
int sumOfStrings = strings.Aggregate(seed, (partialSum, nextString) => partialSum + nextString.Length);
' Filtering a list.
Dim germanShepherds = dogs.Where(Function(dog) dog.Breed = DogBreed.GermanShepherd)

' Using the query syntax.
Dim queryGermanShepherds = From dog In dogs
                          Where dog.Breed = DogBreed.GermanShepherd
                          Select dog

' Mapping a list from type A to type B.
Dim cats = dogs.Select(Function(dog) dog.TurnIntoACat())

' Using the query syntax.
Dim queryCats = From dog In dogs
                Select dog.TurnIntoACat()

' Summing the lengths of a set of strings.
Dim seed As Integer = 0
Dim sumOfStrings As Integer = strings.Aggregate(seed, Function(partialSum, nextString) partialSum + nextString.Length)

Meratakan daftar

// Transforms the list of kennels into a list of all their dogs.
var allDogsFromKennels = kennels.SelectMany(kennel => kennel.Dogs);
' Transforms the list of kennels into a list of all their dogs.
Dim allDogsFromKennels = kennels.SelectMany(Function(kennel) kennel.Dogs)

Union antara dua himpunan (dengan pembanding khusus)

public class DogHairLengthComparer : IEqualityComparer<Dog>
{
    public bool Equals(Dog a, Dog b)
    {
        if (a == null && b == null)
        {
            return true;
        }
        else if ((a == null && b != null) ||
                 (a != null && b == null))
        {
            return false;
        }
        else
        {
            return a.HairLengthType == b.HairLengthType;
        }
    }

    public int GetHashCode(Dog d)
    {
        // Default hashcode is enough here, as these are simple objects.
        return d.GetHashCode();
    }
}
...

// Gets all the short-haired dogs between two different kennels.
var allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, new DogHairLengthComparer());
Public Class DogHairLengthComparer
  Inherits IEqualityComparer(Of Dog)

  Public Function Equals(a As Dog,b As Dog) As Boolean
      If a Is Nothing AndAlso b Is Nothing Then
          Return True
      ElseIf (a Is Nothing AndAlso b IsNot Nothing) OrElse (a IsNot Nothing AndAlso b Is Nothing) Then
          Return False
      Else
          Return a.HairLengthType = b.HairLengthType
      End If
  End Function

  Public Function GetHashCode(d As Dog) As Integer
      ' Default hashcode is enough here, as these are simple objects.
      Return d.GetHashCode()
  End Function
End Class

...

' Gets all the short-haired dogs between two different kennels.
Dim allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, New DogHairLengthComparer())

Persimpangan antara dua set

// Gets the volunteers who spend share time with two humane societies.
var volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
                                                     new VolunteerTimeComparer());
' Gets the volunteers who spend share time with two humane societies.
Dim volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
                                                     New VolunteerTimeComparer())

Pemesanan

// Get driving directions, ordering by if it's toll-free before estimated driving time.
var results = DirectionsProcessor.GetDirections(start, end)
              .OrderBy(direction => direction.HasNoTolls)
              .ThenBy(direction => direction.EstimatedTime);
' Get driving directions, ordering by if it's toll-free before estimated driving time.
Dim results = DirectionsProcessor.GetDirections(start, end).
                OrderBy(Function(direction) direction.HasNoTolls).
                ThenBy(Function(direction) direction.EstimatedTime)

Kesetaraan properti instans

Terakhir, sampel yang lebih canggih: menentukan apakah nilai properti dari dua instans dengan jenis yang sama adalah sama (Dipinjam dan dimodifikasi dari posting StackOverflow ini):

public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
    if (self == null || to == null)
    {
        return self == to;
    }

    // Selects the properties which have unequal values into a sequence of those properties.
    var unequalProperties = from property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                            where !ignore.Contains(property.Name)
                            let selfValue = property.GetValue(self, null)
                            let toValue = property.GetValue(to, null)
                            where !Equals(selfValue, toValue)
                            select property;
    return !unequalProperties.Any();
}
<System.Runtime.CompilerServices.Extension()>
Public Function PublicInstancePropertiesEqual(Of T As Class)(self As T, [to] As T, ParamArray ignore As String()) As Boolean
    If self Is Nothing OrElse [to] Is Nothing Then
        Return self Is [to]
    End If

    ' Selects the properties which have unequal values into a sequence of those properties.
    Dim unequalProperties = From [property] In GetType(T).GetProperties(BindingFlags.Public Or BindingFlags.Instance)
                            Where Not ignore.Contains([property].Name)
                            Let selfValue = [property].GetValue(self, Nothing)
                            Let toValue = [property].GetValue([to], Nothing)
                            Where Not Equals(selfValue, toValue) Select [property]
    Return Not unequalProperties.Any()
End Function

PLINQ

PLINQ, atau Parallel LINQ, adalah mesin eksekusi paralel untuk ekspresi LINQ. Dengan kata lain, ekspresi LINQ reguler dapat diparalelkan dengan mudah di sejumlah utas. Ini dilakukan melalui panggilan ke AsParallel() sebelum ekspresi.

Pertimbangkan hal berikut:

public static string GetAllFacebookUserLikesMessage(IEnumerable<FacebookUser> facebookUsers)
{
    var seed = default(UInt64);

    Func<UInt64, UInt64, UInt64> threadAccumulator = (t1, t2) => t1 + t2;
    Func<UInt64, UInt64, UInt64> threadResultAccumulator = (t1, t2) => t1 + t2;
    Func<Uint64, string> resultSelector = total => $"Facebook has {total} likes!";

    return facebookUsers.AsParallel()
                        .Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector);
}
Public Shared GetAllFacebookUserLikesMessage(facebookUsers As IEnumerable(Of FacebookUser)) As String
{
    Dim seed As UInt64 = 0

    Dim threadAccumulator As Func(Of UInt64, UInt64, UInt64) = Function(t1, t2) t1 + t2
    Dim threadResultAccumulator As Func(Of UInt64, UInt64, UInt64) = Function(t1, t2) t1 + t2
    Dim resultSelector As Func(Of Uint64, string) = Function(total) $"Facebook has {total} likes!"

    Return facebookUsers.AsParallel().
                        Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector)
}

Kode ini akan mempartisi facebookUsers di seluruh utas sistem seperlunya, menjumlahkan total suka pada setiap utas secara paralel, menghitung keseluruhan hasil yang diperoleh oleh setiap utas, dan memproyeksikan hasil tersebut menjadi string yang rapi.

Dalam bentuk diagram:

diagram PLINQ

Pekerjaan terikat CPU paralel yang dapat dengan mudah diekspresikan melalui LINQ (dengan kata lain, adalah fungsi murni dan tidak memiliki efek samping) adalah kandidat yang bagus untuk PLINQ. Untuk pekerjaan yang lakukan memiliki efek samping, pertimbangkan untuk menggunakanPustaka Paralel Tugas .

Sumber daya lainnya

  • Linqpad, lingkungan percobaan dan mesin pencarian basis data untuk C#/F#/Visual Basic
  • EduLinq, e-book untuk mempelajari bagaimana LINQ ke objek diimplementasikan