LINQ 스타일 기본 설정 [닫힘]


21

나는 매일 프로그래밍을 많이 할 때 LINQ를 사용하게되었습니다. 사실, 나는 명시 적 루프를 거의 사용하지 않습니다. 그러나 나는 더 이상 SQL과 같은 구문을 사용하지 않는다는 것을 알았습니다. 확장 기능 만 사용합니다. 오히려 다음과 같이 말합니다.

from x in y select datatransform where filter 

나는 사용한다:

x.Where(c => filter).Select(c => datatransform)

어떤 LINQ 스타일을 선호하고 팀의 다른 스타일에 익숙한가?


5
공식 MS 입장 은 쿼리 구문이 바람직하다는 점에 주목할 필요가 있습니다 .
R0MANARMY

1
궁극적으로 중요하지 않습니다. 중요한 것은 코드를 이해할 수 있다는 것입니다. 어떤 경우에는 한 형태가 더 좋으며 다른 경우에는 다른 형태가 더 좋습니다. 따라서 당시에 적절한 것을 사용하십시오.
ChrisF

나는 두 번째 예제를 람다 구문이라고하는데, 이것은 95 %의 시간을 사용합니다. 다른 5 %는 조인을 할 때 쿼리 구문을 사용합니다. 람다 구문 조인으로 전환하려고하지만 다른 사람들과 마찬가지로 지저분 해집니다.
머핀 남자

답변:


26

MSDN 문서 당 Microsoft의 입장은 쿼리 구문이 바람직하다는 것입니다. 유용하지 않기 때문에 항상 LINQ 메서드 구문을 사용한다는 것은 불행한 일입니다. 나는 내 마음의 내용에 대해 한 줄짜리 쿼리를 해낼 수있는 것을 좋아합니다. 비교:

var products = from p in Products
               where p.StockOnHand == 0
               select p;

에:

var products = Products.Where(p => p.StockOnHand == 0);

더 빠르고 줄이 적고 눈이 더 깨끗해 보입니다. 쿼리 구문은 모든 표준 LINQ 연산자를 지원하지 않습니다. 내가 최근에 한 예제 쿼리는 다음과 같습니다.

var itemInfo = InventoryItems
    .Where(r => r.ItemInfo is GeneralMerchInfo)
    .Select(r => r.ItemInfo)
    .Cast<GeneralMerchInfo>()
    .FirstOrDefault(r => r.Xref == xref);

내 지식으로는 쿼리 구문을 사용 하여이 쿼리를 복제하는 것이 가능하면 다음과 같습니다.

var itemInfo = (from r in InventoryItems
                where r.ItemInfo is GeneralMerchInfo
                select r.ItemInfo)
                .Cast<GeneralMerchInfo>()
                .FirstOrDefault(r => r.Xref == xref);

나에게 더 읽기 쉽게 보이지 않으므로 어쨌든 메소드 구문을 사용하는 방법을 알아야합니다. 개인적으로 저는 LINQ가 가능하게하는 선언적 스타일에 매혹되어 있으며, 가능한 경우, 때로는 제 손해를 입을 수있는 모든 상황에서 사용합니다. 예를 들어, 메서드 구문을 사용하면 다음과 같이 할 수 있습니다.

// projects an InventoryItem collection with total stock on hand for each GSItem
inventoryItems = repository.GSItems
    .Select(gsItem => new InventoryItem() {
        GSItem = gsItem,
        StockOnHand = repository.InventoryItems
            .Where(inventoryItem => inventoryItem.GSItem.GSNumber == gsItem.GSNumber)
            .Sum(r => r.StockOnHand)
     });

좋은 문서없이 프로젝트에 참여하는 사람에게는 위의 코드를 이해하기 어려울 것이라고 생각합니다 .LINQ에 대한 견고한 배경이 없으면 어쨌든 이해하지 못할 수 있습니다. 그럼에도 불구하고 메서드 구문은 매우 강력한 기능을 제공하여 코드 줄로 신속하게 쿼리를 계획하여 여러 컬렉션에 대한 집계 정보를 얻습니다. 그렇지 않으면 많은 지루한 foreach 루핑이 필요합니다. 이와 같은 경우 메소드 구문은 사용자가 얻는 것에 매우 컴팩트합니다. 쿼리 구문을 사용하여이 작업을 수행하려고하면 처리하기가 다소 어려워 질 수 있습니다.


