Tùy chọn kiểu LINQ [đã đóng]


21

Tôi đã đến để sử dụng LINQ trong lập trình hàng ngày của tôi rất nhiều. Trong thực tế, tôi hiếm khi, nếu có, sử dụng một vòng lặp rõ ràng. Tuy nhiên, tôi thấy rằng tôi không sử dụng cú pháp như SQL nữa. Tôi chỉ sử dụng các chức năng mở rộng. Vì vậy, thay vì nói:

from x in y select datatransform where filter 

Tôi sử dụng:

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

Bạn thích phong cách nào của LINQ và những người khác trong nhóm của bạn cảm thấy thoải mái với điều gì?


5
Có thể đáng chú ý rằng lập trường MS chính thức là cú pháp truy vấn thích hợp hơn.
R0MANARMY

1
Cuối cùng, nó không thành vấn đề. Điều quan trọng là mã là dễ hiểu. Một hình thức có thể tốt hơn trong một trường hợp, hình thức khác trong một trường hợp khác. Vì vậy, sử dụng bao giờ là thích hợp tại thời điểm đó.
ChrisF

Tôi tin rằng ví dụ thứ hai của bạn được gọi là cú pháp lambda, cái mà tôi sử dụng 95% thời gian. 5% khác tôi sử dụng cú pháp truy vấn là khi tôi tham gia, tôi đang cố gắng chuyển sang tham gia cú pháp lambda nhưng giống như những người khác đã chỉ ra rằng nó trở nên lộn xộn.
Người đàn ông Muffin

Câu trả lời:


26

Tôi thấy không may là lập trường của Microsoft đối với tài liệu MSDN là cú pháp truy vấn thích hợp hơn, bởi vì tôi không bao giờ sử dụng nó, nhưng tôi luôn sử dụng cú pháp phương thức LINQ. Tôi thích có thể loại bỏ các truy vấn một lớp đến nội dung trái tim tôi. Đối chiếu:

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

Đến:

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

Nhanh hơn, ít đường hơn và mắt tôi trông sạch hơn. Cú pháp truy vấn không hỗ trợ tất cả các toán tử LINQ tiêu chuẩn. Một truy vấn ví dụ gần đây tôi đã làm một cái gì đó như thế này:

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

Theo hiểu biết của tôi, để sao chép truy vấn này bằng cú pháp truy vấn (trong phạm vi có thể), nó sẽ trông như thế này:

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

Trông tôi không dễ đọc hơn và dù sao bạn cũng cần biết cách sử dụng cú pháp phương thức. Cá nhân tôi thực sự say mê phong cách khai báo mà LINQ có thể thực hiện được và sử dụng nó trong mọi tình huống có thể - đôi khi có thể gây bất lợi cho tôi. Trường hợp cụ thể, với cú pháp phương thức tôi có thể làm một cái gì đó như thế này:

// 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)
     });

Tôi tưởng tượng đoạn mã trên sẽ khó hiểu đối với ai đó tham gia dự án nếu không có tài liệu tốt và nếu họ không có nền tảng vững chắc về LINQ thì dù sao họ cũng không thể hiểu được. Tuy nhiên, cú pháp phương thức cho thấy một số khả năng khá mạnh mẽ, để nhanh chóng (về các dòng mã) dự kiến ​​một truy vấn để có được thông tin tổng hợp về nhiều bộ sưu tập, nếu không sẽ mất nhiều vòng lặp foreach tẻ nhạt. Trong trường hợp như thế này, cú pháp phương thức cực kỳ nhỏ gọn cho những gì bạn nhận được từ nó. Cố gắng làm điều này với cú pháp truy vấn có thể trở nên khó sử dụng khá nhanh.


Dàn diễn viên bạn có thể thực hiện trong phần chọn nhưng thật không may, bạn không thể chỉ định lấy các bản ghi X hàng đầu mà không cần sử dụng các phương thức LINQ. Điều này đặc biệt khó chịu ở những nơi bạn biết bạn chỉ cần một bản ghi duy nhất và phải đặt tất cả các truy vấn trong ngoặc.
Ziv

