首页 > 编程语言 >浅谈C#中的值类型和引用类型

浅谈C#中的值类型和引用类型

时间:2024-08-23 16:27:31浏览次数:9  
标签:浅谈 C# fields Length result 类型 new string

1. 值类型

  • 常见的值类型:int/long/short/byte/float/double/bool/char/Struct(用户建立的结构体通常是值类型的)/Nullable Types(这是一个特殊的值类型,表示一个正常值或者空,比如int?)
  • 值类型的例子:
int a=10;
int b=a;

Console.WriteLine($"a:{a}");//a:10
Console.WriteLine($"b:{b}");//b:10

b=20;

Console.WriteLine($"a:{a}");//a:10,原始值不受影响
Console.WriteLine($"b:{b}");//b:20,只有b的值改变了
  • 值类型直接存储在内存(称之为栈(STACK),栈以LIFO访问,后进栈的数据先被访问,栈的大小是固定的,不是动态分配的,所以访问速度快)中,当把一个值赋值给另外一个变量时,其实是把变量的值复制给了新的变量,而不会改变原有值(a=10;b=a;b=20;这个例子中并不会因为b变成20了就反过来使a也变成20了,因为这个过程是复制,副本虽然变了,但是a=10这个原始值一直没有被改变。)
  • 当一个方法传递值类型的参数时(包括结构体),会将参数的值复制到函数的参数中,对参数的修改不会影响到原始变量;

2. 引用类型

  • 类,接口,委托,数组,字符串(字符串比较特殊,他可以像值类型一样用,但是它又具有不可变性。)
  • 用new动态分配内存,由GC(垃圾回收器)释放
  • 引用类型实际上操作的是地址。在C#中,你需要获取引用类型实例的地址只需要用&,如下
string str = "Hi";
IntPtr address = new IntPtr(&str)
  • 但是当想要在值类型实例上获得地址,就变得很困难,你可能需要先把该值类型封装在一个引用类型(比如元素是值类型的数组类型)中,然后再获取该引用类型的地址。
  • 在值类型传参时使用ref,ref实际上传入的不是值类型的值(副本),而是值类型的引用(地址)。如下:
public static void ModifyValue(ref int num)
{
  num = 42;
}
public static void Main()
{
  int value = 10;
  Console.WriteLine(Value);// output 10

  ModifyValue(ref value);
  Console.WriteLine(value);// output 42
}
  • 引用类型存储在内存的堆(Heap),动态分配,当在堆上分配了实例之后(new之后),访问该实例实际上是通过访问该实例的内存地址来访问该实例的。

3. 由值类型和引用类型不同引发的问题案例

  • 如下,有一个方法,这个方法尝试把一个字符串切分后的值准确的赋值给结构体实例:
    public static T ParseString2Struct<T>(string in_str) where T : struct
    {
        T result = default(T);
        //Type type = result.GetType();
        //object clone_result = Activator.CreateInstance(type);

        FieldInfo[] fields = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public);
        object parsedValue;

        var lines = in_str.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
        if (lines.Length != fields.Length)
        {
            throw new ArgumentException("richtextbox string length does not match the structer.");
        }

        for (int i = 0; i < fields.Length; i++)
        {
            var lineParts = lines[i].Split(':');
            if (lineParts.Length != 2 || lineParts[0].Trim() != fields[i].Name)
            {
                throw new ArgumentException("richtextbox string format does not match the structer.");
            }
            var value = lineParts[1].Trim();
            var fieldType = fields[i].FieldType;

            try
            {
                parsedValue = Convert.ChangeType(value, fieldType);
            }
            catch (Exception)
            {
                throw new ArgumentException("richtextbox string does not match the structer.");
            }
            if (!fields[i].IsInitOnly)
            {
                fields[i].SetValue(result, parsedValue);
            }          
        }
        //result = (T)clone_result;
        return result;
    }
}
  • 在这个方法里,使用T result = default(T);初始化值类型,这里不管是使用default(T)还是new T()其实本质都是获取了一个全新的值类型副本,但是default和new之间有一些小区别:用default的时候不会去获取结构构造函数中的初始值,而是直接使用该字段的数据类型的默认初始值。用new的时候程序会去扫描并使用该结构体构造函数中的初始值
  • 下面这句话用于调用该方法:
Ds64_65 ds6465 = DataSet_lib.ParseString2Struct<Ds64_65>(str);
  • 实际调试中发现一个很奇怪的现象,不管parsedValue值是多少,result的字段无论怎样都不能被赋值:
    • FieldInfo[] fields = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public);用来设定结构体字段的public属性。
    • fields[i].IsInitOnly用来检查每个字段都没有设置只读属性。
    • 为了检查FieldInfo[]类中的方法是否适用,甚至通过修改结构体构造函数的初始值再用new T()创建一个新的初始结构体,然后使用GetValue()方法,可以正常获得初始化值。
  • 通过询问AI才知道SetValue()方法对于值类型的注意点:
    • 1.值传递是传递的字段的副本,副本的改变对字段本身的值没有影响。
    • 2.传递的值和字段类型是兼容的,不兼容会抛异常,这一条上面的方法可以确保。
    • 3.对于结构体中的字段,SetValue()方法只会修改字段的副本,而不是原始结构体实例。意味着在使用SetValue()修改结构体之后,需要将修改后的副本重新赋值原始结构体实例.
  • SetValue()这个方法本身又不能传入ref类型,所以不能靠ref把修改后的值反馈回来。
  • 对于修改建议,AI建议是使用SetValueDirect:
    • __makeref():用于获取结构体字段的引用,底层特性,不建议直接使用
