Lấy tên thuộc tính từ biểu thức Lambda trong .NET

Biểu thức Lambda (Lambda Expression) là một tính năng mạnh mẽ của .NET, cho phép các nhà phát triển “diễn tả” một hoạt động. Một số thư viện OSS phổ biến nhất trong .NET ngày nay tận dụng các biểu thức: FluentValidation, AutoMapper, Dapper và Marten.

Các tác giả của các thư viện này sử dụng các biểu thức để giúp diễn giải ý định của người dùng thành mã thực thi và được sử dụng phổ biến nhất trong triển khai LINQ. Các biểu thức có thể có lợi trong các kịch bản mã dưới dạng cấu hình (code-as-configuration), trong đó các kiểu và giá trị của chúng ta là các đối tượng .NET.

Trong bài viết ngắn nhưng có giá trị này, chúng ta sẽ triển khai các phương thức mở rộng cho phép chúng ta truy xuất PropertyInfo từ một biểu thức lambda, hữu ích trong việc xây dựng các API hỗ trợ biểu thức của chúng ta.

Sử dụng biểu thức để lập bản đồ

Trước tiên, hãy xem một ví dụ có thể trông quen thuộc với người dùng AutoMapper.

[Fact]
public void Map()
{
    var mapper = new Mapper<LambdaTests.First, LambdaTests.Second>();

    mapper.Map(f => f.Id, t => t.Name);
}

Ở ví dụ trên, chúng ta sử dụng AutoMapper để ánh xạ đối tượng kiểu First sang đối tượng kiểu Sencond. Class Mapper ở trên có thể trông giống như thế này. Tôi đã bỏ qua việc triển khai bên trong cho ngắn gọn.

public class Mapper<TSource,TTarget>
{
    public Mapper<TSource, TTarget> Map<TProperty>(
        Expression<Func<TSource, TProperty>> from,
        Expression<Func<TTarget, TProperty>> to
    )
    {
        // store mappings
        return this;
    }

    public TTarget To(TSource source)
    {
        // map from source to target
        return default;
    }

    public TSource From(TTarget target)
    {
        // reverse map from target to source
        return default;
    }
}

Từ đây, chúng ta có thể truy cập kiểu dữ liệu nguồn và đích của mình, và với các biểu thức thành viên bổ sung, chúng ta cũng có thể truy xuất PropertyInfo được xác định bởi nhà phát triển.

Trong cách tiếp cận này, chúng ta có thể sử dụng thông tin ánh xạ để xử lý các giá trị khi chúng ta ánh xạ từ nguồn tới đích. Một ví dụ điển hình là chuyển đổi dữ liệu nhạy cảm như thông tin thẻ tín dụng, số nhận dạng chính phủ và ngày sinh.

Bây giờ chúng ta có các biểu thức, làm cách nào để lấy giá trị PropertyInfo của một biểu thức x => x.Id?

Truy xuất PropertyInfo từ một biểu thức Lambda

Chúng ta có thể sử dụng namespace System.Linq.Expressions để làm việc thông qua biểu thức do người dùng định nghĩa.

using System;
using System.Linq.Expressions;
using System.Reflection;

public static class Lambdas
{
    public static PropertyInfo GetPropertyInfo<TType, TReturn>(
        this Expression<Func<TType, TReturn>> property
    )
    {
        LambdaExpression lambda = property;
        var memberExpression = lambda.Body is UnaryExpression expression
            ? (MemberExpression) expression.Operand
            : (MemberExpression) lambda.Body;

        return (PropertyInfo) memberExpression.Member;
    }

    public static string GetPropertyName<TType, TReturn>(
        this Expression<Func<TType, TReturn>> property
    ) => property.GetPropertyInfo().Name;
}

Ví dụ sử dụng từ một lớp kiểm thử đơn vị (unit test) dưới đây cho thấy cách sử dụng các phương thức mở rộng.

using System;
using System.Linq.Expressions;
using Xunit;