2
Chỉ với bản ghi bạn có thể thực hiện Chọn (x => x.ItemInfo) .OfType <GeneralMerchInfo> () thay vì Where (). Chọn (). Truyền <> (), mà tôi tin là nhanh hơn (O lớn 2n thay vì n * 2m tôi nghĩ). Nhưng bạn hoàn toàn đúng, cú pháp lambda tốt hơn nhiều từ quan điểm dễ đọc.
Ed James

16

Tôi tìm thấy các cú pháp chức năng làm hài lòng hơn trên mắt. Ngoại lệ duy nhất là nếu tôi cần tham gia nhiều hơn hai bộ. Tham gia () bị điên rất nhanh.


Đồng ý ... Tôi thích giao diện và khả năng đọc hơn từ các phương thức mở rộng ngoại trừ (như đã chỉ ra) khi tham gia. Các nhà cung cấp linh kiện (ví dụ Telerik) sử dụng các phương thức mở rộng rất nhiều. Ví dụ tôi đang nghĩ đến là Rad Controls của họ trong ASP.NET MVC. Bạn cần phải rất thành thạo sử dụng các phương pháp mở rộng để sử dụng / đọc chúng.
Bắt kịp

Đã đến để nói điều này. Tôi thường sử dụng lambdas trừ khi có sự tham gia. Khi đã tham gia, cú pháp LINQ có xu hướng dễ đọc hơn.
Sean

10

Có bao giờ là quá muộn để thêm một câu trả lời?

Tôi đã viết rất nhiều mã LINQ-to-object và tôi cho rằng ít nhất trong miền đó phải hiểu cả hai cú pháp để sử dụng bất kỳ mã nào tạo ra mã đơn giản hơn - không phải luôn luôn là cú pháp dấu chấm.

Tất nhiên, có những lúc cú pháp chấm cách để đi - những người khác đã cung cấp một số trường hợp này; tuy nhiên, tôi nghĩ rằng sự hiểu biết đã được thay đổi ngắn - nếu bạn đọc rap tệ, nếu bạn muốn. Vì vậy, tôi sẽ cung cấp một mẫu mà tôi tin rằng sự hiểu biết là hữu ích.

Đây là một giải pháp cho câu đố thay thế chữ số: (giải pháp được viết bằng LINQPad, nhưng có thể độc lập trong ứng dụng bảng điều khiển)

// 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);

... Kết quả nào:

N = 1, O = 6, K = 4

Không quá tệ, logic chảy theo tuyến tính và chúng ta có thể thấy rằng nó đưa ra một giải pháp đúng duy nhất. Câu đố này đủ dễ để giải bằng tay: lý do rằng 3 >> N0 và O> 4 * N ngụ ý 8> = O> = 4. Điều đó có nghĩa là có tối đa 10 trường hợp để kiểm tra bằng tay (2 cho N-by- 5 cho O). Tôi đã đi lạc đủ - câu đố này được cung cấp cho mục đích minh họa LINQ.

Chuyển đổi trình biên dịch

Có rất nhiều trình biên dịch làm điều này để dịch nó thành cú pháp dấu chấm tương đương. Bên cạnh các mệnh đề thứ hai và tiếp theofromSelectMany thông thường được chuyển thành các cuộc gọi, chúng ta có letcác mệnh đề trở thành Selectcác cuộc gọi với các phép chiếu, cả hai đều sử dụng định danh trong suốt . Như tôi sắp trình bày, việc đặt tên cho các định danh này theo cú pháp dấu chấm sẽ làm mất khả năng đọc của phương pháp đó.

Tôi có một mẹo để phơi bày những gì trình biên dịch làm trong việc dịch mã này thành cú pháp chấm. Nếu bạn bỏ ghi chú hai dòng nhận xét ở trên và chạy lại, bạn sẽ nhận được kết quả đầu ra sau:

