在游戏开发中经常需要把策划提供的Excel表格转换成Unity可以序列化读取的ScriptableObject对象。不同的单元格数据可能会被转换成不同的类型,如string,int,float,bool,Enum等。为了简洁转换代码,可以定义一个泛型方法ToValue
统一处理。
using System;
public class ExcelReader
{
protected T ToValue<T>(string s)
{
Type type = typeof(T);
object o = null;
if (type.IsEnum)
{
o = Enum.Parse(type, s);
}
else if (typeof(string).Equals(type))
{
o = s;
}
else if (typeof(bool).Equals(type))
{
o = s.Equals("1");
}
else if (typeof(int).Equals(type))
{
int value = 0;
int.TryParse(s, out value);
o = value;
}
else if (typeof(float).Equals(type))
{
float value = 0f;
float.TryParse(s, out value);
o = value;
}
return (T)o;
}
}
大部分情况下,这个方法已经够用了。但是它不能处理这种数据。
对于这个需求,首先想到的是定义一个专门处理数组的转换方法ToValueArray
。
public static readonly char[] separators = { '|', ',' };
public T[] ToValueArray<T>(string s)
{
var strs = s.Split(separators, StringSplitOptions.RemoveEmptyEntries);
T[] array = new T[strs.Length];
for (int i = 0; i < strs.Length; ++i)
{
array[i] = ToValue<T>(strs[i]);
}
return array;
}
但是这样写转换代码的时候可能就要一会儿ToValue(XX)
,又一会儿ToValueArray(XX)
。有没有办法直接实现类似ToValue<int[]>(XX)
呢?这样就一水的ToValue
下来,别提有多整洁优雅了。
通过查阅资料,我知道
- Type的属性IsArray可以判定类型是否是数组。
- Type的方法GetElementType可以获取数组的元素类型。
- 通过反射可以创建数组。
Activator.CreateInstance(数组元素类型, new object[] { 数组长度 });
- 数组类型都具有SetValue方法可以设置数组元素,有好几个重载。感兴趣的读者可以调用一下代码打印看看。
Type t = Type.GetType("System.Int32[]");
foreach(MethodInfo mi in t.GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
Console.WriteLine(mi.Name);
}
- MethodInfo的MakeGenericMethod方法可以生成泛型方法对应的实例方法,毕竟泛型方法没有办法直接Invoke。
结合以上5点,我可以在ToValue
里判断当前类型是否是数组,是的话获取对应的数组元素类型,并创建单元格数组对应长度的数组,通过反射调用ToValue
本身获取每个string元素对应的值,再通过SetValue
一一设置。
if (type.IsArray)
{
string[] strs = s.Split(separators, StringSplitOptions.RemoveEmptyEntries);
Type elementType = type.GetElementType();
MethodInfo mi = GetType().GetMethod("ToValue", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.ExactBinding);
mi = mi.MakeGenericMethod(elementType);
object array = Activator.CreateInstance(type, new object[] { strs.Length });
MethodInfo setValue = type.GetMethod("SetValue", new Type[2] { typeof(object), typeof(int) });
for (int i = 0; i < strs.Length; i++)
{
object v = mi.Invoke(this, new object[] { strs[i], false });
setValue.Invoke(array, new object[] { v, i });
}
return (T)array;
}
这样转换代码就整齐多了。
另外还可以制作一个转换代码的生成工具。只要我们定好表格的数据结构,就可以由工具批量的生成转换代码,从而从天量的表格转换中解脱出来。这里就不一一赘述了。