LINQ 개요
LINQ(Language-Integrated Query)는 언어 수준의 쿼리 기능과 C# 및 Visual Basic에 상위 함수 API를 제공하여 표현력 있는 선언적 코드를 작성할 수 있도록 해줍니다.
언어 수준 쿼리 구문
언어 수준 쿼리 구문은 다음과 같습니다.
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)
IEnumerable<T>
API를 사용하는 동일한 예제입니다.
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는 표현적입니다.
애완 동물 목록이 있다고 상상해보십시오. 그러나 이를 RFID
값으로 애완 동물에 직접 액세스할 수 있는 사전으로 변환하고 싶습니다.
일반적인 명령적 코드는 다음과 같습니다.
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
코드의 의도는 새 Dictionary<int, Pet>
만들고 루프를 통해 추가하는 것이 아니라 기존 목록을 사전으로 변환하는 것입니다. LINQ는 의도를 유지하지만 명령적 코드는 유지하지 않습니다.
동일한 LINQ 식입니다.
var petLookup = pets.ToDictionary(pet => pet.RFID);
Dim petLookup = pets.ToDictionary(Function(pet) pet.RFID)
LINQ를 사용하는 코드는 프로그래머로서의 의도와 코드 간의 차이를 줄여주기 때문에 유용합니다. 또 다른 보너스는 코드 간결성입니다. 위에서 설명한 것처럼 코드베이스의 많은 부분을 1/3으로 줄이면 됩니다. 달콤한 거래, 그렇죠?
LINQ 공급자는 데이터 액세스를 간소화합니다.
많은 소프트웨어에서 모든 것이 일부 원본(데이터베이스, JSON, XML 등)의 데이터 처리와 관련된 모든 것이 이루어집니다. 여기에는 종종 각 데이터 원본에 대한 새 API를 학습하는 작업이 포함되며, 이는 성가신 일이 될 수 있습니다. LINQ는 데이터 액세스의 공통 요소를 선택한 데이터 원본과 관계없이 동일하게 보이는 쿼리 구문으로 추상화하여 이를 간소화합니다.
특정 특성 값이 있는 모든 XML 요소를 찾습니다.
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
이 작업을 수행하기 위해 XML 문서를 수동으로 트래버스하는 코드를 작성하는 것은 훨씬 더 어렵습니다.
XML과 상호 작용하는 것만이 LINQ 공급자를 사용하여 수행할 수 있는 것은 아닙니다.
Linq to SQL MSSQL 서버 데이터베이스에 대한 ORM(완전 Object-Relational 매퍼)입니다.
Json.NET 라이브러리는 LINQ를 통해 효율적인 JSON 문서 순회를 제공합니다. 또한 필요한 작업을 수행하는 라이브러리가 없는 경우 고유한 LINQ 공급자작성할
쿼리 구문을 사용하는 이유
쿼리 구문을 사용하는 이유는 무엇인가요? 이것은 종종 나오는 질문입니다. 결국 다음 코드는 다음과 같습니다.
var filteredItems = myItems.Where(item => item.Foo);
Dim filteredItems = myItems.Where(Function(item) item.Foo)
이보다 훨씬 더 간결합니다.
var filteredItems = from item in myItems
where item.Foo
select item;
Dim filteredItems = From item In myItems
Where item.Foo
Select item
API 구문이 쿼리 구문을 수행하는 보다 간결한 방법이 아닌가요?
아니요. 쿼리 구문을 사용하면 let 절을 사용할 수 있습니다. 이 절을 사용하면 식의 범위 내에서 변수를 도입하고 바인딩할 수 있으며 식의 후속 부분에서 변수를 사용할 수 있습니다. API 구문만 사용하여 동일한 코드를 재현할 수 있지만 읽기 어려운 코드가 발생할 가능성이 높습니다.
그래서 이것은 질문을 구걸, 쿼리 구문을 사용해야합니까?
이 질문에 대한 대답은 예입니다.
- 기존 코드베이스는 이미 쿼리 구문을 사용합니다.
- 복잡성 때문에 쿼리 내에서 변수의 범위를 지정해야 합니다.
- 쿼리 구문을 선호하기 때문에 코드베이스에 방해가 되지 않습니다.
이 질문에 대한 대답은 이고,이 아닙니다.
- 기존 코드베이스에서 이미 API 구문을 사용하고 있습니다.
- 쿼리 내에서 변수의 범위를 지정할 필요가 없습니다.
- API 구문을 선호하기 때문에 코드베이스에 방해가 되지 않습니다.
필수 LINQ
다음 예제는 LINQ의 몇 가지 필수 부분에 대한 빠른 데모입니다. LINQ가 여기에 소개된 기능보다 더 많은 기능을 제공하기 때문에 이는 포괄적이지는 않습니다.
빵과 버터 - Where
, Select
및 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)
목록 목록 평면화
// 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)
두 집합 간의 합집합(사용자 지정 비교자 사용)
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())
두 집합 간의 교집합
// 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())
주문
// 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)
인스턴스 속성의 같음
마지막으로, 더욱 고급 예제: 동일한 형식의 두 인스턴스의 속성 값이 같은지 확인하기 (StackOverflow 게시물 에서 가져와 수정됨).
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 또는 병렬 LINQ는 LINQ 식에 대한 병렬 실행 엔진입니다. 즉, 일반 LINQ 식은 여러 스레드에서 간단하게 병렬 처리할 수 있습니다. 이 작업은 표현식 이전에 AsParallel()
호출을 통해 수행됩니다.
다음을 고려합니다.
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)
}
이 코드는 필요에 따라 적절하게 시스템 스레드 간에 facebookUsers
를 분할하고, 각 스레드에서 총 좋아요 수를 병렬로 합산하며, 각 스레드가 계산한 결과를 합산한 후, 그 결과를 보기 좋게 문자열로 변환합니다.
다이어그램 형식:
LINQ를 통해 쉽게 표현할 수 있는 병렬 처리 가능한 CPU 바인딩된 작업(즉, 순수 함수이며 부작용이 없음)은 PLINQ에 적합한 후보입니다. 부작용이
추가 리소스
.NET