N = 1, O = 6, K = 4

cây biểu thức giải pháp 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_ Trong suốtIdentifier0 = <> h _TransparentIdentifier, NO = ((10 * <> h_ TransparentIdentifier0.N) + <> h _TransparentIdentifier0.O))). Select (<> h_ TransparentIdentifier1 => mới <> 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_ Trong suốtIdentifier2.product % 10))). Trong đó (<> h _TransparentIdentifier3 => (((<> h_ Trong suốtIdentifier3.K ! = <> h _TransparentIdentifier3. <> h_ Minh bạchh _TransparentIdentifier1. <> h_TransparentIdentifier0.O) AndAlso (<> h _TransparentIdentifier3.K! = <> H_ TransparentIdentifier3. <> H _TransparentIdentifier2. <> H_ TransparentIdentifier1. <> H _TransparentIdentifier0.N)) AndAlso ((<> h_ TransparentIdentifier3. <> H _TransparentIdentifier2. sản phẩm / 10) == <> h_ Trong suốtIdentifier3 . <> h _TransparentIdentifier2. <> h_ Trong suốtIdentifier1 . <> h _TransparentIdentifier0.O))). Chọn (<> h_ suốtIdentifier3 => new < > h_ Trong suốtIdentifier3 . <> h _TransparentIdentifier2. <> h_ Trong suốtIdentifier1 . <> h _TransparentIdentifier0.N,O = <> h_ Trong suốtIdentifier3 . <> H_TransparentIdentifier2. <> H_ Trong suốtIdentifier1 . <> H _TransparentIdentifier0.O, K = <> h__TransparentIdentifier3.K))

Đặt mỗi toán tử LINQ trên một dòng mới, dịch các số nhận dạng "không thể nói được" sang các số nhận dạng mà chúng ta có thể "nói", thay đổi các loại ẩn danh thành dạng quen thuộc của chúng và thay đổi AndAlsobiệt ngữ cây biểu thức để &&hiển thị các biến đổi mà trình biên dịch thực hiện để tương đương trong cú pháp dấu chấm:

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);
}

Mà nếu bạn chạy, bạn có thể xác minh rằng nó lại xuất ra:

N = 1, O = 6, K = 4

... nhưng bạn có bao giờ viết mã như thế này không?

Tôi muốn đặt câu trả lời là NONBHN (Không chỉ Không, mà là Không!) - vì nó quá phức tạp. Chắc chắn bạn có thể đưa ra một số tên định danh có ý nghĩa hơn "temp0" .. "temp3", nhưng vấn đề là họ không thêm bất cứ điều gì vào mã - họ không làm cho mã hoạt động tốt hơn, họ không làm làm cho mã đọc tốt hơn, chúng chỉ làm xấu mã và nếu bạn đang làm bằng tay, chắc chắn bạn sẽ làm hỏng nó một hoặc ba lần trước khi làm cho đúng. Ngoài ra, chơi "trò chơi tên" là đủ khó cho các định danh có ý nghĩa, vì vậy tôi hoan nghênh sự phá vỡ trò chơi tên mà trình biên dịch cung cấp cho tôi trong việc hiểu các truy vấn.

Mẫu câu đố này có thể không đủ thực tế để bạn thực hiện nghiêm túc; tuy nhiên, các kịch bản khác tồn tại khi sự hiểu biết truy vấn tỏa sáng:

  • Độ phức tạp của JoinGroupJoin: phạm vi của các biến phạm vi trong joinmệnh đề hiểu truy vấn biến các lỗi có thể biên dịch theo cú pháp dấu chấm thành lỗi thời gian biên dịch theo cú pháp hiểu.
  • Bất cứ khi nào trình biên dịch sẽ giới thiệu một định danh minh bạch trong biến đổi hiểu, việc hiểu trở nên đáng giá. Điều này bao gồm việc sử dụng bất kỳ những điều sau đây: nhiều fromkhoản, join& join..intođiều khoản và letđiều khoản.