public class LambdaTests
{
    [Fact]
    public void Can_get_name_of_Id()
    {
        Expression<Func<First,string>> expression = 
            f => f.Id;
        
        Assert.Equal(
            nameof(First.Id), 
            expression.GetPropertyName());
    }

    [Fact]
    public void Can_get_propertyInfo_of_Name()
    {
        Expression<Func<Second,string>> expression =
            f => f.Name;
        
        Assert.Equal(
            nameof(Second.Name),
            expression.GetPropertyInfo().Name);
    }

    [Fact]
    public void Can_set_value_using_property_info()
    {
        Expression<Func<Third,Guid>> expression =
            f => f.Guid;
        
        var info = expression.GetPropertyInfo();
        var expected = Guid.NewGuid();

        var target = new Third(Guid.Empty);
        info.SetValue(target, expected);
        
        Assert.Equal(expected, target.Guid);
    }

    public record First(string Id);
    public record Second(string Name);
    public record Third(Guid Guid);
    
}

Chúng ta có thêm một lợi ích khi có quyền truy cập PropertyInfo, đó là cho phép chúng ta thiết lập giá trị của bất kỳ thuộc tính nào trong thời gian chạy (runtime). Chúng ta có thể sử dụng phương thức SetValue và một đối tượng của kiểu dữ liệu đích. Thật tuyệt vời phải không nào!

Phần kết luận

Biểu thức Lambda (Lambda Expression) là một tính năng mạnh mẽ của .NET vì chúng có thể giúp người dùng thể hiện ý định. Trong các thư viện OSS như FluentValidation và Automapper, các nhà phát triển sử dụng các biểu thức Lambda làm cấu hình trong mã.

Nếu bạn đang lên kế hoạch xây dựng một API tương tự, hãy chuẩn bị cho nhiều generic, expression và reflection. Một cảnh báo cho các nhà phát triển, việc sử dụng reflection có thể phải trả giá bằng hiệu suất. Các nhà phát triển nên đo lường việc thực thi của nó và tác động của nó trong các tình huống thực tế.

Tôi hy vọng bạn thấy bài viết này hữu ích, và cảm ơn một lần nữa vì đã đọc.

Nếu Comdy hữu ích và giúp bạn tiết kiệm thời gian làm việc

Bạn có thể vui lòng đưa Comdy vào whitelist của trình chặn quảng cáo ❤️ để hỗ trợ chúng tôi trong việc trả tiền cho dịch vụ lưu trữ web để duy trì hoạt động của trang web.

Lập Trình C#LINQ
Bài Viết Liên Quan:
Tạo tập tin Zip với .NET 5
Trung Nguyen 11/11/2021
Tạo tập tin Zip với .NET 5

Trong bài viết này, chúng ta sẽ tìm hiểu lớp tiện ích ZipFile trong C#, cách nén tập tin và thư mục, cùng với giải nén tập tin zip.

Đọc và ghi file Excel trong C#
Trung Nguyen 29/10/2021
Đọc và ghi file Excel trong C#

Bài viết này sẽ giới thiệu cách đơn giản nhất mà tôi đã tìm thấy để đọc và ghi file Excel bằng C# sử dụng ExcelMapper.

Làm việc với PriorityQueue của .NET 6
Trung Nguyen 25/10/2021
Làm việc với PriorityQueue của .NET 6

Bài viết này sẽ giúp bạn tìm hiểu PriorityQueue của .NET 6 là gì, cách chúng ta thêm các phần tử và cách chúng ta có thể xếp hàng lại cho các phần tử.

Hướng dẫn nhanh và ví dụ về pattern matching trong C#
Trung Nguyen 23/10/2021
Hướng dẫn nhanh và ví dụ về pattern matching trong C#

Bài viết này sẽ trình bày một số ví dụ về pattern matching hữu ích và bạn có thể xem xét sử dụng trong các dự án hiện tại hoặc tương lai của bạn.