-
目录
值类型引用类型的区别
1.值类型的数据保存在内存的栈中;引用类型数据存储在内存的堆中,而内存单元只存放堆中对象的地址。
2.值类型存取速度快,引用类型存取速度慢
3.值类型表示实际数据,引用类型表示存储在内存堆中数据的地址或引用
4.值类型继承自System.ValueType,引用类型继承自System.Object
5.栈的内存分配是自动释放;而堆在.net 中会有GC来释放
堆与栈简单理解
C#程序在CLR上运行的时候,内存从逻辑上划分两大块:栈,堆。这俩基本元素组成我们C#程序的运行环境。
1、堆与栈概念介绍
堆:在c里面叫堆,在c#里面其实叫托管堆。
栈:就是堆栈,因为和堆一起叫着别扭,就简称为栈。
2、托管堆
托管堆不同于堆,它是由CLR(公共语言运行库(Common Language Runtime))管理,当堆中满了之后,会自动清理堆中的垃圾。所以,做为.net开发,我们不需要关心内存释放的问题。
3、内存堆栈与数据堆栈
内存堆栈:存在内存中的两个存储区(堆区,栈区)。
栈区:存放函数的参数、局部变量、返回数据等值,由编译器自动释放。
堆区:存放着引用类型的对象,由CLR释放。
数据堆栈:是一种后进先出的数据结构,它是一个概念,主要是栈区。
装箱和拆箱
装箱:将值类型转为引用类型,将栈内存搬到堆内存里。
拆箱:将引用类型转为值类型,将堆内存搬到值内存里。
这里的引用类型用object。
好处:不确定类型可以方便参数的存储和传递。
坏处:内存迁移,性能消耗,所以要少用。
注意:
装箱、拆箱只会发生在值类型和引用类型之间。
类的继承关系中的向上或向下转型并不会引起装箱、拆箱。例如,如果有一个类 Child
继承 自另一个类 Parent
,将 Child
类型的对象转换为 Parent
类型并不会导致装箱。这只是在内存中创建了一个指向同一对象的父类引用,而不会创建新的对象。
如何避免装箱拆箱操作
-
使用泛型集合
使用泛型集合(例如 List<T>
、Dictionary<TKey, TValue>
等)而不是非泛型集合(如 ArrayList
、Hashtable
等),因为泛型集合只能存储指定类型的元素,不会引起装箱和拆箱操作。
2.避免将值类型转换为引用类型:
- 避免将值类型转换为引用类型,特别是在性能敏感的代码路径中。
- 尽量使用泛型类型参数而不是
object
类型,以避免装箱。
- 尽量使用泛型类型参数而不是
3.使用 as
操作符:
- 如果你确实需要将引用类型转换为值类型,并且不确定是否会引起拆箱异常,可以使用
as
操作符进行安全转换。
显示转换和隐式转换
- 隐式转换(Implicit Conversion):
- 隐式转换是指在不需要额外操作的情况下,将一种数据类型转换为另一种数据类型。
- 隐式转换通常发生在转换目标类型的范围更大、更精确的情况下,这种转换是安全的,不会导致数据丢失。
- 例如,将
int
类型转换为long
类型是一种隐式转换,因为long
类型的范围比int
类型更大,不会导致数据丢失 - 例如,将子类转换成父类。
int a = 10; long b = a; // 隐式转换
class Parent {} class Child : Parent {} Child child = new Child(); Parent parent = child; // 隐式向上转型
2.显示转换(Explicit Conversion):
- 显示转换是指在需要额外操作的情况下,将一种数据类型转换为另一种数据类型。
- 显示转换通常发生在转换目标类型的范围比源类型更小、更不精确的情况下,这种转换可能导致数据丢失,需要程序员显式指定进行转换。
- 例如,将
double
类型转换为int
类型是一种显示转换,因为double
类型的范围比int
类型更大,可能导致数据精度丢失,需要程序员显式指定进行转换。 - 例如,父类转成子类。
double c = 10.5; int d = (int)c; // 显示转换
class Parent {} class Child : Parent {} Parent parent = new Child(); Child child = (Child)parent; // 显式向下转型
解释var 和 dynamic
1.var
- var 关键字用于声明隐式类型变量。编译器会根据变量的初始表达式推断琪类型,并在编译时将其替换成具体的类型。
- var 其类型是在编译时确定,一旦确定了类型,就不能在改变。
- var适用于静态类型语言的情况。
var number = 10; // 编译器会将 number 推断为 int 类型 var name = "John"; // 编译器会将 name 推断为 string 类型
2.dynamic
-
dynamic 关键字用于声明动态类型变量。动态类型在编译时不会进行类型检查,而是在运行时根据实际情况来确定类型。
-
dynamic 变量的类型是在运行时确定的,这意味着你可以在运行时改变变量的类型
-
dynamic 可以用于弱类型语言(如JavaScript)交互,或者用于编写需要在运行时处理各种类型的代码。
dynamic dynamicVar = 10; // dynamicVar 在运行时被确定为 int 类型 dynamicVar = "John"; // dynamicVar 在运行时被改变为 string 类型
c#中匿名类型是什么?
匿名类型是一种特殊的类型,他允许您在不事先定义类的情况下穿件对象。匿名类型通常用于零食存储一组相关的数据,并且在使用LINQ查询等情况下非常有用。
匿名类型的特点包括:
- 无需显示定义类:您不需要显示定义一个类来创建匿名类型。相反,您可以使用 new 关键字创建一个对象,并通过初始化语法为期提供属性的值。
- 自动属性:匿名类型属性是自动属性,编译器会根据初始化器中提供的属性名称和类型自动生成属性。
- 只读:匿名类型的属性是只读的,一旦创建就不能修改。
- 用于临时数据:通常用语存储一组相关的数据。
var person = new { Name = "John", Age = 30 }; Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
匿名类型的一些限制:
-
不能在方法之外的作用域使用
-
不能添加新的成员或扩展匿名类型
-
属性是只读的,不能更改其值。
多线程及其不同的状态
多线程允许在同一程序中并发多个任务。每个现成都有自己的执行路径,可以同时执行不同的代码段。线程可以处于不同的状态。主要包含以下几种状态:
- Unstarted(未启动):线程对象被创建但尚未启动。可以调用
Thread.Start()
方法来启动线程。 - Running(运行中):线程正在执行其代码。
- stopped(已停止):线程已经执行完毕,结束运行。
- Suspended(暂停):线程暂停执行,等待恢复执行。
- Background(后台):后台线程是一种特殊的线程,他在主线程结束时自动结束,即使他没有执行完。可以通过设置“
IsBackground
”属性为 ‘True’来创建后台线程。
泛型是什么?
泛型是一种编程特性,允许您编写可重用,类型安全的代码,而无需为每种数据类型编写不同的实现。通过泛型,可以编写具有参数化类型的类、接口和方法,以便他们能够在编译时使用不同的数据类型。
泛型的好处:
- 类型安全:泛型允许在编译时检查类型,从而减少了运行时类型错误的可能性
- 代码重用:通过编写泛型代码,可以编写一次代码,并将其应用于多种不同类型的数据,从而提高了代码的可重用性。
- 性能优化:泛型允许编译器生成特定的数据类型的代码,从而避免了装箱和拆箱操作,并提高了性能。
示例:
// 泛型类示例
public class GenericList<T>
{
private T[] data;
public GenericList(int capacity)
{
data = new T[capacity];
}
public void Add(T item)
{
// 添加元素到列表
}
// 其他方法...
}
// 使用泛型类
GenericList<int> intList = new GenericList<int>(10);
intList.Add(5);
intList.Add(10);
GenericList<string> stringList = new GenericList<string>(5);
stringList.Add("Hello");
stringList.Add("World");
可空类型
可空类型是C#语言中的一个特性,他允许基本数据类型具有额外的null值。通常情况下,c#中的值类型(如int、float、bool等)是不允许复制为null的,但通过可空类型,可以在值类型的基础上添加一个null值。
使用场景:
- 数据库处理:数据库中某些字段允许为空。
- api和外部数据:从外部接口或数据源中获取的数据类型可能包含缺失值,通过可空类型,可以更好表示这些数据。
- 业务逻辑:在某些业务场景中,值可能是位置的或不适用的,可使用可空类型表示。
示例:
int? nullableInt = null;
float? nullableFloat = 3.14f;
if (nullableInt.HasValue)
{
Console.WriteLine("nullableInt has value: " + nullableInt.Value);
}
else
{
Console.WriteLine("nullableInt is null");
}
if (nullableFloat.HasValue)
{
Console.WriteLine("nullableFloat has value: " + nullableFloat.Value);
}
else
{
Console.WriteLine("nullableFloat is null");
}
goto语句介绍
-
goto 语句由关键字 goto 后跟一个标签名称组成,通过标签名称指定跳转的位置。
-
可以在方法的任何地方放置标签,并且可以多次使用相同的标签。
示例
/// <summary>
/// 使用goto进行代码重试示例
/// </summary>
public static void GotoRetryUseExample()
{
int retryCount = 0;
for (int i = 0; i < 10; i++)
{
retryLogic:
try
{
//模拟可能出错的操作
Random random = new Random();
int result = random.Next(0, 2);
if (result == 0)
{
throw new Exception("Error occurred");
}
Console.WriteLine("Operation successful on attempt: " + retryCount);
}
catch (Exception ex)
{
retryCount++;
if (retryCount < 3)
{
Console.WriteLine("Error occurred, retrying...");
goto retryLogic; //跳转到重试逻辑
}
else
{
Console.WriteLine("Max retry limit reached.");
return;
}
}
}
}
/// <summary>
/// goto正常输出使用示例
/// </summary>
public static void GotoGeneralUseExample(int num)
{
if (num < 0)
{
goto LessThanZero;
}
else if (num == 0)
{
goto EqualToZero;
}
else
{
goto GreaterThanZero;
}
LessThanZero:
Console.WriteLine("数字小于零");
goto End;
EqualToZero:
Console.WriteLine("数字等于零");
goto End;
GreaterThanZero:
Console.WriteLine("数字大于零");
goto End;
End:
Console.WriteLine("End...");
}
注:虽然在某些情况下,goto
可以简化代码结构,但是它被广泛认为是一种不良的编程实践,因为它经常导致代码难以理解和维护。