C#.NET必备50道面试题( 初级)系列: C#.NET必备50道面试题( 初级)1~10道 C#.NET必备50道面试题( 初级)11~20道 C#.NET必备50道面试题( 初级)21~30道 C#.NET必备50道面试题( 初级)31~40道 待更新
扩展方法允许我们在不实际修改类或不添加继承自它的新类的情况下,向类添加新功能。
我们在特殊的静态类中定义它们,因此不需要重新编译原始类。
即使它们在单独的类中定义,我们仍然可以像使用扩展类的常规成员方法一样使用它们。
要用this关键字
扩展方法允许我们在不修改类的情况下向其添加功能,当使用外部类且无法访问其源代码时。这特别有用。
IEnumerable是一个接口,它使我们能够使用foreach循环遍历集合。
在最常见的情况下,=运算符按引用比较对象,而Equals方法被重写以按内容比较它们。
引用类型:一般规则是,=运算符比较引用,而如果我们想定义自定义的比较对象方式,则应该重写Equals方法。但是,如果没有重写,它将像=运算符一样按引用比较对象
请记住,=运算符也可以被重载,因此我们不能总是假设对于任何类型它都会按引用比较 对象。
总结:Equals如果被重写了,就按重写方法比较,如果没有重写就按=运算符比较。
- 创建浅拷贝时,值类型成员会被复制,但对于引用类型,只会复制引用。
- 对于深拷贝,不仅值类型会被复制,对于引用类型也会创建全新的对象。引用不会被复制, 而是会创建指向新对象的新引用。(随着新对象的创建,也会有新的引用。这意味着新person对象将拥有自己的引用,指向内存中的不同pet。)
还有一件事会引起很多混淆。
人们,包括面试官,经常问,这是深拷贝还是浅拷贝?
答案是两者都不是。这只是一个赋值操作。person2变量将简单地指向与person变量相同的内存对象。没有成员被复制,即使是值类型的成员也不会被复制。
这意味着如果person2的height被更改,person的height也会被更改。
如你所见,两个对象的height都被更改了,因为实际上它们是一个对象,只是被两个引用指向。
记住,浅拷贝和深拷贝都会创建一个新的person对象。
- 何时使用浅拷贝和深拷贝?
一般来说,如果你需要创建一个真正独立的对象副本,可以随意修改而不影响原始对象,你应该创建深拷贝。否则,你可以考虑使用浅拷贝。
- MemberwiseClone方法的作用是什么?
它创建对象的浅拷贝。
- 使用浅拷贝的风险是什么?
风险是原始对象和副本可能持有对同一对象的引用。
垃圾回收器是一种管理应用程序所用内存的机制。如果一个对象不再被使用,垃圾回收器将释放它所占用的内存。垃圾回收器还负责对应用程序的内存进行碎片整理。
垃圾回收器管理存在于堆上的对象,即引用类型,例如字符串、列表、数组和所有类。垃圾回收器确定是否还有任何现有引用指向该对象。如果没有,它决定该对象不再被使用并释放该对象所使用的内存。
这里重要的一点是,垃圾回收器不会立即清理内存。我们并不知道这将何时发生。
因此,如果由于某些原因你必须立即清理某些资源,请不要将其留给垃圾回收器。实现IDisposable接口并使用Dispose方法。
有一种方法使用GC.Collect方法强制垃圾回收器收集内存,但如果你想立即释放内存,这不应该是默认解决方案。
那么,垃圾回收器何时决定清理内存?
- 首先,当系统物理内存不足时,CLR将由操作系统通知这一点。
- 其次,当堆上已分配对象使用的内存超过给定阀值时,此阀值在进程运行过程中不断调整。
- 第三,当调用GC.基本上,除非有必要,否则垃圾回收器不会开始工作。
基本上,除非有必要,否则垃圾回收器不会开始工作。
需要理解的重要一点是,垃圾回收器在自己的独立线程上运行,当这种情况发生时,所有其他线程都会被停止,直到垃圾回收器完成其工作。
这显然可能导致性能问题。
例如,考虑用C#创建的视频游戏。
如果每秒创建大量短生命周期的对象,垃圾回收器将有很多工作要做,并且它会经常将游戏冻结一小段时间以完成其工作。
解决方法:
例如,通过使用对象池,这些对象被重用而不是频繁创建和销毁短生命周期对象。
需要记住的重要一点是,垃圾回收器在收集期间会冻结所有其他线程。
内存泄漏:内存泄漏是指即使用它的对象不再使用,某些内存也没有被清理的情况。
重要的是要理解,垃圾回收器并不能100%保护我们免受内存泄漏的影响。
.NET中内存泄漏的最常见来源之一与事件处理程序有关。
例如通过附加将在点击按钮时调用的方法来处理点击按钮
当主窗口的按钮被点击时(由此方法处理),将创建一个子窗口,这发生在这里。
我们希望在主窗口中发生事件时,在子窗口中发生某些事情。
这是一件非常重要的事情。
通过订阅事件处理程序,从主窗口到子窗口创建了一个引用。这是必要的,因为如果主窗口中触发了事件,必须通知子窗口,因此主窗口必须对子窗口有引用。
现在,假设用户关闭了子窗口。似乎ChildWindow对象将由垃圾回收器清理。但它不会,因为从主窗口到子窗口仍然存在隐藏引用。垃圾回收器看到此引用并决定ChildWindow对象仍在使用中。现在,想象如果用户不断打开和关闭子窗口会发生什么。将创建新对象,但它们的内存永远不会被释放。最终,我们将耗尽内存。
在这种情况下,解决方案是在窗体关闭时取消订阅事件处理程序。
这只是一个简化的示例,说明我们如何可能遇到垃圾回收器无法保护我们免受的内存泄漏。
你应该始终考虑内存问题,并记住垃圾回收器只是一个工具,即使相当聪明。
什么是内存碎片和碎片整理?
当内存片段被分配和释放时,内存变得碎片化,这意味着空闲内存是小块的而不是一个长块。这称为内存碎片。如果需要将大对象放入内存,可能无法找到足够长的空闲内存块。
将已分配内存片段移动以使它们粘在一起以创建大块空闲内存的过程称为碎片整理。
可空类型是可以被赋值为null的任何类型。
Nullable结构体是值类型的包装器,允许将null赋值给此类型的变量。
例如,我们不能将null赋值给整数,但可以将其赋值给类型为nullable的变量
在C#中,所有引用类型默认是可空的,而值类型默认是非可空的。null表示缺少值或缺失值。
请注意,C#8中还引入了可空引用类型和非可空引用类型的概念。
属性是提供读取和写入私有字段值机制的成员。
属性是一种包装字段的机制,它暴露了用于获取和设置此字段值的方法。
到目前为止,你可能会问,如果我只需使用公共字段就能实现完全相同的功能,那么使用属性的意义何在?
答案是封装和数据隐藏。
使用字段时,我们暴露了Person类的内部实现细节。
使用属性而不是字段使我们能够提供ge和set访问器的自定义实现它将类的实现细节隐藏起来,使我们能够自由更改而不会引起任何问题。
但这还不是属性功能的终点。实际上,属性不需要包装特定字段。它可以简单地计算某个值。
使用属性的另一个优势是我们可以为读取和写入定义不同的访问修饰符。
下一个优势是,我们可以在属性的ge和set方法中定义任何逻辑,例如验证。
属性是提供读取、写入或计算私有字段值机制的成员。
使用属性而非字段的优势是:
属性和字段有什么区别?
属性提供了一定程度的抽象。它们作为用于访问类特定数据的特殊方法工作。它们不需要暴露字段,它们可以暴露常量字面量或某些计算值。此外,属性可以被重写。
泛型允许我们创建通过类型参数化的类和方法。我们不需要为每种类型创建单独的类和方法。例如可以存储任何类型的元素的List。我们可以创建一个通过类型参数化的单一类。
必须是引用类型
必须是值类型
这意味着T类型必须提供一个无参数构造函数。
这意味着只有从BaseClass派生的类型将被接受为T参数。
这意味着只有实现IFlying接口的类型将被接受。
我们使用where关键字声明约束。
什么是类型约束?
- 类型约束允许将泛型类型的使用限制为仅满足特定标准的类型。
使用泛型的好处是什么?
它们允许我们通过创建一个可以与任何类型一起工作的单一类来减少代码重复,
减少代码重复使代码更容易维护且不易出错。
const它用于声明一个值不应该改变的变量。确切的值必须在编译时已知。
const排常适合声明像π值、一周中的天数、某个容器的最大大小或字节中的位数这样的变量。
根据定义,const字段是常量,因此当然不能更改它们的值。
- 一个限制是我们必须在声明时赋值给const。
- 另一个限制是const只能是数字、布尔值、字符串或null引用。你不能将一个在运行时构造的对象赋值给const。因为此对象的构造发生在运行时,而const的值必须在编译时确定。
最后,const不能声明为static,因为它们是隐式静态的。
readonly
readonly意味着在首次赋值后,该值不会被重新赋值。
readonly值不需要在编译时已知。它们可以是某些计算或用户输入的结果。我们在任何希望值在首次设置后永不更改的地方使用readonly。
实际上,readonly字段只能在两个地方赋值。
首先,在它们的声明处,就发生在这里,其次,在构造函数中。
何时使用const,何时使用readonly?
- 当值在编译器时间已知,甚至在程序启动之前,我会使用const。
- 当值在运行时已知,但我不希望它在赋值后更改时,我会使用readonly。
简而言之,ref关键字通过引用将值类型传递给方法,这意味着在此方法内部对该值的任何修改在方法外部都是可见的。它允许修改传递给方法的值类型。
ref关键字的一个重要限制是;通过ref传递的值必须在调用方法之前初始化。
out关键字的限制是值必须在方法内部赋值。(用结构体可能更好)
参考链接:https://www.bilibili.com/video/BV1FkYSzCE2F?spm_id_from



