5评论

Attribute特性小案例-俄罗斯世界杯球迷入境

Mitty 2018-10-30 2.9k浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏开发行业精英群711501594

这是一篇之前写的老文章,当时在复习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;
        }
需要进行很多层的调用,移动端,尤其考虑性能角度,不是不能用,是尽量少用,但频繁使用是不可取的。

感谢您的阅读,如文中有误,欢迎指正,大家共同提高。