Attribute特性小案例-俄罗斯世界杯球迷入境
发表于2018-10-30
这是一篇之前写的老文章,当时在复习Attribute的知识,又赶上在世界杯期间,就用世界杯来做一个Attribute的小案例来加深应用和理解
功能简述:
为了方便球迷“出入”俄罗斯观看世界杯比赛,在球迷购买了正式比赛任意场次的门票以后,可以通过球票相关的凭证信息在FIFA的官网申请FAN ID(一个球迷的“身份证”)
FAN ID是何物?
这是一个球迷的证件 ,上面有你护照的信息,姓名等等,FAN ID有如下几个作用:
1.凭借FAN ID 球迷可以在世界杯期间无限次的“进出”俄罗斯,这张FAN ID就相当于俄罗斯的签证Visa。
2.比赛日当天,凭FAN ID可以免费乘坐公共交通,如地铁,公交,甚至是往返城市的铁路,均是免费的。
下面就简单以这个小例子来演示一下特性Attribute的应用。实现两个功能:
1.外国公民入境--持有俄罗斯的VISA或者球迷FAN ID均可入境(VISA和FAN ID有其一即可)
2.比赛日当天--凭FAN ID可以免费乘坐公共交通。
我们先定义特性类:
[AttributeUsage(AttributeTargets.Field)] public sealed class IDENTITY_AUTHAttribute:System.Attribute { public string FAN_ID{ get; set;} public string FAN_NAME{ get; set;} public string VISA_ID{ get; set;} }
[AttributeUsage(AttributeTargets.Field)]
我们指定,IDENTITY_AUTHA特性只能应用在字段Field上。
解释说明:
定义IDENTITY_AUTHAttribute特性类,定义三个属性properties:
FAN_ID FAN_NAME VISA_ID
球迷只需要持有FAN ID就可以了,但我们在入境的途中,发生了一件小插曲,我的名字叫WANG HUAN,我们在FIFA注册的时候,用的英文名字的习惯,姓和名是反着的,填写的HUAN WANG,但入境时,俄罗斯那边说你的名字和你护照的的名字不符,因为是颠倒的,要求你改掉......必须打电话进行更正.....险些没有赶上飞机。
VISA_ID 即是你申请的俄罗斯签证,并假定它在有效期内。
将该特性应用在下面的公民上面,标识身份。
定义公民类:
public class Citizen{ public string passportNumber; public string name; public int age; public int gender;//1-male 2-female public Citizen(string passportNumber,string name,int age,int gender) { this.passportNumber = passportNumber; this.name = name; this.age = age; this.gender = gender; } }
解释说明:
定义Citizen类,添加了一些常用的字段Field,主要用到pasportNumber和name。
下面的代码有点点儿长,但都很简单,我还是分段来说,先通过Attribute为几个公民配置相关的信息,并将他们加入到容器中,方便我们遍历。
[IDENTITY_AUTH(FAN_ID="132993994",FAN_NAME="huanwang")] public Citizen wanghuan; [IDENTITY_AUTH(FAN_ID="663938249",FAN_NAME="feipeng")] public Citizen pengfei; [IDENTITY_AUTH(FAN_ID="991240982",FAN_NAME="gumenghua")] public Citizen gumenghua; [IDENTITY_AUTH(VISA_ID="9865326014343")] public Citizen bajia; [IDENTITY_AUTH] public Citizen hulei;
解释说明:
我们定义了5个公民,每个公民均使用IDENTITY_AUTH特性指定了不同的信息,有的只有VISA ID,也有的连护照都没有。
下面,初始化这些公民的信息,并加入到容器中。
public List<Citizen> CitizenList = new List<Citizen> (); // Use this for initialization void Start () { wanghuan = new Citizen ("10001", "wanghuan", 29, 1); pengfei = new Citizen ("10002", "pengfei", 30, 1); gumenghua = new Citizen ("10003", "gumenghua", 32, 2); bajia = new Citizen("10004","bajia",29,2); hulei = new Citizen ("10005", "hulei", 33, 1); CitizenList.Add (wanghuan); CitizenList.Add (pengfei); CitizenList.Add (gumenghua); CitizenList.Add (bajia); CitizenList.Add (hulei); }
10001,10002......这些是passportNumber中国公民的护照号码。后面的参数分别是name名字,age年龄,gender性别。
我们先实现第一个功能:
外国公民入境--持有俄罗斯的VISA或者球迷FAN ID均可入境(VISA和FAN ID有其一即可)
实现方式是通过反射获取公民身上的IDENTITY_AUTH特性,判断VISA_ID,如果没有则判断FAN_ID&&FAN_NAME。
(这里FAN_ID和FAN_NAME要同时进行校验,FAN_NAME正确的应该是你护照上的全拼,可以看到,上面公民wanghuan的身份信息是有误的,FAN_NAME="huangwang",而中国护照上的名字则是"wanghuan",这样无法通过校验)
//check in foreach (var citizen in CitizenList) { CheckIn (citizen); }
外国公民入境方法实现:
/// <summary> /// Checks in /// </summary> /// <returns><c>true</c>, if in was checked, <c>false</c> otherwise.</returns> /// <param name="val">Value.</param> public bool CheckIn(Citizen val) { Type t = typeof(AirportCheckIn);//.GetType (); FieldInfo info = t.GetField (val.name); if (info.IsDefined (typeof(IDENTITY_AUTHAttribute),false)) { IDENTITY_AUTHAttribute attribute = (IDENTITY_AUTHAttribute)Attribute.GetCustomAttribute (info, typeof(IDENTITY_AUTHAttribute)); if (attribute != null) { string str = ""; if (!string.IsNullOrEmpty(attribute.VISA_ID)) { //通过VISA入境 Debug.Log(val.name+" has the VISA of Russia.But do not have permission for free Public Transportation."); } else { str = val.name+" do NOT have the VISA of Russia."; if (!string.IsNullOrEmpty (attribute.FAN_ID)) { str += " But " + val.name + " has the FAN ID,we need to check it!"; if (attribute.FAN_NAME.Equals (val.name)) {//compare with name str += val.name + " FAN ID was approved,Public Transportation are free for you."; Debug.Log (str); } else { str += " Unfortunately," + val.name + " FAN ID is not same as passport name,please call FIFA FAN ID CENTER to change it!"; Debug.Log (str); return false; } } else { Debug.Log(val.name+" do not have VISA and FAN ID! REJECTED!"); return false; } } } } return true; }
这里的AirportCheckIn类是我定义公民信息的类,上面代码中我隐去了。
代码的排版上有点儿问题,可以copy到notepad++上查看,代码很简单,先判断VISA ID,如果有VISA ID(这里我们假定VISA ID只要配置均是正确的),则可以直接入境,如果VISA ID不存在,则继续判断FAN ID和FAN NAME做校验,如果也有效,则验证通过,可以入境。
那么,第二个功能也就更加的简单了。
比赛日当天--凭FAN ID可以免费乘坐公共交通。
//free public transportation foreach (var citizen in CitizenList) { CheckPublicTransportationFree (citizen); }
判断是否可以乘坐免费公共交通的方法:(假定我们当前就是比赛日,因为只有比赛日才可以有免费乘坐公共交通的机会)
/// <summary> /// Checks the public transportation free. /// </summary> /// <returns><c>true</c>, if public transportation free was checked, <c>false</c> otherwise.</returns> /// <param name="val">Value.</param> public bool CheckPublicTransportationFree(Citizen val) { Type t = typeof(AirportCheckIn);//.GetType (); FieldInfo info = t.GetField (val.name); if (info.IsDefined (typeof(IDENTITY_AUTHAttribute),false)) { IDENTITY_AUTHAttribute attribute = (IDENTITY_AUTHAttribute)Attribute.GetCustomAttribute (info, typeof(IDENTITY_AUTHAttribute)); if (attribute != null) { string str = ""; if (string.IsNullOrEmpty(attribute.FAN_ID)) { Debug.Log(val.name+" do not have FAN ID,you should buy tickets."); } else { str += val.name + " has the FAN ID."; if (attribute.FAN_NAME.Equals (val.name)) {//compare with name str += "and FAN ID was approved,Public Transportation are free for you! Enjoy the FIFA World Cup Show!"; Debug.Log (str); } else { str += " Unfortunately," + val.name + " FAN ID is not same as passport name,please call FIFA FAN ID CENTER to change it!"; Debug.Log (str); return false; } } } } return true; }
解释说明:
只需要通过反射获取IDENTITY_AUTH特性,判断FAN_ID&&FAN_NAME即可。
控制台输出:
上面的例子仅仅是为了演示Attribute的应用,但在实际上的开发中,尤其是在移动端,还是不要频繁的使用反射,上面的列子并没有调用传递参数相关的API,反射需要将参数方向在一个object[],这个过程可能会产生boxing/unboxing,会在拖管堆上分配内存空间等等消耗,而且反射是依赖字符串的,依赖字符串便会有遍历搜索的情况,效率比较低。
比如上面GetField的源码在C#中的实现如下:
public override FieldInfo GetField(String name, BindingFlags bindingAttr) { if (name == null) throw new ArgumentNullException(); Contract.EndContractBlock(); bool ignoreCase; MemberListType listType; RuntimeType.FilterHelper(bindingAttr, ref name, out ignoreCase, out listType); RuntimeFieldInfo[] cache = Cache.GetFieldList(listType, name); FieldInfo match = null; bindingAttr ^= BindingFlags.DeclaredOnly; bool multipleStaticFieldMatches = false; for (int i = 0; i < cache.Length; i++) { RuntimeFieldInfo fieldInfo = cache[i]; if ((bindingAttr & fieldInfo.BindingFlags) == fieldInfo.BindingFlags) { if (match != null) { if (Object.ReferenceEquals(fieldInfo.DeclaringType, match.DeclaringType)) throw new AmbiguousMatchException(Environment.GetResourceString("Arg_AmbiguousMatchException")); if ((match.DeclaringType.IsInterface == true) && (fieldInfo.DeclaringType.IsInterface == true)) multipleStaticFieldMatches = true; } if (match == null || fieldInfo.DeclaringType.IsSubclassOf(match.DeclaringType) || match.DeclaringType.IsInterface) match = fieldInfo; } } if (multipleStaticFieldMatches && match.DeclaringType.IsInterface) throw new AmbiguousMatchException(Environment.GetResourceString("Arg_AmbiguousMatchException")); return match; }
需要进行很多层的调用,移动端,尤其考虑性能角度,不是不能用,是尽量少用,但频繁使用是不可取的。
感谢您的阅读,如文中有误,欢迎指正,大家共同提高。