C sharp不完全折腾:结构体,值类型,引用类型,泛型

1. 背景

  • 需要创建多个不同结构的结构体,依据输入的int数来选择并返回确定且唯一的结构体实例,并且实现把结构实例映射成一个字典(键值对)格式用来输出到richtextbox.

2. 结构体的初始化

  • 一个最基本的结构体格式如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //rwd im0
    public struct rwdim0
    {
    public ushort blocktype;
    public short blocklen;
    public ushort blockversion;
    public ushort manufactureID;
    public char[] order_num;//length 20
    public char[] serial_num;//length 16
    public short hardware_revision;
    public char fw_profix;
    public byte fw_cartridge;
    public byte fw_bugfix;
    public byte fw_internal_change;
    public short revirsion_counter;
    public short profil_ID;
    public short profil_specific_type;
    public ushort im_version;
    public ushort im_supported;
    }
  • 需要用这个结构体的时候,直接rwdim0 my_im0 = new rwdim0()(或者不使用new,使用手动初始化也是允许的),在这种情况下是没有办法在声明结构体时对数据进行初始化的, 如下:
    1
    2
    3
    4
    5
    6
    //rwd im0
    public struct rwdim0
    {
    public char[] order_num;//length 20
    public char[] serial_num;//length 16
    }
  • 如果需要在结构体定义阶段就初始化长度,只需要在创建结构体的同时创建一个无参构造函数即可
    • 可以把初始值写在构造函数里
    • 也可以把初始值写在结构体的字段里,此时该结构体必须包含一个空的构造函数
    • 如果省略空的构造函数却把初值给到结构体字段内:此时会出现错误:CS8983:具有字段初始值设定项的结构必须包含显示声明的构造函数
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      //初始化写在构造函数
      public struct im0
      {
      public char[] order_num;//length 20
      public char[] serial_num;//length 16
      public byte fw_bugfix;

      public im0()
      {
      order_num = new char[20];//char是值类型,默认的值是\0 而不是null;
      serial_num = new char[16];
      fw_bugfix = 0;//byte是值类型,默认的值是0, 而不是null
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      //初始化写在结构体字段内,此时有一个空的构造函数
      public struct im0
      {
      public char[] order_num = new char[20];//length 20
      public char[] serial_num = new char[16];//length 16
      public byte fw_bugfix = 0;
      public im0()
      {

      }
      }

3. 实现返回多种结构的方法

  • 这个实现看似很简单,好像用一个switch..case语句就能解决,但是在考虑如何接收返回值的时候,真正的难题才开始。
  • 如下:我首先考虑用object接收:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    //get struct
    public static object GetStructInstance(int index_num)
    {
    switch (index_num)
    {
    case 0:
    case 1:
    case 2:
    case 3:
    return new Ds0_1_2_3();
    case 64:
    case 65:
    return new Ds64_65();
    case 128:
    return new Ds128();
    case 192:
    case 193:
    case 194:
    case 195:
    return new Ds192_193_194_195();
    default:
    throw new ArgumentOutOfRangeException(nameof(index_num), "invalid index number");
    }
    }
  • 调用语句如下,用一个object类型的results接收这个可变的结果:
    1
    2
    //read data
    object results = DataSet_lib.GetStructInstance(Convert.ToInt16(DS_index_textbox.Text.ToString()));
  • 看似接收没有问题,但是要把results用作后续结构体示例变成字典实例的时候却犯了难,如下:是结构体实例变字典实例的实现方法:
    • 这里用到了泛型<T>,因为要求的输入是一个结构体,所以用了where T : struct来做约束
    • 在使用了where T : struct但是传入的参数却是一个object类型的时候,会出现一个很典型的错误:CS0453: 类型object必须是不可为null值的类型,才能用作泛型类型或者方法...中的参数T。这个错误表达了:因为object类型是引用类型,但是struct类型却是一个值类型,引用类型有null值,值类型没有null值,所以不能随意转换。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
          //struct 2 dictionary
      public static Dictionary<string, object> Struct2Dictionary<T>(T struc) where T : struct
      {
      //正常执行
      Dictionary<string, object> dict = new Dictionary<string, object>();
      var fields = typeof(T).GetFields();

      foreach (var field in fields)
      {
      dict.Add(field.Name, field.GetValue(struc));
      }
      return dict;
      }
      }
  • 为了解决CSO453的问题,我把where T : struct约束给删了,此时不报错了,但是运行的时候richtextbox一直拿不到值,也没有报错,百思不得其解,于是尝试用调试模式看一看。
    • 选择方法所在的事件,选中该行并右键,选择运行到光标处,随后按F11一步一步看数据。
    • 在进入Struct2Dictionary方法之前,需要传入的result已经能得到值了(通过调试模式看到的值),但是在Struct2Dictionary方法中,var fields = typeof(T).GetFields();这条语句最后拿到的fields一直是空的,出现一个红色提示:{System.Reflction.FieldInfo[0]},这表示反射出来的字段是空的。(另外此处我用的var 推断类型,其实实际类型应该是FieldInfo[],看反射出的类型就知道了。)
    • 为了解决这个问题,我先尝试在使用results之前先判断一下results里面的字段是不是公有的(否则有可能反射不出来),如下写了一个判断方法:
    • 方法结果是好的,可以正常判断数据是公共的,也能正常拿到返回值,但是正常的返回值一进Struct2Dictionary方法的var fields = typeof(T).GetFields();语句就只能得到{System.Reflction.FieldInfo[0]};又开始卡住了。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      //object 2 public object
      public static object ConvertResults(object results)
      {
      if (results != null)
      {
      Type resultsType = results.GetType();
      if (resultsType.IsNotPublic)
      {
      object publicResults = Activator.CreateInstance(resultsType);
      foreach (var field in resultsType.GetFields())
      {
      field.SetValue(publicResults, field.GetValue(results));
      }
      return publicResults;
      }
      }
      return results;
      }
  • 为了解决{System.Reflction.FieldInfo[0]},我甚至把var fields = typeof(T).GetFields();按照如下改动妄图扩大范围,结果依旧无济于事:
    1
    var fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  • 最终只能放弃从object->struct->dictionary的路线,**如果有大佬知道问题出在哪里的话,希望可以帮我解答一下,感激不尽**

4. 泛型,返回不同的结构体实例

  • 上面的坑踩了没走出来,于是舍弃返回object类型,如下,把GetStructInstance()方法改为返回一个泛型实例:
    • return (T)(object)是因为:结构体实例不能直接转换为泛型T,强转为object类的结构体也不能隐式转换为T,所以经过了两个显示转换。另外,泛型T是值类型还是引用类型取决于实际参数。
    • 泛型在创建的时候不必指定其具体的类型,但是调用的时候必须明确当前的具体类型。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      //get struct
      public static T GetStructInstance<T>(int index_num)
      {
      switch (index_num)
      {
      case 0:
      case 1:
      case 2:
      case 3:
      return (T)(object)new Ds0_1_2_3();
      case 64:
      case 65:
      return (T)(object)new Ds64_65();
      case 128:
      return (T)(object)new Ds128();
      case 192:
      case 193:
      case 194:
      case 195:
      return (T)(object)new Ds192_193_194_195();
      default:
      throw new ArgumentOutOfRangeException(nameof(index_num), "invalid index number");
      }
      }
  • 这种用法避免了直接返回object类型,返回的是已经确定好的类型,缺点是调用上需要写的代码量增加,不够灵活,如下:
    • 这种用法就能正确得到字典的值并输出到richtextbox上了。对于总是返回null的情况,考虑给结构体字段都加上public来允许反射。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      //read data
      switch (Convert.ToInt16(DS_index_textbox.Text.ToString()))
      {
      case 0:
      case 1:
      case 2:
      case 3:
      Ds0_1_2_3 ds0_1_2_3 = DataSet_lib.GetStructInstance<Ds0_1_2_3>(Convert.ToInt16(DS_index_textbox.Text.ToString()));
      my_ds_dic = DataSet_lib.Struct2Dictionary(ds0_1_2_3);
      break;
      case 64:
      case 65:
      Ds64_65 ds6465 = DataSet_lib.GetStructInstance<Ds64_65>(Convert.ToInt16(DS_index_textbox.Text.ToString()));
      my_ds_dic = DataSet_lib.Struct2Dictionary(ds6465);
      break;
      case 128:
      Ds128 ds128 = DataSet_lib.GetStructInstance<Ds128>(Convert.ToInt16(DS_index_textbox.Text.ToString()));
      my_ds_dic = DataSet_lib.Struct2Dictionary(ds128);
      break;
      case 192:
      case 193:
      case 194:
      case 195:
      Ds192_193_194_195 ds192_195 = DataSet_lib.GetStructInstance<Ds192_193_194_195>(Convert.ToInt16(DS_index_textbox.Text.ToString()));
      my_ds_dic = DataSet_lib.Struct2Dictionary(ds192_195);
      break;
      default:
      throw new ArgumentOutOfRangeException("invalid index number");
      }
      foreach (var ds in my_ds_dic)
      {
      readdata_richtextbox.AppendText($"{ds.Key}:{ds.Value}\n");
      }

5. 总结

  • 折腾了一天,总结一下哪些有用的
    • 结构体的初始化方法
    • object类型的使用,var类型的使用
    • 泛型的使用
    • 运行到光标处F11调试程序
    • where T : xxx约束泛型
    • 值类型和引用类型中间转换出现的错误及原因

C sharp不完全折腾:结构体,值类型,引用类型,泛型
http://example.com/2024/08/20/C sharp不完全折腾:结构体,值类型,引用类型,泛型/
作者
xiao cuncun
发布于
2024年8月20日
许可协议