首页 > 编程语言 >C#不完全折腾:结构体,值类型,引用类型,泛型

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

时间:2024-08-20 13:04:48浏览次数:13  
标签:case index C# object 类型 num 泛型 new public

1. 背景

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

2. 结构体的初始化

  • 一个最基本的结构体格式如下:
//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,使用手动初始化也是允许的),在这种情况下是没有办法对一些动态长度(如数组等)的数据类型进行初始化的, 如下:
//rwd im0
public struct rwdim0
{
    public char[] order_num;//length 20
    public char[] serial_num;//length 16
}
  • 如果需要在结构体定义阶段就初始化长度,只需要在创建结构体的同时创建一个无参构造函数即可
    • 可以把初始值写在构造函数里
    • 也可以把初始值写在结构体的字段里,此时该结构体必须包含一个空的构造函数
    • 如果省略空的构造函数却把初值给到结构体字段内:此时会出现错误:CS8983:具有字段初始值设定项的结构必须包含显示声明的构造函数
        //初始化写在构造函数
        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
            }
        }
        //初始化写在结构体字段内,此时有一个空的构造函数
        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接收:
//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接收这个可变的结果:
            //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值,所以不能随意转换。
        //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]};又开始卡住了。
        //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();按照如下改动妄图扩大范围,结果依旧无济于事:
var fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  • 最终只能放弃从object->struct->dictionary的路线,如果有大佬知道问题出在哪里的话,希望可以帮我解答一下,感激不尽

4. 依靠泛型,返回出实际的结构体实例

  • 上面的坑踩了没走出来,于是舍弃返回object类型,如下,把GetStructInstance()方法改为返回一个泛型实例:
    • return (T)(object)是因为:结构体实例不能直接转换为泛型T,强转为object类的结构体也不能隐式转换为T,所以经过了两个显示转换。另外,泛型T是值类型还是引用类型取决于实际参数。
 //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上了,如果还是不行,可以考虑给结构体字段都加上public
//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约束泛型
    • 值类型和引用类型中间转换出现的错误及原因

标签:case,index,C#,object,类型,num,泛型,new,public
From: https://www.cnblogs.com/xiacuncun/p/18369277

相关文章

  • Acrobat DC安装报错1603,Microsoft Visual C++2013(x64)失败
    之前顺利安装过AcrobatDC,但可能因为自动更新了,导致让我重新登录才能使用,无法再次破解。于是我卸载后重新安装,发现提示MicrosoftVisualC++2013(x64)运行安装失败。我也在网上找了教程,在Adobe官网上下载了MicrosoftVisualC++2013(x64)进行自安装,安装后也可以在设置——应......
  • 【C语言】基础知识详解(二) 字符串
    一、什么是字符串?在C语言中,字符串是一种特殊的字符数组,用于存储一系列字符。字符串的表示:在C语言中,字符串是由字符组成的数组,并以空字符'\0'结束。空字符用于标识字符串的结束。例如,字符串"hello"在内存中实际上是{'h','e','l','l','o','\0'}。字符串声明:可以使......
  • LeetCode-Python-3154. 到达第 K 级台阶的方案数(DFS + 数学)
    给你有一个 非负 整数 k 。有一个无限长度的台阶,最低 一层编号为0。Alice 有一个整数 jump ,一开始值为0。Alice从台阶1开始,可以使用 任意 次操作,目标是到达第 k 级台阶。假设Alice位于台阶 i ,一次 操作 中,Alice可以:向下走一级到 i-1 ,但该操作......
  • 学懂C++(三十九):网络编程——深入详解 TCP 和 UDP 的区别和应用场景
    目录一、TCP的特点及应用场景1.可靠性2.流控制和拥塞控制3.有序传输4.应用场景二、UDP的特点及应用场景1.无连接2.不可靠性3.轻量级4.支持广播和多播5.应用场景三、TCP和UDP的区别四、TCP和UDP的工作原理1.TCP的工作原理三次握手数据传输......
  • 学懂C++(四十):网络编程——深入详解 HTTP、HTTPS 及基于 Windows 系统的 C++ 实现
    目录一、引言二、HTTP协议1.HTTP概述2.HTTP工作原理3.HTTP请求和响应格式HTTP请求格式4.HTTP状态码三、HTTPS协议1.HTTPS概述2.HTTPS工作原理四、基于Windows系统的C++实现1.准备工作2.HTTP客户端实现示例代码3.HTTPS客户端实现示例代......
  • 《安富莱嵌入式周报》第341期:Stack Overflow调查报告分享开发者年薪情况,开源USB高速分
    周报汇总地址:http://www.armbbs.cn/forum.php?mod=forumdisplay&fid=12&filter=typeid&typeid=104视频版:https://www.bilibili.com/video/BV1Gw4m1k7jw目录:1、开源多功能USB2.0高速分析仪2、开源100W微型无刷伺服电机控制器3、MicroChip新款DSC系单片机集成40Msps12bitAD......
  • D. Ice Cream Balls
    题目链接:https://codeforces.com/contest/1862/problem/D题目:思路讲解:首先观察样例和题目不难看出,x个不重复的数字一共能组成x(x-1)/2个数组,而当有重复的数字出现的时候,就只能多组成一个数组,那思路就很明确了,先找出最多能有几个不重复的数字,之后再加上需要完成的数组和已有的数......
  • CE修改器使用教程 [入门篇]
    原文地址:CE修改器使用教程[入门篇]-腾讯云开发者社区-腾讯云(tencent.com) CheatEngine一般简称为CE,它是一款开放源代码的作弊软件,其主要功能包括、内存扫描、十六进制编辑器、动态调试功能于一体,且该工具自身附带了安全工具,可以用它很方便的生成自己的脚本,CE可以说是目前......
  • 神经网络之卷积篇:详解单层卷积网络(One layer of a convolutional network)
    详解单层卷积网络如何构建卷积神经网络的卷积层,下面来看个例子。已经写了如何通过两个过滤器卷积处理一个三维图像,并输出两个不同的4×4矩阵。假设使用第一个过滤器进行卷积,得到第一个4×4矩阵。使用第二个过滤器进行卷积得到另外一个4×4矩阵。最终各自形成一个卷积神经网络......
  • 2024年全国青少年信息素养大赛国赛PYTHON组(C++做法)
    目录前提第一题第二题第三题第四题第五题:第六题前提鄙人是C++学生,所以将PYTHON题做为C++题,还请各位多多海涵!!!部分芝士来自度娘和其它网站温馨提示:题目顺序可能不同,请各位仔细浏览! 第一题题目描述蓝蓝最近学到了一些单词,比如orange(橘子),apple(苹果),pear(梨)。......