.NET C# 装箱与拆箱

2024-06-19 12:04
文章标签 c# net .net netcore 装箱 拆箱

本文主要是介绍.NET C# 装箱与拆箱,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

.NET C# 装箱与拆箱

目录

  • .NET C# 装箱与拆箱
    • 1 装箱 (Boxing)
      • 1.1 过程:
      • 1.2 示例:
    • 2 拆箱 (Unboxing)
      • 2.1 过程:
      • 2.2 示例:
    • 3 性能影响
    • 4 性能优化
      • 4.1 使用泛型集合
        • 示例:
      • 4.2 使用`Nullable<T>`
        • 示例:
      • 4.3 避免使用非泛型集合
        • 示例:
      • 4.4 避免在接口类型上操作值类型
      • 4.5 使用结构体实现接口时,小心装箱
        • 示例:
      • 4.6 使用`readonly struct`优化性能
        • 示例:
      • 4.7 使用`Span<T>`和`Memory<T>`
        • 示例:
      • 4.8 使用内联方法(`[MethodImpl(MethodImplOptions.AggressiveInlining)]`)
        • 示例:

在C#中,装箱和拆箱是将值类型转换为引用类型及其逆过程的操作。理解装箱和拆箱对于性能优化和正确使用类型转换非常重要。下面详细介绍这两个过程。

1 装箱 (Boxing)

装箱是将值类型转换为引用类型的过程。值类型存储在栈中,而引用类型存储在堆中。装箱过程会将值类型的值拷贝到堆中,并创建一个引用对象。

1.1 过程:

  1. 在堆中分配内存以存储值类型数据和类型信息。
  2. 将值类型数据拷贝到新分配的内存中。
  3. 返回对该内存的引用。

1.2 示例:

int value = 123;          // 值类型
object obj = value;       // 装箱,将值类型转换为引用类型

在这个示例中,value是一个整数,属于值类型。当我们将它赋值给obj时,发生了装箱操作,obj现在是对堆中整数值123的引用。

2 拆箱 (Unboxing)

拆箱是将引用类型转换回值类型的过程。拆箱时必须确保引用类型实际上是被装箱的值类型,否则会抛出InvalidCastException

2.1 过程:

  1. 检查引用是否指向已装箱的值类型数据。
  2. 从堆中提取值类型数据并复制到栈上的值类型变量中。

2.2 示例:

object obj = 123;         // 装箱
int value = (int)obj;     // 拆箱

在这个示例中,obj包含装箱的整数值123。当我们将obj拆箱为整数时,需要进行显式类型转换,即(int)obj。这会将引用类型转换回值类型,并将数据复制回栈中的value

3 性能影响

  • 装箱和拆箱是开销较大的操作,因为它们涉及堆内存的分配和释放以及数据的复制。
  • 尽量避免频繁的装箱和拆箱操作。例如,使用List<int>而不是ArrayList来存储整数,这样可以避免装箱和拆箱操作,因为List<int>是类型安全的。

4 性能优化

一些策略和技巧可以帮助减少或避免装箱和拆箱操作。以下是一些详细的方法:

4.1 使用泛型集合

泛型集合类如List<T>Dictionary<TKey, TValue>Queue<T>Stack<T>等,都可以避免值类型的装箱和拆箱。泛型允许在编译时确定类型,从而直接在值类型上操作,而不需要转换为引用类型。

示例:
// 使用泛型集合避免装箱和拆箱
List<int> numbers = new List<int>();
numbers.Add(42);          // 不会发生装箱
int number = numbers[0];  // 不会发生拆箱

4.2 使用Nullable<T>

Nullable<T>结构(也可以使用T?语法)允许值类型可以为空,而不需要装箱。

示例:
int? nullableInt = 5;     // 不会发生装箱
nullableInt = null;       // 不会发生装箱if (nullableInt.HasValue)
{int value = nullableInt.Value;  // 不会发生拆箱
}

4.3 避免使用非泛型集合

尽量避免使用非泛型集合类如ArrayListHashtableQueueStack等,因为它们会导致值类型的装箱和拆箱。可以使用对应的泛型版本。

示例:
// 避免使用ArrayList,因为它会导致装箱和拆箱
ArrayList arrayList = new ArrayList();
arrayList.Add(42);        // 装箱
int value = (int)arrayList[0]; // 拆箱// 使用List<int>来避免装箱和拆箱
List<int> list = new List<int>();
list.Add(42);             // 不会发生装箱
int value2 = list[0];     // 不会发生拆箱

4.4 避免在接口类型上操作值类型

当值类型通过接口进行操作时,会发生装箱。例如:

int x = 10;
IComparable comparable = x;  // 装箱
int result = comparable.CompareTo(5); // 拆箱

避免这种情况的方法是直接在值类型上操作,而不是通过接口:

int x = 10;
int result = x.CompareTo(5);  // 不会发生装箱和拆箱

4.5 使用结构体实现接口时,小心装箱

如果结构体实现了接口,并且接口方法在结构体实例上被调用时,会发生装箱。可以通过显示实现接口或者直接调用结构体的方法来避免装箱。

