C#中如何修改set行为实现字符串属性赋值自动Trim
字符串属性自动Trim()的落地方案
针对大量字符串属性需要统一在赋值时自动去除首尾空格的需求,不需要逐属性手写setter逻辑,以下是三种项目中常用的实现方式,按侵入性和改造成本从低到高排序:
1. 动态代理AOP拦截(改造成本最低)
- 实现逻辑:借助动态代理框架拦截所有属性set操作,判断属性类型为
string时,先对传入值执行Trim()再执行原赋值逻辑,不需要修改属性本身的写法。
以常用的Castle DynamicProxy为例,核心实现代码如下:
using Castle.DynamicProxy; public class StringTrimInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { // 匹配属性set方法(CLR中属性set方法为特殊命名方法,前缀固定为set_) if (invocation.Method.IsSpecialName && invocation.Method.Name.StartsWith("set_")) { string propName = invocation.Method.Name.Substring(4); var propertyInfo = invocation.Method.DeclaringType.GetProperty(propName); // 仅对string类型属性、且传入值非空时执行Trim if (propertyInfo.PropertyType == typeof(string) && invocation.Arguments[0] is string strValue) { invocation.Arguments[0] = strValue.Trim(); } } invocation.Proceed(); } }
使用时只需要两步:
- 实体类中需要自动Trim的字符串属性标记为
virtual(供代理类重写),非字符串属性不受任何影响:
public class User { public virtual string FirstName { get; set; } public virtual string LastName { get; set; } public int Age { get; set; } }
- 实例化实体时通过代理生成器创建对象,代替直接
new:
var proxyGenerator = new ProxyGenerator(); var user = proxyGenerator.CreateClassProxy<User>(new StringTrimInterceptor()); user.FirstName = " Zhang San "; // 赋值后值自动变为"Zhang San"
- 优点:现有代码改动量极小,Trim逻辑统一维护,后续调整规则只需要改拦截器即可
- 缺点:实体不能直接实例化,必须走代理创建流程;有极少量运行时反射开销,常规业务场景完全感知不到
2. 编译期源代码生成(性能最优)
- 实现逻辑:通过C#源代码生成器在编译阶段扫描代码,对标记了自动Trim特性的字符串属性,自动生成带Trim逻辑的完整属性代码(和手写带backing field的属性逻辑完全一致),运行时没有任何额外开销。
使用逻辑很简单:
- 定义一个空的标记特性:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class AutoTrimStringsAttribute : Attribute { }
- 给需要自动处理的实体类加上特性和
partial关键字,类内部所有字符串自动属性都会在编译期被注入Trim逻辑:
[AutoTrimStrings] public partial class Product { public string ProductName { get; set; } public string Description { get; set; } public decimal Price { get; set; } // 非字符串属性不做处理 }
- 优点:运行时性能和手写代码完全一致,不需要修改实体实例化逻辑,直接
new即可正常使用 - 缺点:需要引入或自行实现源代码生成器,实体类需要添加
partial修饰符
3. 统一基类封装(无第三方依赖)
- 实现逻辑:让所有实体继承统一基类,基类内部封装属性赋值的通用逻辑,字符串类型赋值时自动执行Trim。
核心基类代码示例:
public abstract class EntityBase { private readonly Dictionary<string, object> _propertyStore = new(); protected string GetStringProp(string propName) { return _propertyStore.TryGetValue(propName, out var val) ? (string)val : null; } protected void SetStringProp(string propName, string value) { _propertyStore[propName] = value?.Trim(); } }
实体类实现时调用基类的存取方法即可:
public class Order : EntityBase { public string OrderNo { get => GetStringProp(nameof(OrderNo)); set => SetStringProp(nameof(OrderNo), value); } }
- 优点:不需要引入第三方组件,逻辑完全可控
- 缺点:属性写法比自动属性繁琐,相比逐属性手写setter简化幅度有限,适合对第三方依赖限制极严的场景
注意:所有Trim逻辑都需要提前做null值判断,避免对null调用
Trim()抛出空引用异常,上述示例代码均已处理该边界情况。
内容的提问来源于stack exchange,提问作者Mahsa stz
相关产品推荐
相关产品推荐

