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.

Lập Trình C#LINQ
Bài Viết Liên Quan:
Loạt bài: Khám phá .NET 6
Trung Nguyen 02/04/2022
Loạt bài: Khám phá .NET 6

Trong loạt bài này, tôi sẽ xem xét một số

Các thành viên static abstract trong interface C# 10
Trung Nguyen 20/02/2022
Các thành viên static abstract trong interface C# 10

Ngôn ngữ C# đã bật các bộ tăng áp liên

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.