- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
C# 是一种强类型语言。 每个变量和常量都有一个类型,每个求值的表达式也是如此。 每个方法声明都为每个输入参数和返回值指定名称、类型和种类(值、引用或输出)。 .NET 类库定义了内置数值类型和表示各种构造的复杂类型。 其中包括文件系统、网络连接、对象的集合和数组以及日期。 典型的 C# 程序使用类库中的类型,以及对程序问题域的专属概念进行建模的用户定义类型.
简单来说就是指:类,参数,字段,属性,方法,模块,程序集等元素 。
//命名空间(模块)=>类型系统
namespace Example_4_1
{
//类属于类型系统
class UserLogin
{
//字段,属性=>类型系统
private string username;
private string password;
//构造方法=>类型系统
public UserLogin(string username, string password)
{
this.username = username;//参数=>类型系统
this.password = password;
}
//方法=>类型系统
public bool Login(string inputUsername, string inputPassword)
{
if (inputUsername == username && inputPassword == password)
{
return true;
}
else
{
return false;
}
}
}
}
编译器使用类型信息来确保在代码中执行的所有操作都是类型安全的,比如 。
int a = 5;
int b = a + 2; //OK
bool test = true;
// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;
编译器将类型信息作为元数据(metadata)嵌入到程序中。所以C#的类型系统,在IL层面叫"metadata" 。
CLR在运行时使用metadata来进一步保证类型安全,避免出现类型转换错误.
C#中所有的类型元素,在 CLR 上都有对应的类与之承载 使用windbg来一探究竟 。
AppDomain 在.net core中 出于跨平台需要,相对.net framework只剩下了两个,System Domain与Domain 1 。
Assembly 。
Module 。
Class 我们可以用二级命令来显示,模块中定义的类型(class),以及模块引用的类型.
Method 同时也显示了父类objcet的方法 。
Field dump方法表中的EEClass,得出类的字段 。
网络上有一种说法,C#中的这个#,实际上是++++。相当于C++的超集C++++。那么为什么这么说呢?从CLR的角度出发,CLR中所有类型,在C++都有一一对应.
https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/appdomain.cpp https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/class.cpp https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/field.cpp 。
在面试八股文中,有一个经常出现的问题:值类型与引用类型的区别? 而这个问题,一个高频次的答案就是:Class是引用类型,struct是值类型。值类型分配在栈用,引用类型分配在堆中。 这个说法说并不准确,为什么呢?因为它是从实现的角度对两个概念进行描述,相当于先射箭再画靶。而不是基于两种类型内在的真正差别 。
值类型:这种类型的实例直接包含其所有数据。值类型的值是自包含,自解释的 引用类型:这种类型的实例包含对其数据的引用。引用类型所描述的值是指示其他值的位置 。
值类型 | 引用类型 | |
---|---|---|
生存期 | 包含其所有数据,自包含,自解释。值类型包含的数据生存期与实例本身一样长 | 描述了其他值的位置,其他值的生存期并不取决于引用类型值本身 |
共享性 | 不可共享,如果我们想在其他地方使用它。默认使用“传值”语义,按字节复制一份,原始值不受影响。 | 可被共享,如果我们想在如果我们想在其他地方使用它。默认使用”传引用“语义。因此在传递之后,会多出一个指向同一个位置的引用类型实例。 |
相等性 | 仅当它们的值的二进制序列一样时才认为相同 | 当它们所指示的位置一样就认为相同 |
从定义中可以看出,没有地方说明,谁存储在栈中,谁存储在堆中。 实际上,值类型分配在栈上,引用类型分配在堆上。只是微软在设计CLI标准时根据实际情况所作出的一个设计决策。 由于它确实是一个非常好的决策,因此微软在实现不同的CLI时,沿用了这个决策。但请记住,这并不是银弹,不同的硬件平台有不同的设计 事实上类型的存储实现,主要通过JIT编译的设计来体现。JIT编译器在x86/x64的硬件平台上,由于有栈,堆,寄存器存在。JIT可以随意使用,只要它愿意,它可以把值类型分配在堆中,分配在寄存器中都是可以的。只要不违反类型的定义,又有何不可呢?
如果仅从定义出发,将所有值类型保存在堆上是完全可行的,只是使用栈或者CPU寄存器实在太香了而已。 因此主要考虑生存期与共享这两个因素,放在栈空间中更加合适.
internal class Program
{
static void Main(string[] args)
{
var myStruct = new MyStruct();
myStruct.x = 100;
myStruct.y = 102;
}
}
struct MyStruct
{
public int x;
public int y;
}
可以看到,值类型的内存布局没有任何额外开销,直接在线程栈中完成分配,并随线程栈释放。完美契合符合生存期/共享性的概念.
由于引用类型可以共享数据,因此它们的生存期并不确定。所以考虑引用类型存储到哪里要比值类型要简单得多。 通常来说,引用类型存储在栈上不符合定义,此时哪里能存储引用类型就很明显了.
internal class Program
{
static void Main(string[] args)
{
var myClass = new MyClass();
myClass.x = 100;
myClass.y = 102;
Debugger.Break();
}
}
class MyClass
{
public int x;
public int y;
}
与值类型的汇编相比较,可以很明显的看出差异.
使用windbg也可以验证,MyClass对象分配在托管堆中,并结构为objHeader + methodtable + 对象本身 。
值类型除了可以存储在“栈”上,也可以在“寄存器”,“托管堆”中 。
internal class ExampleStruct
{
public int Main(int i)
{
var myStruct = new MyStruct();
myStruct.vaule1 = i;
return Helper(myStruct);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]//告诉编译器,让方法尽量内联
private int Helper(MyStruct arg)
{
return arg.vaule1;
}
}
struct MyStruct
{
public int vaule1;
public int vaule2;
public int vaule3;
public int vaule4;
}
在64位release模式下,因为Helper方法被内联。所以并不会调用Helper方法,因此省略了传递Struct数据给Help方法,JIT编译器将整个操作优化成只需要对CPU寄存器进行操作.
可能是环境问题,我的release版本始终没有内联,导致无法在windbg中复现。有兴趣的小伙伴可以参考<.NET内存管理宝典> 168页内容.
internal class ExampleStruct2
{
public void Main()
{
var myStruct = new MyStruct2
{
vaule1 = 100,
vaule2 = 102
};
//因为委托是引用类型,引用类型内部引用一个值类型,也会把值类型提升至托管堆
var f = () =>
{
Console.WriteLine(t.vaule1);
Console.WriteLine(t.vaule2);
};
f();
Debugger.Break();
}
}
struct MyStruct2
{
public int vaule1;
public int vaule2;
public int vaule3;
public int vaule4;
}
可以看到,当值类型被引用类型所持有时,同样会分配在堆空间中.
在.NET9 之前,这句话是成立的,因为栈空间不符合引用类型的定义.
但在.NET9之后,这个概念发生了改变。我们先来思考一段代码 。
public class ExampleClass
{
public void Main()
{
var myClass = new MyClass()
{
vaule1 = 100,
vaule2 = 102
};
Console.WriteLine(myClass.vaule1);
Console.WriteLine(myClass.vaule2);
}
}
public class MyClass
{
public int vaule1;
public int vaule2;
public int vaule3;
public int vaule4;
}
虽然MyClass是一个引用类型,但在方法中,myClass实际上随着Main方法的执行完成而不再使用。因此把myClass放在堆空间中,会造成GC的负担以及内存浪费。能不能让JIT更智能一点?如果引用类型的生命周期与线程栈类似,是否可以放在栈空间中呢?
答案是肯定的,而且在JAVA中已经运用很多年了,这就是大名鼎鼎的逃逸分析(escape analysis) 。
.NET9 刚刚启用此特性,因此范围比较有限。不过在未来,相信会进一步扩大范围,实现更高性能的内存分配.
.NET 9内存分配: .NET 8内存分配
再引用一个官方的例子 https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-9/#object-stack-allocation 。
// dotnet run -c Release -f net8.0 --filter "*" --runtimes net8.0 net9.0
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);
[MemoryDiagnoser(false)]
[DisassemblyDiagnoser]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Tests
{
[Benchmark]
public int GetValue() => new MyObj(42).Value;
private class MyObj
{
public MyObj(int value) => Value = value;
public int Value { get; }
}
}
在.net 8 中的汇编如下
; Tests.GetValue()
push rax
mov rdi,offset MT_Tests+MyObj
call CORINFO_HELP_NEWSFAST
mov dword ptr [rax+8],2A
mov eax,[rax+8]
add rsp,8
ret
; Total bytes of code 31
在.net 8中内存分配如下 。
在.net 9中的汇编如下 。
; Tests.GetValue()
mov eax,2A
ret
; Total bytes of code 6
在.net 9中内存分配如下 。
可以看到,.NET9通过方法内联,直接将new MyObj(42).Value提升为 return 42 . 不会在堆中创建MyObj对象,而是直接在栈空间赋值.
以上例子可以看到,值类型与引用类型其内核就是生命周期是否可控,是否被其他线程共享?无论什么类型,只要它生存期大于线程栈或者被其他线程所共享访问。那么它就会被分配在堆上。反之,则分配在栈或者寄存器上。 更简单来说, JIT如果不知道对象什么时候被释放,那么它一定会分配到堆空间中。如果知道什么时候被释放,那么它会尽量分配到栈空间中,甚至寄存器中.
最后此篇关于.NETCore类型系统(TypesSystem)底层原理浅谈的文章就讲到这里了,如果你想了解更多关于.NETCore类型系统(TypesSystem)底层原理浅谈的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
服务架构进化论 原始分布式时代 一直以来,我可能和大多数的人认知一样,认为我们的服务架构的源头是单体架构,其实不然,早在单体系
序列化和反序列化相信大家都经常听到,也都会用, 然而有些人可能不知道:.net为什么要有这个东西以及.net frameword如何为我们实现这样的机制, 在这里我也是简单谈谈我对序列化和反序列化的
内容,是网站的核心所在。要打造一个受用户和搜索引擎关注的网站,就必须从网站本身的内容抓起。在时下这个网络信息高速发展的时代,许多低质量的信息也在不断地充斥着整个网络,而搜索引擎对一些高质量的内容
从第一台计算机问世到现在计算机硬件技术已经有了很大的发展。不管是现在个人使用的PC还是公司使用的服务器。双核,四核,八核的CPU已经非常常见。这样我们可以将我们程序分摊到多个计算机CPU中去计算,在
基本概念: 浅拷贝:指对象的字段被拷贝,而字段引用的对象不会被拷贝,拷贝对象和原对象仅仅是引用名称有所不同,但是它们共用一份实体。对任何一个对象的改变,都会影响到另外一个对象。大部分的引用类型,实
.NET将原来独立的API和SDK合并到一个框架中,这对于程序开发人员非常有利。它将CryptoAPI改编进.NET的System.Security.Cryptography名字空间,使密码服务摆脱
文件与文件流的区别(自己的话): 在软件开发过程中,我们常常把文件的 “读写操作” ,与 “创造、移动、复制、删除操作” 区分开来
1. 前言 单元测试一直都是"好处大家都知道很多,但是因为种种原因没有实施起来"的一个老大难问题。具体是否应该落地单元测试,以及落地的程度, 每个项目都有自己的情况。 本篇为
事件处理 1、事件源:任何一个HTML元素(节点),body、div、button 2、事件:你的操作 &
1、什么是反射? 反射 (Reflection) 是 Java 的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。 Oracle 官方对
1、源码展示 ? 1
Java 通过JDBC获得连接以后,得到一个Connection 对象,可以从这个对象获得有关数据库管理系统的各种信息,包括数据库中的各个表,表中的各个列,数据类型,触发器,存储过程等各方面的信息。
可能大家谈到反射面部肌肉都开始抽搐了吧!因为在托管语言里面,最臭名昭著的就是反射!它的性能实在是太低了,甚至在很多时候让我们无法忍受。不过不用那么纠结了,老陈今天就来分享一下如何来优化反射!&nbs
1. 前言 最近一段时间一直在研究windows 驱动开发,简单聊聊。 对比 linux,windows 驱动无论是市面上的书籍,视频还是社区,博文以及号主,写的人很少,导
问题:ifndef/define/endif”主要目的是防止头文件的重复包含和编译 ========================================================
不知不觉.Net Core已经推出到3.1了,大多数以.Net为技术栈的公司也开始逐步的切换到了Core,从业也快3年多了,一直坚持着.不管环境
以前面试的时候经常会碰到这样的问题.,叫你写一下ArrayList.LinkedList.Vector三者之间的区别与联系:原先一直搞不明白,不知道这三者之间到底有什么区别?哎,惭愧,基础太差啊,木
目录 @RequestParam(required = true)的误区 先说结论 参数总结 @RequestParam(r
目录 FTP、FTPS 与 SFTP 简介 FTP FTPS SFTP FTP 软件的主动模式和被动模式的区别
1、Visitor Pattern 访问者模式是一种行为模式,允许任意的分离的访问者能够在管理者控制下访问所管理的元素。访问者不能改变对象的定义(但这并不是强制性的,你可以约定为允许改变)。对管
我是一名优秀的程序员,十分优秀!