You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用属性选择器时如何区分string与string?可空类型

问题描述

现有一个Anonymizer类实现了IAnonymizer接口,其泛型Anonymize方法通过属性选择器匿名化实体的指定属性。但目前存在问题:无法区分**非可空字符串(string)和可空字符串(string?)**属性——两种场景下泛型参数TProperty的类型均为string,导致可空字符串属性无法被正确设置为null,而是被设置为<Anonymized>。

现有实现代码

Anonymizer类

public class Anonymizer : IAnonymizer
{
    public void Anonymize<TEntity, TProperty>(TEntity entity, Expression<Func<TEntity, TProperty>> propertySelector)
    {
        var value = typeof(TProperty) switch
        {
            { } t when IsNullable(t)  => (TProperty?)(object) null!,
            { } t when t == typeof(string) => (TProperty) (object) "<Anonymized>",
            { } t when t == typeof(int) => (TProperty) (object) -1,
            _ => default,
        };
        
        var memberExpression = (MemberExpression)propertySelector.Body;
        var property = (PropertyInfo)memberExpression.Member;
        property.SetValue(entity, value);
    }

    private static bool IsNullable(Type t)
    {
        return !t.IsValueType ||  Nullable.GetUnderlyingType(t) != null;
    }
}

测试用例

public class AnonymizerTests
{
    [Fact]
    public void Test_string()
    {
        var sut = new Anonymizer();
        
        var myObject = new MyClass {MyText = "Hello World", MyNumber = 42};

        sut.Anonymize(myObject, e => e.MyText);

        myObject.MyText.Should().Be("<Anonymized>");
        myObject.MyNumber.Should().Be(42);
    }

    
    [Fact]
    public void Test_int()
    {
        var sut = new Anonymizer();
        
        var myObject = new MyClass {MyText = "Hello World", MyNumber = 42};

        sut.Anonymize(myObject, e => e.MyNumber);

        myObject.MyText.Should().Be("Hello World");
        myObject.MyNumber.Should().Be(-1);
    }
    
        
    [Fact]
    public void Test_NullableString()
    {
        var sut = new Anonymizer();
        
        var myObject = new MyClass
        {
            MyText = "Hello World", 
            MyNumber = 42,
            MyNullableString = "Hello World"
        };

        sut.Anonymize(myObject, e => e.MyNullableString);
        
        myObject.MyNumber.Should().Be(42);
        myObject.MyNullableString.Should().BeNull();
    }

    class MyClass
    {
        public string MyText { get; set; }
        public int MyNumber { get; set; }
        public string? MyNullableString { get; set; }
    }
}
解决建议

核心问题是泛型参数TProperty会被C#的nullable特性“抹平”可空引用类型的标记——string?作为泛型参数传入时,编译器会将TProperty解析为string,导致无法通过typeof(TProperty)区分可空与非可空字符串。

解决思路是:直接从属性本身的元数据判断可空性,而非依赖泛型参数的类型。具体修改如下:

修改后的Anonymizer类

public class Anonymizer : IAnonymizer
{
    public void Anonymize<TEntity, TProperty>(TEntity entity, Expression<Func<TEntity, TProperty>> propertySelector)
    {
        // 先获取目标属性的元数据
        var memberExpression = (MemberExpression)propertySelector.Body;
        var property = (PropertyInfo)memberExpression.Member;
        var propertyType = property.PropertyType;

        // 根据属性的实际类型和可空性确定匿名化值
        object? value = propertyType switch
        {
            // 可空值类型(如int?)或可空引用类型(如string?)设为null
            { } t when IsPropertyNullable(property) => null,
            // 非可空字符串设为固定标记
            { } t when t == typeof(string) => "<Anonymized>",
            // 非可空int设为-1
            { } t when t == typeof(int) => -1,
            // 其他类型返回默认值
            _ => default(TProperty)
        };

        property.SetValue(entity, value);
    }

    /// <summary>
    /// 判断属性是否为可空类型(包括可空值类型和可空引用类型)
    /// </summary>
    private static bool IsPropertyNullable(PropertyInfo property)
    {
        // 1. 处理可空值类型(如int?)
        if (Nullable.GetUnderlyingType(property.PropertyType) != null)
            return true;

        // 2. 处理可空引用类型(如string?):检查属性上的NullableAttribute
        var nullableAttr = property.GetCustomAttribute<NullableAttribute>();
        if (nullableAttr != null)
        {
            // NullableAttribute.Argument数组中,1表示可空
            return nullableAttr.Argument.Length > 0 && nullableAttr.Argument[0] == 1;
        }

        // 3. 检查类级别的NullableContextAttribute,判断默认可空上下文
        var contextAttr = property.DeclaringType?.GetCustomAttribute<NullableContextAttribute>();
        if (contextAttr != null)
        {
            // Flag=1表示默认可空上下文
            return contextAttr.Flag == 1;
        }

        // 4. 兜底:引用类型在未启用nullable的项目中默认视为可空
        return !property.PropertyType.IsValueType;
    }
}

修改说明

  1. 优先获取属性元数据:不再依赖typeof(TProperty),而是直接从PropertyInfo获取属性的实际类型和可空标记。
  2. 完善可空判断逻辑:
    • 先判断是否为可空值类型(通过Nullable.GetUnderlyingType)。
    • 再检查属性是否带有NullableAttribute(C# 8+中string?会自动添加该标记)。
    • 最后检查类的默认可空上下文,确保在类级别的nullable设置下也能正确判断。
  3. 调整值的生成逻辑:根据属性的实际可空性决定设为null还是固定标记,解决了可空字符串的匿名化问题。

修改后,Test_NullableString测试用例将正常通过,可空字符串会被设置为null,而非可空字符串仍保持<Anonymized>的标记。

内容的提问来源于stack exchange,提问作者Stijn Van Antwerpen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 09:26:11