摘一段模式的說明, F#的: msdn是這麼描述它的:“模式”是用於轉換輸入數據的規則。模式將在整個 F# 語言中使用,采用多種方式將數據與一個或多個邏輯結構進行比較、將數據分解為各個構成部分,或從數據中提取信息。
模式匹配自有其定義,同時也有很多種類,這裡針對相對復雜的【結構比較】和【數據抽取】進行處理(有時候也叫類型檢查與轉換)。
直白點說,就是“檢查下某個對象,看看是否有我們感興趣的屬性成員,如果有就取出這些成員值供後續使用”。
1、結構比較
考察如下對象
code 01
var o = new { a = 2, b = 3, d = 0, c = new { a1 = 7, b1 = 2, e = new { name = "aaa", Id = 0 } } };
當我們明確知道其具體類型時,可以通過屬性訪問獲取相關值,
code 02
int r1=o.a; int r2=o.c.a1; string r3=o.c.e.name;
但是,當 類型不明確 時,比如:
code 03
method1(object obj)
在method1中,如何快速方便的獲取其相關屬性值?
首先,我們知道問題的出現是因為“類型不明確”,那麼我們要做的第一件是就是還原類型信息;
在還原類型信息之前,首先要把我們想獲取的信息描述出來,以 code 02 為例,
1、希望o上有一個名為a的屬性,類型int
2、希望o上有一個名為c的屬性,同時c上有一個名為a1的屬性, 類型int
3、希望o上有一個名為c的屬性,同時c上有一個名為e的屬性,同時e上有一個名為name的屬性 類型string
。。。。。。
不難發現,a、我們要描述的類型信息不必要與原類型一致,僅表示出期望得到的部分即可;
b、要描述的類型信息中能正確表達層級關系
c、要能夠描述所有類型的屬性成員
d、明確知道期望的類型信息
e、最好使用語言環境中直接提供的技術手段
綜合以上,這裡使用匿名對象進行類型描述,簡單而且能同時滿足以上5點。
code 04
var typeinfo = new { a = 3,//default(int) c = new { a1 = 1, e = new { name = default(string) } } };
注意:類型描述時屬性值沒有意義,一般可以用default(type),這裡使用值是為了後面比對結果。
有了類型描述後,進行類型檢查就變的相對簡單了,我們以類型描述信息為基准,逐個檢查目標對象上有無對應的成員即可。
直接使用反射就可以了。
code 05
if ( pi.Name==npi.Name&& pi.PropertyType == npi.PropertyType) { return true.Result(new GetValue(o => npi.Getter(o)));//擴展方法等見code 06 }
code 06
public struct Result<T> { public bool OK; public T Value; public Result(bool ok, T resultOrReason) { this.OK = ok; this.Value = resultOrReason; } public static implicit operator Result<T>(bool value) { return new Result<T>(value, default(T)); } public static explicit operator bool(Result<T> value) { return value.OK; } public static bool operator ==(Result<T> a, Result<T> b) { return a.Equals(b); } public static bool operator !=(Result<T> a, Result<T> b) { return !a.Equals(b); } public override bool Equals(object obj) { var r = (Result<T>)obj; return this.OK == r.OK && object.Equals(this.Value, r.Value); } public override int GetHashCode() { return this.OK.GetHashCode() + (this.Value == null ? 0 : this.Value.GetHashCode()); } } 同時返回bool和結果委托:
//返回實例上所有篩選值 public delegate IEnumerable<object> GetAllValues(object instance); //返回實例上某個值 public delegate object GetValue(object instance);
//擴展方法
//bool +結果 public static Result<Value> Result<Value>(this bool state, Value value) { return new Result<Value>(state, value); } //屬性取值, 反射 public static object Getter(this PropertyInfo info, object instance) { return info.GetValue(instance); } //新實例,反射 public static object New(this Type t, params object[] args) { return args.IsEmpty() ? Activator.CreateInstance(t) : Activator.CreateInstance(t, args); }
考慮到結構會出現嵌套情況,主要代碼下:
code 07
1 public static Result<GetAllValues> MatchType(this Type pattern, Type target) { 2 var pis = pattern.GetProperties(); 3 var tpis = target.GetProperties(); 4 if (pis.Length < tpis.Length) 5 { 6 7 var fac = new List<GetValue>(); 8 for (int i = 0; i < pis.Length; i++) 9 { 10 var pi = pis[i]; 11 var r = pi.MatchProp(tpis); 12 if (r.OK) 13 { 14 fac.Add(r.Value); 15 continue; 16 } 17 return false; 29 } 30 return true.Result(new GetAllValues(o => fac.Select(c => c(o)))); 31 } 32 return false; 33 } 34 static Result<GetValue> MatchProp(this PropertyInfo pi, IEnumerable<PropertyInfo> target) { 35 36 var npi = target.FirstOrDefault(c => c.Name == pi.Name)??(pi.Name=="_"?target.FirstOrDefault(c=>c.PropertyType==pi.PropertyType):null); 37 if (npi != null) { 38 if (pi.PropertyType.IsAnonymous() ) 39 { 40 var r = pi.PropertyType.MatchType(npi.PropertyType); 41 if (r.OK) { 42 return true.Result(new GetValue(o => pi.PropertyType.New(r.Value(npi.Getter(o)).ToArray()))); 43 } 44 } 45 else if ( pi.PropertyType == npi.PropertyType) 46 { 47 return true.Result(new GetValue(o => npi.Getter(o))); 48 49 } 50 } 51 return false; 52 53 }
代碼說明:
屬性使用 名稱+屬性類型進行檢查
如果類型描述中出現 匿名類型 屬性(line:38) ,進行層級檢查
屬性名稱為'_' 時忽略屬性名,即 匹配第一個類型相等的屬性(僅指明一種檢查擴展方式: 可以通過屬性信息進行特殊處理)
匹配成功後返回 針對目標對象的取值函數
2、目標值抽取
c#中無法方便的動態定義變量,因此,結構檢查完成,返回的結果為{true/false,取值函數} (Result<GetAllValues>)。
考慮使用方便,抽取值需要以友好的方式提供給使用者,這裡直接創建結構描述類型(匿名類型)的新實例作為返回結果
借助泛型
public static Result<TResult> AsPattern<TPattern, TResult>(this TPattern pattern, object matchobj, Func<TPattern, TResult> then) { var matchType = matchobj.GetType(); var patternType = typeof(TPattern); var matchResult = patternType.MatchType(matchType); if (matchResult.OK) { var patternInstance = patternType.New(matchResult.Value(matchobj).ToArray()); return true.Result(then((TPattern)patternInstance)); } return false; }
調用:
1 var result =typeinfo.AsPattern(o, (c) => c).Value;//result 類型為code 04中typeinfo 的類型 2 //result.a; 3 //result.c.a1; 4 //result.c.e.name;
3、多個模式匹配及方法匹配:
單個模式處理完成後, 多個模式處理 就是簡單的集合化。
方法匹配:如果需要在c#中也可以很方便的進行(無ref out 方法),慎用。
1、使用匿名委托描述方法:new {test=default(func<string,object>)} =》期望一個名稱為test,參數string,返回object的方法
2、首先檢查屬性:在目標中檢查有無 名稱為 test,類型為func<string,object> 的屬性,如不存在,則在目標方法中查找
關鍵代碼
方法簽名判斷
public static bool SignatureEqual(this MethodInfo mi, Type retType, IEnumerable<Type> paramTypes) { return mi.ReturnType == retType && paramTypes.SequenceEqual(mi.GetParameters().Select(p => p.ParameterType)); }
//方法與委托類型的參數和返回值是否一致 public static bool SignatureEqual(this MethodInfo mi, Type delegateType) { var cmi = delegateType.GetMethod("Invoke"); return mi.SignatureEqual(cmi); } public static bool SignatureEqual(this MethodInfo mi, MethodInfo nmi) { return mi.SignatureEqual(nmi.ReturnType, nmi.GetParameters().Select(p => p.ParameterType)); }
簽名一致後,返回方法調用
new GetValue(o => m.CreateDelegate(pi.PropertyType, o))//m MethodInfo
匹配完成後 直接通過 result.test("aaa")即可調用