“大菜”:源于自己刚抬入猿途混沌拾起,自我感觉不是一般的菜,因而得名“大菜”,于自身共勉。
不知不觉已经踏入坑已10余年之多,对于c#多多少少有一点自己的认识,写出来渴求同类抨击,对自己也算是个十年之痒的一个总结。
CLR支持两种类型:值类型和引用类型。 面试过很多5年左右的同学,有很多连值类型和引用类型的基本概念都回答不上来,难道现在的c#开发人员基础这么弱了吗?还是大家都不重视基础呢?这个随便找一篇博客都可以基础入门的。
哪些类型是引用类型呢?其实一个可以称为”类“的类型都是引用类型。 引用类型总是从托管堆上分配的,常用的语法就是New XX(). C#的new 操作符会返回对象的指针 - 也就是指向对象数据的内存地址的一个引用。引用类型的传递其实传递的是对象的指针(string类型比较特殊),所以在特定的场景下性能是高于值类型的。一个引用类型在创建时默认为null,也就是说当前变量不指向一个有效的对象,也就是我们常遇到的异常“未将对象引用设置到对象的实例”。
因为引用类型变量都需要进行一次堆内存的分配,这会给GC造成很大的压力,所以CLR提供了轻量级类型“值类型”。 值类型一般在线程栈上分配。(注意:值类型可以嵌入一个引用对象中)一个值类型变量其实就包含了值类型实例的值,所以它没有引用类型的指针(大家猜想值类型需不需要类型对象指针呢?)
值类型和引用类型都是System.Object的子类
值类型和引用类型都可以继承接口。(很多人都认为值类型不能继承接口)
interface Itest
{
void test();
}
struct TestStruct : Itest
{
public void test()
{
throw new NotImplementedException();
}
}
有的同学说值类型的性能高于引用类型,那为什么不都用值类型呢?引用类型也是如此。任何东西都有两面性,只有合适的类型,没有万能的类型。
所以装箱是比较耗费性能的,还有可能引发一次GC操作,而拆箱只是一个获取指针的过程耗费资源要比装箱小的多。注意:一个对象拆箱之后只能还原为原先未装箱之前的类型,例如:你不能把int32类型装箱后还原为int16类型。 所以面试的时候可以和面试官装B一下了~~
static void Main(string[] args) {
Console.WriteLine("test start");
int totalCount = 10000000;
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < totalCount; i++)
{
TestRef temp = new TestRef() { Id = i, Name = "test" };
}
sw.Stop();
Console.WriteLine($"引用类型耗时:{sw.ElapsedMilliseconds}");
sw.Reset();
sw.Start();
for (int i = 0; i < totalCount; i++)
{
TestVal temp = new TestVal() { Id = i, Name = "test" };
}
sw.Stop();
Console.WriteLine($"值类型耗时:{sw.ElapsedMilliseconds}");
Console.Read();
} class TestRef
{
public int Id { get; set; } public string Name { get; set; }
}
struct TestVal
{
public int Id { get; set; } public string Name { get; set; }
}
运行结果:
引用类型耗时:205
值类型耗时:152
可见初始化速度值类型是优于引用类型的,也可能是引用类型引发了GC导致。
static void Main(string[] args) {
Console.WriteLine("test start");
long totalCount = 1000000000;
Stopwatch sw = new Stopwatch();
sw.Start(); TestRef tempRef = new TestRef() { Id = 1, Name = "test" , Name2="r3rewfdsafdsa", Name3="fsrewfdsafdsafdsa", Name4="fdafdasfdsafdsa", Name5="432tretsfds", Name6="fdsafdasfdasfd" };
for (int i = 0; i < totalCount; i++)
{
TestR(tempRef);
}
sw.Stop();
Console.WriteLine($"引用类型耗时:{sw.ElapsedMilliseconds}");
sw.Reset();
sw.Start();
TestVal tempVal = new TestVal() { Id = 1, Name = "test", Name2 = "r3rewfdsafdsa", Name3 = "fsrewfdsafdsafdsa", Name4 = "fdafdasfdsafdsa", Name5 = "432tretsfds", Name6 = "fdsafdasfdasfd" };
for (int i = 0; i < totalCount; i++)
{
TestV(tempVal);
}
sw.Stop();
Console.WriteLine($"值类型耗时:{sw.ElapsedMilliseconds}");
Console.Read();
}
static void TestR(TestRef r)
{
return;
}
static void TestV(TestVal v)
{
return;
}
class TestRef
{
public int Id { get; set; }
public string Name { get; set; }
public string Name2 { get; set; }
public string Name3 { get; set; }
public string Name4 { get; set; }
public string Name5 { get; set; }
public string Name6 { get; set; }
}
struct TestVal
{
public int Id { get; set; }
public string Name { get; set; }
public string Name2 { get; set; }
public string Name3 { get; set; }
public string Name4 { get; set; }
public string Name5 { get; set; }
public string Name6 { get; set; }
}
运行结果:
引用类型耗时:4437
值类型耗时:5226
可见在普通情况下,作为参数值类型和引用类型用时差距不大,但是,如果值类型的实例属性比较多的情况下差距降进一步拉大。
非正式环境测试用例,结果仅供参考
不止是面试的时候经常问应用场景这个问题,就是自己平时写程序也应该清楚。程序设计选择的时候大部分场景都是用引用类型,但是如果你满足下列条件,值类型可能更适用:
顺便说一句,好久不写博客,样式真实花时间啊,后来干脆写markdown格式的,请大家见谅!!
请尊重一个猿的辛苦,转载请标明出处 ^ ~ ^ 。部分图片来源于网络,如果侵权请及时联系我。
让我们一起进步吧