선택 내에서 수행 할 수있는 캐스트이지만 불행히도 LINQ 메서드를 사용하지 않고 최상위 X 레코드를 가져 오도록 지정할 수 없습니다. 이것은 단일 레코드 만 필요하고 모든 쿼리를 괄호 안에 넣어야하는 곳에서 특히 성가시다.
Ziv

2
레코드에 대해서만 Where (). Select (). Cast <> () 대신 Select (x => x.ItemInfo) .OfType <GeneralMerchInfo> ()를 수행 할 수 있습니다. n * 2m 대신에 나는 생각한다). 그러나 람다 구문은 가독성 관점에서 훨씬 낫습니다.
Ed James

16

기능적 구문이 더 기쁘게 생각합니다. 유일한 예외는 둘 이상의 세트에 참여해야하는 경우입니다. Join ()은 매우 빠르게 미칩니다.


합의 ... 참여할 때를 제외하고 확장 방법의 모양과 가독성을 훨씬 선호합니다. 컴포넌트 공급 업체 (예 : Telerik)는 확장 방법을 많이 사용합니다. 내가 생각하는 예는 ASP.NET MVC의 Rad 컨트롤입니다. 그것들을 사용 / 읽기 위해서는 확장 방법을 사용하는 데 능숙해야합니다.
Catchops 2016 년

이 말을했다. 일반적으로 조인이 없으면 람다를 사용합니다. 조인이 있으면 LINQ 구문이 더 읽기 쉽습니다.
Sean

10

다른 답변을 추가하기에는 너무 늦었습니까?

나는 많은 LINQ-to-objects 코드를 작성했으며 최소한 그 도메인에서 더 간단한 코드를 만드는 데 사용하기 위해 두 구문을 모두 이해하는 것이 좋다고 주장합니다. 항상 도트 구문은 아닙니다.

도트 구문이 때 물론 시간이있다 IS 길을 가야하는 것 - 다른 사람이 이러한 경우 여러 제공하고 있습니다; 그러나, 나는 당신이 원한다면 이해력이 부족하다고 생각합니다. 이해력이 유용한 샘플을 제공하겠습니다.

자릿수 대체 퍼즐에 대한 해결책은 다음과 같습니다. (LINQPad를 사용하여 작성된 솔루션이지만 콘솔 앱에서는 독립형 일 수 있음)

// NO
// NO
// NO
//+NO
//===
// OK

var solutions =
    from O in Enumerable.Range(1, 8) // 1-9
                    //.AsQueryable()
    from N in Enumerable.Range(1, 8) // 1-9
    where O != N
    let NO = 10 * N + O
    let product = 4 * NO
    where product < 100
    let K = product % 10
    where K != O && K != N && product / 10 == O
    select new { N, O, K };

foreach(var i in solutions)
{
    Console.WriteLine("N = {0}, O = {1}, K = {2}", i.N, i.O, i.K);
}

//Console.WriteLine("\nsolution expression tree\n" + solutions.Expression);

... 출력 :

N = 1, O = 6, K = 4

그리 나쁘지는 않지만, 논리는 선형으로 흐르고 올바른 단일 솔루션이 나온 것을 볼 수 있습니다. 이 퍼즐은 손으로 쉽게 풀 수 있습니다 .3>> N0, O> 4 * N은 8> = O> = 4를 의미합니다. 즉, 손으로 테스트 할 최대 10 개의 사례가 있음을 의미 N합니다. O). 나는 충분히 길을 잃었다-이 퍼즐은 LINQ 일러스트레이션 목적으로 제공된다.

컴파일러 변환

컴파일러가 이것을 동등한 도트 구문으로 변환하기 위해 많은 작업을 수행합니다. 보통 외에 두 번째 이후 from조항이로 바뀌 얻을 SelectMany호출 우리는이 let될 절을 Select사용 둘 다 돌기 전화, 투명-식별자 . 내가 보여 주려고 할 때, 도트 구문에서 이러한 식별자의 이름을 지정하는 것은 그 접근법의 가독성을 없애줍니다.

컴파일러 가이 코드를 도트 구문으로 변환 할 때 수행하는 작업을 노출시키는 트릭이 있습니다. 위의 주석 처리 된 두 줄의 주석을 해제하고 다시 실행하면 다음과 같은 결과가 나타납니다.

N = 1, O = 6, K = 4