示例:
struct MyStruct : IComparable<MyStruct>
{public int CompareTo(MyStruct other){// 实现接口方法return 0;}
}MyStruct a = new MyStruct();
MyStruct b = new MyStruct();
int result = a.CompareTo(b);  // 不会发生装箱IComparable<MyStruct> comparable = a;
result = comparable.CompareTo(b); // 装箱

4.6 使用readonly struct优化性能

在一些场景下,使用readonly struct可以提高性能,因为它们是不可变的,编译器可以进行更多优化,并且在某些情况下可以减少装箱。

示例:
readonly struct ImmutablePoint
{public int X { get; }public int Y { get; }public ImmutablePoint(int x, int y){X = x;Y = y;}
}

4.7 使用Span<T>Memory<T>

在需要处理大块内存时,使用Span<T>Memory<T>可以避免装箱和拆箱,提供更高效的内存操作。

示例:
Span<int> span = stackalloc int[100]; // 不会发生装箱
for (int i = 0; i < span.Length; i++)
{span[i] = i; // 直接操作,不会发生装箱和拆箱
}

4.8 使用内联方法([MethodImpl(MethodImplOptions.AggressiveInlining)]

在性能关键的代码中,内联方法可以减少函数调用开销,并在某些情况下避免装箱和拆箱。

示例:
using System.Runtime.CompilerServices;struct MyStruct
{public int Value;[MethodImpl(MethodImplOptions.AggressiveInlining)]public void SetValue(int value){Value = value;}
}

通过综合使用这些方法,可以有效减少或避免装箱和拆箱操作,提高C#程序的性能。

这篇关于.NET C# 装箱与拆箱的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1074972

相关文章

C#中lock关键字的使用小结

《C#中lock关键字的使用小结》在C#中,lock关键字用于确保当一个线程位于给定实例的代码块中时,其他线程无法访问同一实例的该代码块,下面就来介绍一下lock关键字的使用... 目录使用方式工作原理注意事项示例代码为什么不能lock值类型在C#中,lock关键字用于确保当一个线程位于给定实例的代码块中时

C# $字符串插值的使用

《C#$字符串插值的使用》本文介绍了C#中的字符串插值功能,详细介绍了使用$符号的实现方式,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录$ 字符使用方式创建内插字符串包含不同的数据类型控制内插表达式的格式控制内插表达式的对齐方式内插表达式中使用转义序列内插表达式中使用

C#中的Converter的具体应用

《C#中的Converter的具体应用》C#中的Converter提供了一种灵活的类型转换机制,本文详细介绍了Converter的基本概念、使用场景,具有一定的参考价值,感兴趣的可以了解一下... 目录Converter的基本概念1. Converter委托2. 使用场景布尔型转换示例示例1:简单的字符串到

C#监听txt文档获取新数据方式

《C#监听txt文档获取新数据方式》文章介绍通过监听txt文件获取最新数据,并实现开机自启动、禁用窗口关闭按钮、阻止Ctrl+C中断及防止程序退出等功能,代码整合于主函数中,供参考学习... 目录前言一、监听txt文档增加数据二、其他功能1. 设置开机自启动2. 禁止控制台窗口关闭按钮3. 阻止Ctrl +

C#解析JSON数据全攻略指南

《C#解析JSON数据全攻略指南》这篇文章主要为大家详细介绍了使用C#解析JSON数据全攻略指南,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、为什么jsON是C#开发必修课?二、四步搞定网络JSON数据1. 获取数据 - HttpClient最佳实践2. 动态解析 - 快速

C#连接SQL server数据库命令的基本步骤

《C#连接SQLserver数据库命令的基本步骤》文章讲解了连接SQLServer数据库的步骤,包括引入命名空间、构建连接字符串、使用SqlConnection和SqlCommand执行SQL操作,... 目录建议配合使用:如何下载和安装SQL server数据库-CSDN博客1. 引入必要的命名空间2.

C#读写文本文件的多种方式详解

《C#读写文本文件的多种方式详解》这篇文章主要为大家详细介绍了C#中各种常用的文件读写方式,包括文本文件,二进制文件、CSV文件、JSON文件等,有需要的小伙伴可以参考一下... 目录一、文本文件读写1. 使用 File 类的静态方法2. 使用 StreamReader 和 StreamWriter二、二进

C#中Guid类使用小结

《C#中Guid类使用小结》本文主要介绍了C#中Guid类用于生成和操作128位的唯一标识符,用于数据库主键及分布式系统,支持通过NewGuid、Parse等方法生成,感兴趣的可以了解一下... 目录前言一、什么是 Guid二、生成 Guid1. 使用 Guid.NewGuid() 方法2. 从字符串创建

C# 比较两个list 之间元素差异的常用方法

《C#比较两个list之间元素差异的常用方法》:本文主要介绍C#比较两个list之间元素差异,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. 使用Except方法2. 使用Except的逆操作3. 使用LINQ的Join,GroupJoin

解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题

《解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题》:本文主要介绍解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4... 目录未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘打开pom.XM