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
, C2
等)緩存表達式一次。)
我想知道是否有人對更好的方法有任何意見,或者我是否試圖“過度設計”這個問題?對於涉及一遍又一遍地傳遞魔法弦的情況,是否存在共同模式?
另一種選擇:使用反射,而不是使用C#6功能或編譯。現在,您正在處理編譯性能下降以防止反射性能下降(可能更少)。
...
var property = (PropertyInfo) ((MemberExpression)member.Body).Member;
var propertyName = property.Name;
var value = property.GetValue(obj, new object[0]);
...
好吧,你是對的, 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
約束,因此允許驗證數字,日期等值,但無論如何。