솔루션 식 트리 System.Linq.Enumerable + d_ b8.SelectMany (O => Range (1, 8), (O, N) => new <> f _AnonymousType0 2(O = O, N = N)).Where(<>h__TransparentIdentifier0 => (<>h__TransparentIdentifier0.O != <>h__TransparentIdentifier0.N)).Select(<>h__TransparentIdentifier0 => new <>f__AnonymousType12 (<> h_ TransparentIdentifier0 = <> h _TransparentIdentifier0, NO = ((10 * <> h_ TransparentIdentifier0.N) + <> h _TransparentIdentifier0.O))). Select (<> h_ TransparentIdentifier1 => new <> f _AnonymousType2 2(<>h__TransparentIdentifier1 = <>h__TransparentIdentifier1, product = (4 * <>h__TransparentIdentifier1.NO))).Where(<>h__TransparentIdentifier2 => (<>h__TransparentIdentifier2.product < 100)).Select(<>h__TransparentIdentifier2 => new <>f__AnonymousType32 (<> h_ TransparentIdentifier2 = <> h _TransparentIdentifier2, K = ( <> h_ TransparentIdentifier2.product % 10))). Where (<> h _TransparentIdentifier3 => (((<< h_ TransparentIdentifier3.K! = <> h _TransparentIdentifier3. <> h_ TransparentIdentifier2. <>h _TransparentIdentifier1. <> h_TransparentIdentifier0.O) AndAlso (<> H _TransparentIdentifier3.K! = <> H_ TransparentIdentifier3. <> H _TransparentIdentifier2. <> H_ TransparentIdentifier1. <> H _TransparentIdentifier0.N)) AndAlso ((<> H_ TransparentIdentifier3. <> H _TransparentIdentifier2. product / 10) == <> h_ TransparentIdentifier3. <> h _TransparentIdentifier2. <> h_ TransparentIdentifier1. <> h _TransparentIdentifier0.O))). Select (<> h_ TransparentIdentifier3 => new <> f _AnonymousType4`3 (N = < > h_ TransparentIdentifier3. <> h _TransparentIdentifier2. <> h_ TransparentIdentifier1. <> h _TransparentIdentifier0.N,O = <> h_ TransparentIdentifier3. <> h_TransparentIdentifier2. <> h_ TransparentIdentifier1. <> h _TransparentIdentifier0.O, K = <> h__TransparentIdentifier3.K))

각 LINQ 연산자를 새 줄에 입력하여 "말할 수없는"식별자를 "말할 수있는"식별자로 변환하고, 익명 형식을 익숙한 형식으로 변경하고, AndAlso식 트리 용어를 변경 &&하여 컴파일러가 동등한 수준에 도달하는 변환 을 노출합니다. 도트 구문으로 :

var solutions = 
    Enumerable.Range(1,8) // from O in Enumerable.Range(1,8)
        .SelectMany(O => Enumerable.Range(1, 8), (O, N) => new { O = O, N = N }) // from N in Enumerable.Range(1,8)
        .Where(temp0 => temp0.O != temp0.N) // where O != N
        .Select(temp0 => new { temp0 = temp0, NO = 10 * temp0.N + temp0.O }) // let NO = 10 * N + O
        .Select(temp1 => new { temp1 = temp1, product = 4 * temp1.NO }) // let product = 4 * NO
        .Where(temp2 => temp2.product < 100) // where product < 100
        .Select(temp2 => new { temp2 = temp2, K = temp2.product % 10 }) // let K = product % 10
        .Where(temp3 => temp3.K != temp3.temp2.temp1.temp0.O && temp3.K != temp3.temp2.temp1.temp0.N && temp3.temp2.product / 10 == temp3.temp2.temp1.temp0.O)
        // where K != O && K != N && product / 10 == O
        .Select(temp3 => new { N = temp3.temp2.temp1.temp0.N, O = temp3.temp2.temp1.temp0.O, K = temp3.K });
        // select new { N, O, K };

foreach(var i in solutions)
{
    Console.WriteLine("N = {0}, O = {1}, K = {2}", i.N, i.O, i.K);
}

실행하면 다시 출력되는지 확인할 수 있습니다.

N = 1, O = 6, K = 4

...하지만 이런 코드를 작성하겠습니까?

대답은 NONBHN (아니오뿐만 아니라 지옥도 아닙니다!)입니다. 너무 복잡하기 때문입니다. 물론 "temp0".. "temp3"보다 더 의미있는 식별자 이름을 얻을 수 있지만 요점은 코드에 아무 것도 추가하지 않는다는 것입니다. 코드의 성능을 향상시키지 않고 성능을 향상시키지 않습니다. 코드를 더 잘 읽고, 코드를 못 생겼으며, 직접 작성하는 경우에는 한 번 또는 세 번 정도 엉망으로 만들 수 있습니다. 또한 "이름 게임"을 플레이하는 것은 의미있는 식별자를 찾기에 충분하지 않기 때문에 컴파일러가 쿼리 이해에서 제공하는 이름 게임에서 벗어나는 것을 환영합니다.

이 퍼즐 샘플은 실제 상황 이 아닐 수도 있습니다 . 그러나 쿼리 이해가 빛나는 다른 시나리오가 있습니다.

  • 의 복잡성 JoinGroupJoin: 쿼리 이해의 범위 변수의 범위 지정 join조항 그렇지 않으면 이해 구문에 컴파일 시간 오류에 도트 구문으로 컴파일 할 수있는 실수를 켜십시오.
  • 컴파일러가 이해 변환에 투명한 식별자를 도입 할 때마다 이해가 가치가 있습니다. 여기에는 다중 from절, join& join..into절 및 let절 중 하나를 사용하는 것이 포함됩니다 .

저는 고향에서 이해 구문 을 위반 한 하나 이상의 엔지니어링 샵을 알고 있습니다. 나는 이것이 이해 구문이 도구이자 유용한 도구이므로 유감이라고 생각합니다. 나는 그것이 말처럼 많은 생각 "당신이 끌 함께 할 수없는 드라이버로 할 수있는 일이 있습니다. 당신은 부정 행위로 드라이버를 사용할 수 있기 때문에, 끌이 왕의 칙령에 따라 이제부터 금지된다."


-1 : 와우. OP는 약간의 조언을 찾고있었습니다. 당신은 소설을 냈어요! 이것을 조금 강화 하시겠습니까?
Jim G.

8

내 충고는 전체 표현이 이해 구문에서 수행 될 수있을 때 쿼리 이해 구문을 사용하는 것입니다. 즉, 나는 선호합니다 :

var query = from c in customers orderby c.Name select c.Address;

var query = customers.OrderBy(c=>c.Name).Select(c=>c.Address);

그러나 나는 선호한다

int count = customers.Where(c=>c.City == "London").Count();

int count = (from c in customers where c.City == "London" select c).Count();

두 가지를 혼합하는 것이 더 좋은 구문을 생각해 냈습니다. 다음과 같은 것 :

int count = from c in customers 
            where c.City == "London" 
            select c 
            continue with Count();

그러나 슬프게도 우리는 그렇지 않았습니다.

그러나 기본적으로 선호의 문제입니다. 당신과 당신의 동료에게 더 좋아 보이는 것을하십시오.


3
또는 "설명 변수 소개"리팩토링을 통해 다른 LINQ 운영자 호출에서 이해를 분리하는 것을 고려할 수 있습니다. 예를 들어,var londonCustomers = from c in ...; int count = londonCustomers.Count();
devgeezer

3

SQL과 비슷한 방법으로 시작하는 것이 좋습니다. 그러나 제한적이므로 (현재 언어가 지원하는 구성 만 지원함) 결국 개발자는 확장 방법 스타일로 이동합니다.

SQL과 같은 스타일로 쉽게 구현할 수있는 경우가 있습니다.

또한 하나의 쿼리에서 두 가지 방법을 결합 할 수 있습니다.


2

쿼리가 중간에 변수를 정의 해야하는 경우가 아니라면 비 쿼리 구문을 사용하는 경향이 있습니다.

from x in list
let y = x.DoExpensiveCalulation()
where y > 42
select y

하지만 나는 비 쿼리 구문을 작성합니다.

x.Where(c => filter)
 .Select(c => datatransform)

2

주문 때문에 항상 확장 기능을 사용합니다. SQL에서 간단한 예를 들어 보면 select가 먼저 작성되었습니다. 확장 방법을 사용하여 작성하면 훨씬 더 통제력이 있다고 생각합니다. 나는 제공되는 것에 대해 Intellisense를 받고, 발생하는 순서대로 일을 씁니다.


"query comprehension"구문에서 페이지의 순서는 작업이 수행되는 순서와 동일하다는 것을 알 수 있습니다. LINQ는 SQL과 달리 "select"를 먼저 두지 않습니다.
Eric Lippert

1

나는 확장 기능도 좋아한다.

어쩌면 그것이 내 생각에 비약적인 구문이 아닐 수도 있습니다.

linq api가있는 타사 프레임 워크를 사용하는 경우 특히 눈에 잘 들어옵니다.


0

내가 따르는 추론은 다음과 같습니다.

조인 할 때 람다보다 LINQ 표현을 선호하십시오.

조인이있는 람다는 지저분하고 읽기가 어렵다고 생각합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.