Tôi biết nhiều hơn một cửa hàng kỹ thuật ở quê tôi có cú pháp hiểu hiểu ngoài vòng pháp luật . Tôi nghĩ rằng đây là một điều đáng tiếc vì cú pháp hiểu là một công cụ và một công cụ hữu ích ở đó. Tôi nghĩ nó rất giống với câu nói: "Có những điều bạn có thể làm với một cái tuốc nơ vít mà bạn không thể làm với cái đục. Bởi vì bạn có thể sử dụng một cái tuốc nơ vít như một cái đục, từ đó bị cấm theo sắc lệnh của nhà vua."


-1: Wow. OP đã tìm kiếm một lời khuyên nhỏ. Bạn đã viết ra một cuốn tiểu thuyết! Bạn có phiền thắt chặt điều này lên một chút không?
Jim G.

8

Lời khuyên của tôi là sử dụng cú pháp hiểu truy vấn khi toàn bộ biểu thức có thể được thực hiện trong cú pháp hiểu. Đó là, tôi thích:

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

đến

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

Nhưng tôi thích

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

đến

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

Tôi ước rằng chúng tôi đã đưa ra một số cú pháp làm cho nó đẹp hơn để trộn cả hai. Cái gì đó như:

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

Nhưng buồn thay, chúng tôi đã không làm thế.

Nhưng về cơ bản, đó là vấn đề ưu tiên. Làm một cái có vẻ tốt hơn cho bạn và đồng nghiệp của bạn.


3
Ngoài ra, bạn có thể xem xét tách biệt một sự hiểu biết từ các cuộc gọi toán tử LINQ khác thông qua tái cấu trúc "giới thiệu biến giải thích". ví dụ:var londonCustomers = from c in ...; int count = londonCustomers.Count();
devgeezer

3

Giống như SQL là một cách tốt để bắt đầu. Nhưng vì nó bị giới hạn (nó chỉ hỗ trợ các cấu trúc mà ngôn ngữ hiện tại của bạn hỗ trợ) cuối cùng các nhà phát triển chuyển sang kiểu phương thức mở rộng.

Tôi muốn lưu ý rằng có một số trường hợp có thể dễ dàng thực hiện theo kiểu giống như SQL.

Ngoài ra, bạn có thể kết hợp cả hai cách trong một truy vấn.


2

Tôi có xu hướng sử dụng cú pháp không truy vấn trừ khi tôi cần xác định một biến giữa chừng mặc dù truy vấn như

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

nhưng tôi viết cú pháp không truy vấn như

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

2

Tôi luôn luôn sử dụng các chức năng mở rộng vì thứ tự. Lấy ví dụ đơn giản của bạn - trong SQL, bạn đã viết chọn trước - mặc dù trên thực tế, nơi được thực hiện trước. Khi bạn viết bằng các phương thức mở rộng, thì tôi cảm thấy kiểm soát nhiều hơn. Tôi nhận được Intellisense về những gì được cung cấp, tôi viết những thứ theo thứ tự chúng xảy ra.


Tôi nghĩ rằng bạn sẽ thấy rằng trong cú pháp "hiểu truy vấn", thứ tự trên trang giống như thứ tự các thao tác xảy ra. LINQ không đặt "chọn" trước, không giống như SQL.
Eric Lippert

1

Tôi thích chức năng mở rộng quá.

Có lẽ nó là một cú nhảy trong tâm trí của tôi.

Nó cũng cảm thấy dễ đọc hơn, đặc biệt nếu bạn đang sử dụng các khung công tác của bên thứ ba có linq api.


0

Đây là heuristic mà tôi theo dõi:

Ưu tiên biểu thức LINQ hơn lambdas khi bạn tham gia.

Tôi nghĩ rằng lambdas với các liên kết trông lộn xộn và khó đọc.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.