反射
Unity出的程序在安卓和windows都可以运行时使用反射,IOS不行
反射主要用于在实时获取静态变量,静态方法或者成员变量,成员方法。在一些只能在运行的时候才能确定对哪些成员进行赋值或者调用哪些方法的情况。例如,只能比对字符串找到对应的成员变量,然后对其进行赋值的情况。
这种实时的赋值不像在脚本中先写好对象名.成员名的方式进行赋值,哪些值对应赋值给哪些变量只有在运行的时候才能确定。
之前在项目中有用到这个东西,需求是在用户使用新版本的程序时,旧版本的程序的原先本地存储的用户设置值在用新版本安装的时候要保留,然后新的设置值采用程序配置中的默认值。
因为测试发现JsonUtility的覆盖某个对象的反序列化操作会直接将值没有覆盖到的默认值改为语言中的默认值,而 Newtonsoft.json没有覆盖某个对象中对应的方法的反序列化操作, 没有办法先将对象先用配置默认值先赋值一遍然后再用本地存储的用户设置覆盖一遍来达到目的。
另外Unity的JsonUtility对于纯List类型的存储需要额外写解析类,还有如果一个对象中有List类型,之前的尝试中没发现能序列化成功,时间关系就换回了成熟易用的Newtonsoft.json。
后来采用的方法是,额外存多一个键值对对象,这个对象表示当前设置中哪些设置值是已经存储了的。 值就是简单的为true,键是设置类的变量名字,这就需要用到反射来实时获得变量名然后存储起来了。
接下来流程是
在刚开始运行时反序列化这个键值对对象,比对每个字符串列表存储的成员名字,然后遍历设置类的反射中的所有成员名字,名字相同的,就用用户设置值赋值,不存在的,就用配置中的默认值赋值。
多存一个键值对对象还是需要滴,直接将设置存储的json反序列化出来的话,因为指定了反序列化的类,即使直接反序列化出来,也不能用反序列化的类的反射来获得当前设置与之前设置有哪些新增的值,因为反序列化出来的类的反射的成员名总是与当前的相同。
如果用默认值来判断,则要抛弃布尔变量的存储,而且整数和浮点数类型的变量的存储范围也要避开语言的默认值,这样的话,就要多两步看起来比较奇怪的范围映射。这类的写法维护性比较差。
下面给出测试有效的方法,这个方法最后没有用到项目中:
根据值来获得成员变量的名字
/// <summary>
/// 传入类的非静态成员变量的值的字符串
/// 输出这个字符串对应的非静态成员的变量名
/// 在一个对象的所有内部成员有重复值都很小的时候可以考虑使用 测试有效
/// </summary>
/// <param name="tpye"></param>
/// <param name="s"></param>
public static string GetTypeValue<T>(T t, string s)
{
Type tpye = t.GetType();
FieldInfo[] fieldInfos = tpye.GetFields();
for (int i = 0; i < fieldInfos.Length; i++)
{
if (fieldInfos[i] != null)
{
if (fieldInfos[i].GetValue(t) != null && fieldInfos[i].GetValue(t).ToString() == s)
{
return fieldInfos[i].Name;
}
}
}
return null;
}
使用方式
Test test = new Test();
Type t = test.GetType();
GetTypeValue(t, test.a) //返回变量a的名字字符串
或者
先在类中声明方法,这里是SettingsData
public static string GetClassName() {
return System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name; }
然后
Assembly assembly = Assembly.GetExecutingAssembly();
string settingsDataClassName = SettingsData.GetClassName();
//命名空间名.类名
Type type = assembly.GetType(settingsDataClassName);
遍历所有成员名
Assembly assembly = Assembly.GetExecutingAssembly();
if (assembly != null)
{
string settingsDataClassName = SettingsData.GetClassName();
if (settingsDataClassName != null)
{
//命名空间名.类名
Type type = assembly.GetType(settingsDataClassName);
if (type != null)
{
foreach (FieldInfo field in type.GetFields())
{
if (field != null)
{
// field.Name ..
}
}
}
}
}