一、什么是CIL
1.CIL(Common Intermidate Language)是指.Net的公共中间语言,它是一种编程语言。
.Net框架的各种语言在编译时都会编译成同一种中间语言(CIL),之后程序运行的时候CIL会被JIT(Just In Time)转换为二进制语言(机器码)。这使得.Net的各种语言可以互相通信,比如我们可以有一个从F#语言派生的C#语言类。并且通过不同平台的JIT使得一套代码可以在不同的平台上运行。这类似于编程中的依赖倒置思想。
过程:C# 代码 ---> C# 编译器 (csc.exe) ---> CIL (程序集 DLL/EXE) ---> JIT 编译 ---> 机器码
使用某些工具可以反编译exe或dll得到公共语言,如ildasm.
二、什么是CLR
公共语言运行时(CLR)是管理.NET应用程序执行时的运行时环境。CLR就像一个特殊的“.NET操作系统”,管理着所有原本必须由程序员处理的操作(如内存管理)。CLR介于实际的操作系统(例如Windows)和应用程序之间。
CLR为.Net应用提供县城管理,内存管理,异常处理等。
三、c#和.Net的区别
C# 是一种编程语言,而 .NET 是一个框架,支持用 C# 以及其他与 .NET 兼容的语言编写的应用程序。
四、值类型和引用类型
值类型继承自System.valueType,引用类型继承自System.Object
值类型存储在栈上,引用类型存储在堆上。值类型在垃圾收集器只清理引用类型
在赋值时,值类型的变量会被复制,而引用类型的变量只复制引用。
要注意的是:string和Action都是引用类型,但是他们也是不可变类型,他们在更改值的时候是把改变的后的值传递回原变量。之后传递值的时候使用的是当时的值。所以他们虽然是引用类型但是使用时的表现和值类型一样。
五、装箱和拆箱
装箱就是把值类型包装为Object,拆箱就是把Object转换为值类型
六、三种主要的错误类型
1.编译错误,就是语法错误
2.运行时错误,运行时i程序抛出的错误
3.逻辑错误,没有报错但是结果 不正确
七、c#中如何处理异常
异常通过try-catch-finally块进行处理。try块包含可能抛出异常的代码,catch块定义如果抛出特定类型的异常时应执行的操作,而finally块则无论是否抛出异常都会执行。
其中最重要的是finally,它无论是否有异常都会执行。
八、c#中的访问修饰符
C# 中有六种访问修饰符。其顺序从最不严格到最严格如下:
Public(公共) - 类型或成员可以被任何程序集中的任何其他类型使用。
Internal(内部) - 类型或成员只能由其定义所在的同一程序集中的任何其他类型使用。它不能在该程序集之外使用。
Protected(受保护) - 类型或成员只能在同一类中,或继承自该类的类中使用(无论在哪个程序集中)。
Protected internal(受保护内部) - 在同一程序集内,它的工作方式类似于 internal - 因此可以被所有其他类型使用。在该程序集之外,它的工作方式类似于 protected - 只能由继承自该类的类型使用。
Private protected(私有受保护) - 在同一程序集内,它的工作方式类似于 protected - 类型或成员只能由声明类或继承自它的类中使用。在该程序集之外,即使是由继承自该类的类,也无法访问。
Private(私有) - 类型或成员只能由同一类中的代码使用。
九、默认访问修饰符
默认访问修饰符是当程序员未显示声明访问修饰符时,应用于类型或成员的修饰符。他们是给定上下文中有效的限制性最强的访问修饰符。
比如命名空间中的类默认是Interal,因为命名空间中的类的访问修饰符不能是private,protected,不然别的类就无法访问到他
一个类中的方法,成员等默认修饰符是private
十、Sealed访问修饰符
seled用于防止类被继承或防止重写的方法方法被再次重写
十一、params关键字的作用
params允许传递任意数量相同类型的参数
十二、类和结构体的区别
1.类主要是功能的载体,结构体主要是数据的载体。
2.类表达自己是什么能干什么,结构体表达自己持有什么。
3.结构体是值类型,存储在栈中,类是引用类型存储在堆中。
4.结构体密封无法被继承,类可以被继承。
5.结构体没有析构函数,类有析构函数。
6.结构体的成员变量无法定义默认值,使用default来判断是否更改过,而类可以定义变量默认值,使用null判断是否存在。
7.结构体不能有显式的无参构造函数,并且构造函数中必须为所有变量赋值。类则相反。
十三、分部类
分部类是指一个类可以被分布在多个文件中,这有助于保持文件结构的清晰
十四、new关键字的作用
1.new 运算符用于创建新的类型实例
2.new运算符用显示隐藏派生类中基类的成员
方法重写和隐藏:
重写是指用override重写基类方法,之后其他地方调用父类的对应方法时实际执行的是重写后的方法。
而隐藏是指用new表明这个方法只是同名而不是覆盖,之后其他地方调用父类的对应方法时实际执行的还是父类方法。只用显示调用当前类才会执行用new修饰的方法。
3.new约束用于指定泛型类中的类型参数必须具有无参构造函数
十五、Static修饰符
1.用于定义静态类以及静态成员。静态成员属于类型本身而不是特定对象
2.使用using static指令,用于引用静态成员而无需每次都显示指定其名称。
如
使用using指令,代码则可去除Math。
十六、什么是静态类
静态类是一种无法实例化且只能包含静态方法的类,静态类一半用做工具类,比如计算类。
静态构造方法:静态类和非静态类都可以包含静态构造方函数,静态构造函数允许我们初始化静态字段,他在静态类首次使用时被调用。比如:
十七、三元条件运算符
最简单的一个。。
变量=条件?a:b;条件为真返回a,否则返回b。
十八、空值合并运算符和空条件运算符
这些运算符以内需我们在值为空时执行某些操作,而在值不为空时执行其他操作。
空值合并运算符(??):比如name??"stranger"表示name不为空时返回name,否则返回"stranger";
空值合并赋值运算符(??=):比如 numbers??= new List<int>().Add(number);表示检查numbers是否为空,是的话给他赋值一个新值。
空值条件运算符(?.):表示仅在对象不为空时才访问其成员。比如 numbers?.Clear();表示只有在numbers不为空时才调用其Clear方法。
二十、什么是封装
封装是将数据与操作数据的方法捆绑在一起,封装一词来源于胶囊,因为我们可以将其视为数据和方法被封装在一个胶囊中。
封装并不是数据隐藏,我们可以在没有数据隐藏的情况下完成封装,封装主要是为了在逻辑上将数据与操作数据的方案捆绑在一起。
但是使用封装时最好也使用数据隐藏,这样可以避免其他程序员使用不正确的方式调用数据和方法。
十九、是什么Linq
Linq让我们能够以一种统一的方式查询数据。
二十、扩展方法
扩展方法是指可以在类外部编写一个类的扩展方法,通过在方法参数中使用(this 类型 参数)即可定义扩展方法。比如
然后便可通过以下方式直接调用
二十一、Ienumrable
Ienumrable是一个接口,支持使用foreach循环遍历集合。或者说foreach只能遍历Ienumrable类型。
Ienumrable中只返回一个枚举器。枚举器可以看作是一个指针,要使用Ienumrable就是要定义枚举器的行为。
枚举器:
如果我们要自己实现一个Ienumrable我们需要再实现一个Ienumratorl。
先创建Ienumrable
然后实现Ienumrator
最后,我们把GetEnumrator替换为return new WorldEnumrator()即可。
此外,unity的协程就是用迭代器实现的。而unity的协程最有用的是他的等待功能,比如等待一秒,等待xx完成之类的。
它的实现原理是每帧在执行的时候判断是否需要等待,也就是是否有Yield语句,需要等待则跳过后面代码(不执行MoveNext),如果yield结束了比如等待时间结束才执行Movenext();
知道上面原理我们可以自己写出迭代器和协程。
二十二、相等运算符==和Equals的区别是什么
引用类型,==比较引用,也就是比较地址。Equals如果未被重写那么它和==是一样的对比方法。==符号也可以被重载。对于string类型,string的Equals方法被重写比较值,所以使用Equals判断字符串是判断值是否相同。
值类型,值类型没有引用,所以默认情况下值类型不支持==符号来进行判断,除非我们重载它。而Equals方法则是逐个比较结构体的每个值是否相等,完全相等时返回True,否则返回false。
二十三、深拷贝和浅拷贝的区别
浅拷贝回复制值类型成员,但引用类型仅复制引用。
深拷会回复制值类型成员,并为引用类型创建全新对象。
简单来说,区别在于对引用类型的拷贝方式,浅拷贝后的副本的引用还是之前的引用,而深拷贝后的副本的引用是之前引用的拷贝父副本。
二十四、什么是垃圾回收器
垃圾回收器是管理引用程序内存的机制,如果一个对象不再使用垃圾回收期会释放内存,它还负责内存碎片整理。
对于值类型,当控制流达到这些对象所在作用域的末尾时,这些对象的内存会自动释放,不过这不是垃圾回收器完成,而是由栈的自动释放机制来处理。
垃圾回收器管理的是堆上的对象,即引用类型。垃圾管理器检查对象是否被引用,如果对象不再被应用就会被自动回收,但是我们不知道具体回收时间。如果希望理解释放内存,可以调用GC.Collect()方法。
垃圾回收期在独立的线程上运行,在回收故工程中,所有其他线程都会停止,这可能导致性能问题。因此我们可以使用对象池来优化。
内存泄漏!内存泄露是指一些对象不再使用但他的内存也没有被清理的情况,随着程序运行时间越久,没被清理的对象越多,占用的内存就会越多。常见的内存泄漏原因有对象在关闭时没有清理干净事件,事件引用对象导致垃圾管理器不清除对象。
二十五、可空类型
可空类型是指任何可以被赋值为null的类型。
Nullable<T>结构体是值类型的包装器,允许将null复制给该类型的变量。
值类型不能为空类型,引用类型可以为空类型,所以Nullable<T>只用于值类型。
c#中我们使用int?flaot?等形式表示可空类型。
Nullable<T>本身是值类型,但是它可以被赋值为null,其实编译器进行了特殊处理:
二十七、属性
属性是一种成员,它提供读取和写入的属性,它的关键作用在于封装性,和读取值设置值时可以进行额外处理。
二十八、泛型
泛型可以理解为类型占位符,开发者可以使用泛型编写类型无关的代码。
泛型是为了强制类型转换和代码重用的问题。
泛型使用where语句进行类型约束。
集合类,接口,委托是泛型的典型用力。
二十九、ref于out的区别
ref通过引用传递值类型给方法,使用ref是为了让值类型能够像引用类型一样被实时改变值。ref在使用前必须初始化。ref对引用类型也有用,比如交换引用。
out方法则是为了从方法中返回额外变量,返回值一般用来标识函数是否执行成功,真正的额外变量通过out得到。out是在函数内部计算的,所以可以不用初始化,不过out必须在内部进行赋值。
二十九、const和readonl修饰符。
const和readonly都用于定义不可变值。
const必须在声明时初始化值,只支持基本类型的值,const类型是隐式静态的。
readonly可以在声明或者初始化设置值,支持任何类型,可以是实例字段也可以是静态字段。
三十、接口和抽象类的区别
接口定义任何实现它的类需要提供的操作,抽象类是派生类的通用模板,它可以提供一些其他类。他们都能被继承,类只能继承一个类,单类可以继承多个接口。接口继承必须显示实现。
抽象类表示是什么,接口表示能做什么。
抽象类便于代码复用,接口每个继承他的类都必须显示实现接口定义的内容。
三十二、多态
继承自一个概念的多个实例有多种具体实现可以称之为多态 。
三十三、虚方法和抽象方法的区别
虚方法自己可以编写方法体,子类可以选择性地重写。抽象方法不含方法体,子类必须重写。
三十四、方法的重载
相同函数名不同参数则称为重载
三十五、方法重写和方法隐藏的区别
参考十四条
三十六、c#是否支持多重继承?
不支持,c#支持多接口实现,但不支持多重继承,因为这会导致钻石问题/菱形问题。
什么是钻石问题,钻石问题指的是:一个类想要另外两个类的功能则需要继承两个类才能完成。这就是继承的问题,要解决这个问题应该改用接口。或者说使用组合模式。
三十七、什么是DRY原则
DRY原则是指:不要重复你自己,简单来说,不要用重复代码,因为这样容易出错,并且在需求变更时难以维护。
三十八、什么是魔法数字反模式
魔法数值是指卸载程序中的硬编码变量,这导致程序难以读懂和维护,应该创建一个COnst类来管理
三十九、为什么不要使用goto关键字
因为增加了代码复杂性,更加难读懂
四十、什么是面条代码
面条代码是一个贬义词,用来描述混乱的代码
四十一、什么是单例设计模式
简单,声明一个自己的静态变量并保持全局为以,之后全局便可以通过访问这个静态变量来快速获得这个实例。
四十二、什么是建造者设计模式
建造者模式使用链式方法进行链式调用来进行多部配置以构建对象,比如DOTween。使用建造者模式在构造复杂对象时我们不用一次性传入所有参数来使用构造函数构造对象而是可以选择性地构造对象。同时,链式方法名明确提示传入参数的作用增加了可读性。
但建造者模式也有缺点,就是代码复杂度增加,当对象变量更改时建造者类也必须跟着更改。
四十三、什么是适配器设计模式
它允许将一个类的接口转换为客户所需的接口。
简单啊来说就是添加中间层,我们应该依赖于中间层而不是具体实现。就像耳机转接线一样,手机接入typeC接口连接耳机,而耳机是3.5mm耳机插头。
四十四、什么是桥接设计模式
桥接模式是组合由于继承的实现。他将继承拆为多个结构组合在一起。比如汽车分为电动的和烧油的,然后又分为手动挡自动挡。
如果使用传统继承方式会导致继承爆炸.
因此我们应该用结构组合的模式来表达:
桥接模式和策略模式的区别是一个注重结构,一个注重行为。
四十五、什么是工厂方法设计模式
这个比较简单,就是我只告诉工厂我要什么,而不在乎具体实现细节。工厂可以作为借口,客户直接告诉接口我要什么,具体返回什么取决于具体的接口实例。
静态工厂方法和工厂方法的区别:
静态工厂方法其实是对构造方法的封装,和工厂方法几乎没有关系,它的目的是增强构造方法的可读性。
比如以下代码无法直接看出参数含义:
静态工厂方法就是使用静态方法来替换构造函数使得能够更加清晰地看出方法的目的。
四十六、Solid原则中的s原则(Single)
s代表单一职责y原则,一个类只负责一项任务
四十七、Solid的O原则(Open)
O代表开闭原则,表示对扩展开发,对修改关闭。
四十八、Solid的L原则(LSP)
L代表里氏替换原则,表示我们应该能在不知情的情况下用派生类型代替基类型
四十九、Solid的I原则(Interface)
I代表接口隔离原则,表示接口的客户端不应该被迫依赖他们不使用的方法。这个意思是对象的接口中不应该有自己不需要用到的方法,比如
五十、SOlid的D原则(Dependency inversion principle)
依赖倒置原则,指的是高层模块不应该依赖于底层模块,两者都应该依赖于抽象。简单来说,我们应该持有接口而不是具体实现。此外这里的依赖对于两者来说是不同的,实际上是高层定义依赖,而底层实现依赖。比如: