Giới thiệu

C# / .NET framework là một trong những công cụ linh hoạt nhất dành cho các lập trình viên phần mềm. Bài đăng này sẽ thiết lập 6 tính năng của .NET Framework có lẽ đang chưa được các lập trình viên tận dụng triệt để. Hãy cùng tìm hiểu sâu hơn về 6 tính năng này:

1. Stopwatch

Ở một thời điểm nào đó, bạn sẽ muốn kiểm các phần code của mình để tìm những điểm nghẽn (bottleneck) về hiệu suất. Mặc dù có muôn vàn gói định chuẩn mà bạn có thể sử dụng trong code của mình (Benchmark.NET là một trong những gói phổ biến nhất), đôi khi bạn chỉ muốn định chuẩn cho thật nhanh một cái gì đó, tránh rườm rà. Hầu hết mọi người sẽ có xu hướng làm như dưới đây: 

Nó có cho ra kết quả, và nó mất khoảng 2000 mili giây. Tuy nhiên, đây không phải là cách định chuẩn được khuyến khích dùng, bởi vì DateTime.Now có thể không cung cấp cho bạn mức độ chính xác cần thiết - DateTime.Now thường chính xác đến khoảng 15 mili giây. Để chứng minh điều này, hãy xem ví dụ được sắp đặt bên dưới:

Đầu ra có thể sẽ thay đổi mọi lúc, nhưng bạn có thể sẽ nhận được một cái gì đó như sau:

Vì vậy, nó không chính xác lắm, nhưng nếu bạn không màng đến thì sao? Tất nhiên, bạn có thể tiếp tục sử dụng phương pháp định chuẩn DateTime.Now, nhưng Stopwatch là một phương án khác tốt hơn nhiều, nó nằm ​​trong không gian tên (namespace) System.Diagnostics. Nó gọn ghẽ hơn nhiều DateTime.Now và cũng thể hiện ý định của bạn súc tích hơn, chính xác hơn! Hãy thay đổi đoạn code cuối cùng để sử dụng lớp Stopwatch:

Đầu ra sẽ là (tất nhiên, nó có thể thay đổi):

Tốt hơn nhiều! Cho nên, không có lý do gì mà bạn lại sử dụng một cách “cổ lỗ sĩ” khi đã có lớp Stopwatch.

2. Task Parallel Library (TPL)

Như bạn mong đợi, điều này mất khoảng 5000 milli giây/5 giây (100 * 50 = 5000). Hãu xem phiên bản song song khi sử dụng TPL…

Trung bình chỉ mất 1000 mili giây - tức là cải thiện những 500%! Kết quả sẽ khác nhau tùy thuộc vào thiết lập của bạn, nhưng có thể bạn cũng sẽ thấy sự cải thiện tương tự. Hãy chú ý tới vòng lặp – nó rất đơn giản, thậm chí chẳng phức tạp hơn một vòng lặp foreach thông thường.

Nhưng nếu bạn thao tác trên một đối tượng không theo luồng an toàn (non-thread safe object) bên trong vòng lặp, bạn chắc chắn sẽ gặp nhiều khó khăn. Vì vậy, không phải foreach nào cũng có thể thay thế được! Hãy đọc thêm về một vài mẹo TPL tại đây.

3. Expression Trees

Expression Trees là một phần cực mạnh mẽ của .NET Framework, nhưng đã bị đánh giá quá thấp bởi sự thiếu hiểu biết của các lập trình viên không chuyên. Về cơ bản, Expression Trees cho phép bạn bao hàm một biểu thức lambda, chẳng hạn như Func <T> hoặc Action <T>, và phân tích chính biểu thức lambda ấy. Có rất nhiều ví dụ để minh hoặc cho điều này trong .NET Framework, LINQ to SQL và Entity Framework.

Phương thức mở rộng ‘Where’ trong LINQ tới Objects lấy Func<T, int, bool> làm tham số chính - hãy xem đoạn mã dưới đây lấy từ từ Nguồn tham chiếu (chứa mã nguồn .NET)

Nó lặp lại qua IEnumerable và tạo ra những gì khớp với vị từ (predicate). Tuy nhiên, sẽ không như vậy với LINQ tới SQL / Entity Framework – sẽ cần phải dịch vị từ của bạn sang SQL! Vì vậy, chữ ký (signature_ cho phiên bản IQueryable sẽ hơi khác một chút…

Nếu bạn xem qua những khó khăn cốt lõi của phương pháp này, bạn sẽ nhận thấy rằng hàm bây giờ lấy Func <T, int, bool> được bao bọc trong một Expression - đây cơ bản cho phép 'nhà cung cấp' LINQ đọc qua Func để xem chính xác vị ngữ nào đã được chuyển qua và dịch nó sang SQL. Vì vậy, về bản chất, Expressions cho phép bạn kiểm tra code của mình trong thời gian chạy.

Cùng xem xét một điều đơn giản hơn - hãy tưởng tượng bạn có code bên dưới để thêm cài đặt vào từ điển (dictionary) 

Hy vọng rằng sẽ không khó để thấy sự nguy hiểm/lặp lại ở đây - tên của cài đặt trong từ điển là một chuỗi, lỗi đánh máy ở đây sẽ gây ra một số rắc rối. Hơn nữa, chuyện này thật chán! Nếu chúng ta tạo một phương thức mới nhận Expression <Func <T>> (lấy biểu thức lambda trả về một thứ gì đó), chúng ta sẽ nhận được tên thực của biến được truyền vào!

Sau đó chúng ta có thể gọi nó như dưới đây…

Tuyệt vời hơn nhiều nhỉ? Lưu ý rằng trong phương thức GetSetting này, bạn cần kiểm tra xem biểu thức có được truyền vào dưới dạng 'MemberExpression' hay không - do không có gì ngăn được việc gọi code từ việc chuyển một lệnh gọi phương thức (method call) hoặc một giá trị không đổi, khi đó không phải là 'tên thành viên'.

4. Caller Information Attributes

Caller Information Attributes đã được giới thiệu trong .NET Framework 4.0 và mặc dù chúng không hữu ích trong hầu hết các trường hợp, nhưng chúng thực sự thể hiện giá trị của mình khi viết code để ghi thông tin debug. Hãy tưởng tượng bạn có hàm Log thô như bên dưới:

Vì vậy, bạn có thể gọi nó tại các điểm khác nhau trong code của mình để ghi một số thông tin vào tệp văn bản, tuy nhiên, một chuỗi đơn lẻ có thể không hữu ích lắm cho bạn khi debug, trừ khi bạn đã cố gắng đưa đủ chi tiết vào chuỗi. Những gì bạn có thể muốn đó là nơi xuất hiện mục nhập log – bằng phương pháp nào. Bạn có thể kiểm tra stack trace bên dưới:

Nó sẽ in “Main” nếu bạn gọi từ phương thức Main của mình. Tuy nhiên, nó chậm và không hiệu quả vì về cơ bản bạn đang ghi lại stack trace như bạn vẫn làm nếu một ngoại lệ được đưa ra. .NET Framework 4.0 giới thiệu “Caller Information Attributes” đã nói ở trên, cho phép Framework tự động cho phương thức của bạn biết thông tin về những gì đang gọi nó - cụ thể là đường dẫn tệp, phương thức/tên thuộc tính và số dòng. Về cơ bản, bạn sử dụng chúng bằng cách cho phép phương thức của bạn nhận các tham số chuỗi tùy chọn mà bạn đánh dấu bằng một thuộc tính. Hãy xem cách sử dụng 3 thuộc tính có sẵn:

Nó xuất ra đường dẫn đến tệp nguồn, phương thức mà nó được gọi, cả số dòng. Rõ ràng, thông tin này (đặc biệt là hai thông tin sau) rất hữu ích cho mục đích debug. Điều này hoàn toàn không ảnh hưởng đến tốc độ, vì thông tin thực sự được chuyển qua dưới dạng các giá trị tĩnh tại thời điểm biên dịch. Nhược điểm duy nhất là nó gây ô nhiễm chữ ký phương thức (method signature) của bạn với các giá trị tùy chọn này, khi bạn chưa bao giờ thực sự muốn bất kỳ lệnh gọi nào hỗ trợ chúng tự động. Nhưng dù gì thì đó cũng là một cái giá nhỏ phải trả

5. Lớp 'Path' (The ‘Path’ class)

Mọi người biết tính năng này nhiều hơn, nhưng hầu như các lập trình viên vẫn còn lấy phần mở rộng tệp của tên tệp theo cách thủ công, khi mà lớp Path đã được những phương pháp được tích hợp sẵn, được thử và kiểm tra để giải quyết chuyện này. Lớp này nằm trong không gian tên System.IO và chứa nhiều phương thức hữu ích giúp giảm số lượng code mẫu bạn phải viết. Nhiều người sẽ quen thuộc với các phương thức như Path.GetFileName và Path.GetExtension nhưng có một phương thức mơ hồ hơn bên dưới:

Path.Combine

Phương thức này sử dụng 2,3 hoặc 4 đường dẫn và hợp nhất chúng lại. Nói chung, mọi người thực hiện việc này để nối tên tệp vào đường dẫn thư mục chẳng hạn như directoryPath + “\” + tên tệp. Vấn đề là bạn giả định rằng '\' là dấu phân tách thư mục trên hệ thống mà ứng dụng của bạn đang chạy – vậy điều gì sẽ xảy ra nếu ứng dụng đang chạy trên Unix, sử dụng dấu gạch chéo ('/') làm dấu phân tách thư mục (directory separator)? Đáng suy ngẫm, bởi vì hiện nay .NET Core đang cho phép các ứng dụng .NET chạy trên nhiều nền tảng hơn. Path.Combine sẽ sử dụng directory separator áp dụng được cho hệ điều hành và cũng sẽ xử lý các dấu phân tách thừa - tức là nếu bạn nối một thư mục có '\' ở cuối vào tên tệp có '\' ở đầu, Path.Combine sẽ bỏ một dấu phân tách thừa đi. Bạn có thể xem thêm lý do tại sao nên sử dụng Path.Combine tại đây

Path.GetTempFileName

Thông thường, bạn sẽ cần phải viết một tệp khi chỉ cần quyền truy cập tạm thời. Bạn không quan tâm đến tên hoặc nơi lưu trữ nó, bạn chỉ cần viết và đọc liền ngay sau đó.

Mặc dù bạn có thể tự viết code để quản lý việc này, nhưng sẽ rất chán và dễ xảy ra lỗi. Nhập phương thức Path.GetTempFileName cực kỳ hữu ích này trong lớp Path - nó tạo một tệp trống trong thư mục tạm thời do người dùng xác định và trả về đường dẫn đầy đủ để bạn sử dụng, mà không cần tham số. Vì nó nằm trong thư mục tạm thời của người dùng, Windows sẽ tự động duy trì điều này, bạn sẽ không cần phải lo lắng về việc hệ thống bị nghẽn bởi các tệp dư thừa. Xem bài viết này để biết một số thông tin về phương pháp, cùng với ‘GetTempPath’ có liên quan

Path.GetInvalidPathChars/Path.GetInvalidFileNameChars

Path.GetInvalidPathChars và người anh em của nó là Path.GetInvalidFileNameChars, trả về một mảng tất cả các ký tự không hợp lệ làm đường dẫn/tên tệp trên hệ thống hiện tại. Nhiều code chủ yếu loại bỏ thủ công một số ký tự không hợp lệ như các dấu ngoặc kép chứ thực chất là không phải các ký tự không hợp lệ đúng nghĩa. Trên tinh thần tương thích nhiều nền tảng, sẽ sai lầm khi cho rằng những gì không hợp lệ trên hệ thống này sẽ không hợp lệ trên hệ thống khác. Các phương pháp này không kiểm tra xem đường dẫn có chứa bất kỳ ký tự nào trong số này hay không, cùng xem các phương thức ‘boiler plate’ bên dưới:

6. StringBuilder

Xâu chuỗi (string concatenation) trong .NET được sử dụng phổ biến, nhưng sẽ rất kém hiệu quả nếu không được thực hiện đúng cách. Bởi vì các chuỗi là bất biến, quá trình xâu chuỗi nào cũng đều dẫn đến một chuỗi mới được trả về sau mỗi lần. Đối với một số lượng nhỏ các lần xâu chuỗi, sự khác biệt này sẽ là tối thiểu, nhưng hiệu suất có thể nhanh chóng giảm sút.

Nhập lớp StringBuilder, bạn sẽ được liên kết với chi phí hiệu suất tối thiểu. Cách thực hiện điều này khá đơn giản - ở cấp độ cao, nó lưu trữ danh sách mọi ký tự bạn thêm vào và chỉ xây dựng chuỗi của bạn khi bạn cần. Hãy xem đoạn code dưới đây để xem nó có hiệu quả thế nào (sử dụng lớp Stopwatch đã được đề cập trước đó):

Kết quả như sau:

Nhanh hơn gấp 250 lần! Chỉ với 10000 lần nối - nếu bạn đang tạo một tệp CSV lớn theo cách thủ công (dù đây là một cách tệ, nhưng đừng quá lo lắng), con số của bạn có thể sẽ lớn hơn. Nếu bạn chỉ nối một vài chuỗi, bạn có thể sử dụng nối mà không cần đến StringBuilder, nhưng chi phí khi tạo StringBuilder là cực nhỏ, hãy nghĩ về nó.

Trong mẫu code này sử dụng sb.Append, theo sau là sb.AppendLine - bạn thực sự có thể chỉ cần gọi sb.AppendLine, chuyển nó qua văn bản bạn muốn nối, nó sẽ thêm một dòng mới ở cuối. Append và AppendLine đi đôi với nhau sẽ làm rõ ràng hơn.

Kết luận

Rồi bạn cũng sẽ sớm thành thạo những tính năng này. Những lập trình viên kỳ cựu đã mất nhiều năm để tìm hiểu về chúng, bài viết này chắc chắn sẽ giúp ích cho một số lập trình viên ít kinh nghiệm hơn, bên cạnh đó là giúp tránh lãng phí thời gian khi sử dụng code mẫu và code dễ bị lỗi do Microsoft cung cấp.

 

Tổng hợp việc làm IT - Software trên VietnamWorks
VietnamWorks InTECH
Theo Chrisstclair