Backstory:在整个项目中,我们不断发现自己使用System.ComponentModel.DataAnnotations.Validator
对象和属性来验证传递给API的属性。 Validator
对象需要传入属性名称和值,这是公平的,但我们都会被魔术字符串的[不断增加的]数量所吸引。传递了这些属性名称。这不仅会带来错误输入属性名称的风险,而且在其中一个属性被重命名的情况下也会降低可维护性(同样,它们有太多的属性)。
为了自动解析成员名称,我创建了这个帮助方法(为了简单起见,我们假设我只处理引用类型属性,因此P: class
约束) :
public static P ValidateProperty<T, P>(T obj, Expression<Func<T, P>> member, List<ValidationResult> results, Func<P, P> onValidCallback = null)
where T : class
where P : class
{
var expr = member.Compile();
var memberName = ((MemberExpression)member.Body).Member.Name;
var value = expr(obj);
bool isValid = Validator.TryValidateProperty(value, new ValidationContext(obj) { MemberName = memberName }, results);
return isValid ? (onValidCallback != null ? onValidCallback(value) : value) : null;
}
我只是这样称呼它:
var value = ValidateProperty(myObject, x => x.PropertyFoo, errors, result => result.Trim());
它的作用就像一个魅力,并没有涉及任何魔法字符串传递。
示例请求如下所示:
public class Request
{
public class C1
{
Property1;
Property2;
...
}
public class C2
{
Property1;
Property2;
...
}
public class C3
{
Property1;
Property2;
...
}
...
}
但我关注的是member.Compile()
对性能的影响。由于T, P
(C1, Property1
,C1, Property2
,C2, Property1
等有许多不同的可能排列,因此我无法缓存已编译的表达式并执行它在下一个电话中,除非T
和P
属于同一类型,否则很少发生。
我可以通过将Expression<Func<T, P>> member
更改为Expression<Func<T, object>> member
来优化此功能,以便现在每个T
类型(即C1
,{{{}}只需缓存一次表达式1}}等等。)
我想知道是否有人有更好的方法可以做到这一点,或者我是否想要超过工程师&#34;问题?对于涉及一遍又一遍地传递魔法弦的情况,是否存在共同模式?
答案 0 :(得分:1)
在C#6(VS2015)中,您可以使用nameof
。对ValidateProperty
方法的这种调用涉及一个额外的参数(属性名称与属性值),它引入了一些冗余,但解决了动态编译大量内容的潜在性能问题。
var value = ValidateProperty(myObject, nameof(PropertyFoo), PropertyFoo, errors, result => result.Trim());
答案 1 :(得分:1)
另一个选择:使用反射,而不是使用C#6功能或编译。现在,您正在为反射性能下降(可能更少)进行编译性能降低。
first_pistol
答案 2 :(得分:1)
嗯,你是对的,Expression.Compile
确实有很大的性能开销。如果性能确实是一个问题,并且另一个答案中提到的反射方法仍然与您有关,那么您可以通过以下方式实现编译的委托缓存:
public static class ValidateUtils
{
static readonly Dictionary<Type, Dictionary<string, Delegate>> typeMemberFuncCache = new Dictionary<Type, Dictionary<string, Delegate>>();
public static P ValidateProperty<T, P>(this T obj, Expression<Func<T, P>> member, List<ValidationResult> results, Func<P, P> onValidCallback = null)
where T : class
where P : class
{
var memberInfo = ((MemberExpression)member.Body).Member;
var memberName = memberInfo.Name;
Func<T, P> memberFunc;
lock (typeMemberFuncCache)
{
var type = typeof(T);
Dictionary<string, Delegate> memberFuncCache;
if (!typeMemberFuncCache.TryGetValue(type, out memberFuncCache))
typeMemberFuncCache.Add(type, memberFuncCache = new Dictionary<string, Delegate>());
Delegate entry;
if (memberFuncCache.TryGetValue(memberName, out entry))
memberFunc = (Func<T, P>)entry;
else
memberFuncCache.Add(memberName, memberFunc = member.Compile());
}
var value = memberFunc(obj);
bool isValid = Validator.TryValidateProperty(value, new ValidationContext(obj) { MemberName = memberName }, results);
return isValid ? (onValidCallback != null ? onValidCallback(value) : value) : null;
}
}
顺便说一下,我会删除P : class
约束,从而允许验证数字,日期等值,但无论如何。