只有你足够强,别人才会和你讲道理。
文章: 278
浏览: 2,533,069
  • 博主:笨木头
  • 关注:Unity、Net Core、Blazor
  • 邮箱:musicvs@163.com
笨木头  2017-06-11 11:20     Unity3D     阅读(4680)     评论(2)
转载请注明,原文地址: http://www.benmutou.com/archives/2346
文章来源:笨木头与游戏开发
转载请注明,原文地址:http://www.benmutou.com/archives/2346
文章来源:笨木头与游戏开发
 
在上一篇中,保存CSV文件每一行的数据为CSVDemo对象时,总是要用if条件判断当前是哪个字段,然后再给对象赋值。
这样太烦人了,要是在开发的过程中不断地有新的想法,不断地新增修改字段,那真是改死人了。
甚至完全失去了改动的兴趣,于是游戏的创意就大打折扣,后果十分严重,嗯嗯。(旁白:说了这么多,接下来就要宣传你的“产品”了吧?
 
不过,大家不用怕,有木头在,木头有个好招,大家看招吧。
 

1. 临时的字典结构

为了以后更方便,我们在搭建代码的时候,通常都会麻烦一点,但是,一劳永逸的事情,我是不会抗拒的。


为了实现我们的目的,我们需要先用一个字典结构把CSV文件保存起来,如下代码:
[cce_cs]
    /// <summary>
    /// 读取CSV文件
    /// 结果保存到字典集合,以ID作为Key值,对应每一行的数据,每一行的数据也用字典集合保存。
    /// </summary>
    /// <param name="filePath"></param>
    /// <returns></returns>
    public static Dictionary<string, Dictionary<string, string>> LoadCsvFile(string filePath)
    {
        Dictionary<string, Dictionary<string, string>> result = new Dictionary<string, Dictionary<string, string>>();

  string[] fileData = File.ReadAllLines(filePath);

  /* CSV文件的第一行为Key字段,第二行开始是数据。第一个字段一定是ID。 */   string[] keys = fileData[0].Split(',');

  for (int i = 1; i < fileData.Length; i++)   {   string[] line = fileData[i].Split(',');   /* 以ID为key值,创建一个新的集合,用于保存当前行的数据 */

  string ID = line[0];   result[ID] = new Dictionary<string, string>();

  for (int j = 0; j < line.Length; j++)   {   /* 每一行的数据存储规则:Key字段-Value值 */   result[ID][keys[j]] = line[j];   }   }   return result;   } [/cce_cs]
实际上大部分代码和上一篇是相同的,主要的改动解释如下:
a. 暂时抛弃了CSVDemo类,把CSV文件以【Dictionary<string, Dictionary<string, string>>】的形式保存起来
b. 相当于是,以每一行的ID作为key值,value值保存的是每一行的所有数据
c. 每一行的所有数据又由一个Dictionary<string, string>保存,key值是字段名,value值是每一列的数据值
d. 最终,每一行都以这样的形式保存起来
 
这样保存起来的结构并不是很方便,因为类型都是字符串,不好取值,没关系,这个只是过度的。
 

2. 强大的反射

你是否曾经有这样的感觉,那些看起来明明一样的代码,仅仅只是类型不一样;那些看起来明明是一样的代码,仅仅只是调用的属性不一样。
总之,那些看起来明明不应该那么重复的代码,它却总是重复了。
 
大多数开发者都会有这样的感觉,于是,逆天的反射和泛型出现在我们的面前。
没错,木头要用的就是反射+泛型这个强力的组合。
 
看看优化后的代码:
[cce_cs]
        /* 把CSV文件按行存放,每一行的ID作为key值,内容作为value值 */
        Dictionary<int, CSVDemo> csvDataDic = new Dictionary<int, CSVDemo>();

  /* CSV文件路径 */   string filePath = Application.streamingAssetsPath + "/CSVDemo.csv";

  /* 从CSV文件读取数据 */   Dictionary<string, Dictionary<string, string>> datasDic = LoadCsvFile(filePath);

  /* 遍历每一行数据 */   foreach (string ID in datasDic.Keys)   {   /* CSV的一行数据 */   Dictionary<string, string> datas = datasDic[ID];

  /* 读取Csv数据对象的属性 */   PropertyInfo[] props = typeof(CSVDemo).GetProperties();

  /* 使用反射,将CSV文件的数据赋值给CSV数据对象的相应字段,要求CSV文件的字段名和CSV数据对象的字段名完全相同 */   CSVDemo obj = new CSVDemo();   foreach (PropertyInfo pi in props)   {   pi.SetValue(obj, Convert.ChangeType(datas[pi.Name], pi.PropertyType), null);   }

  /* 按ID-数据的形式存储 */   csvDataDic[obj.ID] = obj;   } [/cce_cs]
主要修改如下:
a. 利用之前写的LoadCsvFile函数获取到字典结构的CSV文件内容
b. 遍历字典结构,取出每一行的内容
c. 利用反射获取CSVDemo类的所有属性
d. 便利CSVDemo类的所有属性,通过每一个属性名字到字典里获取数据内容,然后给CSVDemo对象相应的属性赋值
e. pi.SetValue就是用来给CSVDemo对象的某个属性赋值的,Convert.ChangeType是为了把字符串内容转换为属性对应的类型


 
最后来测试一下:
[cce_cs]
        /* 测试读取ID为1的数据 */
        CSVDemo csvDemo1 = csvDataDic[1];
        Debug.Log("ID=" + csvDemo1.ID + ",Name=" + csvDemo1.Name);
[/cce_cs]
尝试获取ID为1的那一行数据,输出日志如下:
结果是完全一样的,但是我们不需要再一个个if条件去判断然后赋值了,反射帮我们做了这件事情。(旁白:那泛型呢?

3. 泛型呢?

大家一定还有疑问,每个CSV文件都要写这样一段代码来读取吗?
不,这种重复的事情木头是绝对不会做的,我一做重复的事情就会头晕,于是,泛型救了我。
最终完美的读取并保存CSV文件的函数是这样的:
[cce_cs]
    /// <summary>
    /// 读取CSV文件数据(利用反射)
    /// </summary>
    /// <typeparam name="CsvData">CSV数据对象的类型</typeparam>
    /// <param name="csvFilePath">CSV文件路径</param>
    /// <param name="csvDatas">用于缓存数据的字典</param>
    /// <returns>CSV文件所有行内容的数据对象</returns>
    private Dictionary<int, T_CsvData> LoadCsvData<T_CsvData>(string csvFilePath)
    {
        Dictionary<int, T_CsvData> dic = new Dictionary<int, T_CsvData>();

  /* 从CSV文件读取数据 */   Dictionary<string, Dictionary<string, string>> result = LoadCsvFile(csvFilePath);

  /* 遍历每一行数据 */   foreach (string ID in result.Keys)   {   /* CSV的一行数据 */   Dictionary<string, string> datas = result[ID];

  /* 读取Csv数据对象的属性 */   PropertyInfo[] props = typeof(T_CsvData).GetProperties();

  /* 使用反射,将CSV文件的数据赋值给CSV数据对象的相应字段,要求CSV文件的字段名和CSV数据对象的字段名完全相同 */   T_CsvData obj = Activator.CreateInstance<T_CsvData>();

  foreach (PropertyInfo pi in props)   {   pi.SetValue(obj, Convert.ChangeType(datas[pi.Name], pi.PropertyType), null);   }

  /* 按ID-数据的形式存储 */   dic[Convert.ToInt32(ID)] = obj;   }   return dic;   } [/cce_cs]
基本上把CSVDemo类型改为T_CsvData泛型即可,调用方式如下:
[cce_cs]
        /* 把CSV文件按行存放,每一行的ID作为key值,内容作为value值 */
        Dictionary<int, CSVDemo> csvDataDic2 = LoadCsvData<CSVDemo>(filePath);

  /* 测试读取ID为2的数据 */   CSVDemo csvDemo2 = csvDataDic2[2];

  Debug.Log("ID=" + csvDemo2.ID + ",Name=" + csvDemo2.Name); [/cce_cs]
输出的日志如下:


怎么样?以后读取CSV文件,只需调用【LoadCsvData<CSVDemo>(filePath);】即可。
 

4. 缓存?

大家肯定还有疑问,每次要用到CSV文件的数据时,都加载一遍,太没效率了吧?
你说得对,这样太没效率了,所以下一篇,木头要开始聊聊如何用最帅的方式缓存CSV文件对象了。
 
 
2 条评论
  • xx 2020-09-18 18:52:34

    临时的字典结构是的话如果需要读的表格有bool类型 在后期pi.SetValue(obj, Convert.ChangeType(datas[pi.Name], pi.PropertyType), null) 时无法用changetype将string转为bool。。这个有什么好的解决方法吗?感谢
    0回复
    • 博主 笨木头 2020-09-20 18:51:23

      这个后来我也遇到了,需要特殊处理一下,通过下面这个封装的GetConvertedValue函数来转换:

      ///
      /// 获取转换类型后的属性值
      ///

      /// 对象类型
      ///
      /// 反射的属性对象
      /// 被赋值的对象
      public static object GetConvertedValue(Type type, object value)
      {
      /* ChangeType无法强制转换可空类型,所以需要这样特殊处理(参考:http://bbs.csdn.net/topics/320213735) */

      /* 布尔类型要特殊处理 */
      if (type.FullName.IndexOf("Boolean") > 0)
      {
      return Convert.ChangeType(TranBoolStringToNum(value.ToString()), type);
      }
      /* 枚举类型要特殊处理 */
      else if (type.BaseType == typeof(Enum))
      {
      return Enum.Parse(type, value.ToString());
      }
      else
      {
      return Convert.ChangeType(value, type);
      }
      }

      ///
      /// 将字符串类型的bool值转换为数字
      ///

      ///
      ///
      public static int TranBoolStringToNum(string value)
      {
      int bValue = 0;

      if (value.ToLower().Equals("true"))
      {
      bValue = 1;
      }
      else if (value.ToLower().Equals("false"))
      {
      bValue = 0;
      }
      else
      {
      bValue = Convert.ToInt16(value);
      }

      return bValue;
      }
      0回复
发表评论
粤ICP备16043700号

本博客基于 BlazorAnt Design Blazor 开发