typeof(T).GetFields()[i].SetValueDirect(__makeref(result),parsedValue)
  • 通过GitHub上参考的代码, 找到一个更合适的解决方法,就是创建一个Clone,再把Clone赋值回去,如下:
    • 注意此方法生成的object clone_result,它是一个object类型,引用类型。那就不存在上面值类型放进SetValue()里的那些问题。最后再强转回T,给到result输出。
 public static T ParseString2Struct<T>(string in_str) where T : struct
 {
     T result = default(T);
     Type type = result.GetType();
     object clone_result = Activator.CreateInstance(type);

     FieldInfo[] fields = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public);
     object parsedValue;

     var lines = in_str.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
     if (lines.Length != fields.Length)
     {
         throw new ArgumentException("richtextbox string length does not match the structer.");
     }

     for (int i = 0; i < fields.Length; i++)
     {
         var lineParts = lines[i].Split(':');
         if (lineParts.Length != 2 || lineParts[0].Trim() != fields[i].Name)
         {
             throw new ArgumentException("richtextbox string format does not match the structer.");
         }
         var value = lineParts[1].Trim();
         var fieldType = fields[i].FieldType;

         try
         {
             parsedValue = Convert.ChangeType(value, fieldType);
         }
         catch (Exception)
         {
             throw new ArgumentException("richtextbox string does not match the structer.");
         }
         if (!fields[i].IsInitOnly)
         {
             fields[i].SetValue(clone_result, parsedValue);
         }          
     }
     result = (T)clone_result;
     return result;
 }
  • Clone可以成功解决该问题。

标签:浅谈,C#,fields,Length,result,类型,new,string
From: https://www.cnblogs.com/xiacuncun/p/18376335

相关文章

  • 生物素-LC-聚乙二醇1-NHS酯|Biotin-LC-PEG1-NHS ester
    基本信息英文名称:Biotin-LC-PEG1-NHSester英文同义词:Biotin-LC-PEG1-NHSester中文名称:生物素-LC-聚乙二醇1-NHS酯中文同义词:生物素-LC-聚乙二醇1-NHS酯;生物素-LC-一聚乙二醇-琥珀酰亚胺酯结构式:分子式(Molecularformula):C25H39N5O8S分子量(Molecularweight):569.67......
  • cas:2247545-20-4|Biotin-LC-PEG4-NHS ester|生物素-LC-四聚乙二醇-琥珀酰亚胺酯
    描述Biotin-LC-PEG4-NHSester中的NHS-PEG4-Biotin是一种聚乙二醇化水溶性试剂,用于对抗体、蛋白质和其他含伯胺的大分子进行简单有效的生物素标记。N-羟基琥珀酰亚胺酯(NHS)基团与赖氨酸和N-末端氨基特异性且有效地反应形成稳定的酰胺键。亲水性聚乙二醇(PEG)间隔臂赋予......
  • dlopen 加载使用了std::thread 的so 导致crash的问题分析
    c++11的的createimplement是在thread.cc中实现的,这意味着创建代码在libstdc++.so中,创建代码需要使用与平台有关的apigcc(g++isapartofgcc)的预期:没有调用的thread的代码,不会产生对pthread的依赖,更重要的,不同配置的gcc的线程模型是不同的,依赖库也不同(即不一定是pthrea......
  • 在Centos7上搭建EMQX服务
    一、安装Docker:安装包下载地址:Indexoflinux/static/stable/x86_64/1.1在Centos7进行安装前,可以使用以下命令查看CentOS版本。cat/etc/redhat-release1.2在CentOS7安装docker要求系统为64位、系统内核版本为3.10以上。uname-r2.1查看是否已安装docker列表......
  • YOLOv8改进 | 融合改进 | C2f融合EffectiveSE-Convolutional【完整代码 + 小白必备】
     秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转......
  • YOLOv5改进 | 融合改进 | C3 融合Efficient Multi-Scale Conv提升检测效果
      秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转......
  • Java之static静态代码块和方法
    文章目录一:static关键字二:静态变量三:静态方法四:静态代码块五:总结一:static关键字用于定义类级别的属性和方法,这些属性和方法属于类本身,而不是类的任何特定实例对象static修饰的方法或变量,优先于对象执行,所以内存会先有static修饰的内容,后有对象的内容static关键字......
  • CH340C芯片 串口通信
            CH340C是一种芯片‌,具体来说,它是CH340系列中的一款芯片,属于USB转串口芯片。这种芯片的主要功能是通过USB接口将计算机与外部硬件设备连接起来,实现通信。CH340C说白了就是电平转换芯片        可以将串口电平信号转换为USB信号    ......
  • 什么?!90%的ThreadLocal都在滥用或错用!
    最近在看一个系统代码时,发现系统里面在使用到了ThreadLocal,乍一看,好像很高级的样子。我再仔细一看,这个场景并不会存在线程安全问题,完全只是在一个方法中传参使用的啊!(震惊)难道是我水平太低,看不懂这个高级用法?经过和架构师请教和确认,这完全就是一个ThreadLocal滥用的典型案......
  • QPointer、QScopedPointer、QSharedPointer、QWeakPointer
    QPointer、QScopedPointer、QSharedPointer、QWeakPointerQSharedPointer:std::shared_ptrQWeakPointer:std::weak_ptrQScopedPointer:std::unique_ptrQPointer:无STL等效项。QObject析构时为空。QPointer功能:一个“半自动”的指针包装器。通常情况下,我们在手动delete一......