C# 中常见的易混淆概念
虚方法和抽象方法的区别
- 抽象方法在抽象类中定义,并且没有方法体,要求派生的非抽象子类必须重写抽象方法。
- 虚方法在抽象类和非抽象类中都可以定义,有默认方法体,派生子类可以使用
override
关键字覆盖父类虚方法,但也可以不重写父类的虚方法。 - Difference between virtual and abstract methods [duplicate]
抽象类和接口的区别
主要区别
- 抽象类中可以包含抽象方法和其他方法,接口中只能包含抽象方法。
- 一个类只能继承一个抽象类,但是可以实现多个接口。
- 抽象类和接口都不能实例化。
- 抽象类可以有构造函数,而接口没有
抽象类使用情景
当我们需要一个包含一些公共属性的类或具有某些公共属性的方法时,这些属性的实现对于不同的类是不同的,在这种情况下,最好使用抽象类而不是接口。
使用抽象类来限制用户创建父类的对象,因为创建父类对象就无法调用子类方法。因此,开发人员必须通过将父类对象定义为抽象类来限制其意外创建。
接口使用情景
接口可以提供多重继承的解决方案。
参考链接
- When To Use Abstract Class and Interface In Real Projects
值类型和引用类型的区别
- 值类型在栈上分配内存,引用类型在堆上分配内存。如果值类型是引用类型的成员(例如字段或保留在数组中),则它将在堆上分配内存。在方法范围中声明值类型变量(即局部变量)时,它们将在堆栈上分配内存。
- 值类型继承自 System.ValueType,引用类型继承自 System.Object。
- 值类型表示实际数据,引用类型表示对象实例在堆中分配好内存以后,需要在栈中保存一个4字节的堆内存地址,用来定位该对象实例在堆中的位置,便于找到该对象实例。
- 栈由系统自动分配,不需要垃圾回收,速度较快。但程序员是无法控制的;而堆是由 new 分配的内存,一般速度比较慢,而且容易产生内存碎片且需要 GC 回收内存,不过用起来最方便。
尽管在当前的实现中,引用类型总是分配在堆上,但是值类型可以分配在堆栈上,但不一定。仅当值类型是未装箱的,未转义的局部或临时变量且未包含在引用类型中且未在寄存器中分配时,才在堆栈上分配值类型。
- 如果值类型是类的一部分(如您的示例所示),它将最终出现在堆上。
- 如果装箱,它将最终放在堆上。
- 如果在数组中,它将最终在堆上。
- 如果它是一个静态变量,它将最终在堆上。
- 如果被闭包捕获,它将最终在堆上。
- 如果在迭代器或异步块中使用它,它将最终出现在堆上。
- 如果它是由不安全或不受管理的代码创建的,则可以在任何类型的数据结构(不一定是堆栈或堆)中分配它。
参考:https://stackoverflow.com/a/4487320/6289028
浅拷贝和深拷贝
- 浅拷贝是指将对象中的数值类型的字段拷贝到新的对象中,而对象中的引用型字段则指复制它的一个引用到目标对象。如果改变目标对象中引用型字段的值他将反映在原始对象中,也就是说原始对象中对应的字段也会发生变化。
- 深拷贝与浅拷贝不同的是对于引用的处理,深拷贝将会在新对象中创建一个新的和原始对象中对应字段相同(内容相同)的字段,也就是说这个引用和原始对象的引用是不同的,我们在改变新对象中的这个字段的时候是不会影响到原始对象中对应字段的内容。
- 参考链接:https://www.cnblogs.com/personblog/p/11308831.html
强引用和弱引用
- 强引用最终导致的结果就是被引用的对象的被引用次数+1。
- 弱引用不会对被引用对象的被引用次数有任何影响。
- 弱引用允许垃圾收集器收集对象,同时仍允许应用程序访问该对象。如果需要该对象,仍然可以对其进行强引用并防止其被收集。
- 避免对小对象使用弱引用,因为指针可能比小对象还大。
- 参考链接:
- WeakReference Class
- Weak References
修饰符 ref
和 out
的区别
ref
表示变量进入方法前已经被初始化了,而且方法可以读取和修改变量。out
表示变量值未被初始化,将在方法内初始化,且变量在方法return
前必须初始化。- What’s the difference between the ‘ref’ and ‘out’ keywords?
struct 和 class 的区别
- struct 是值类型,而 class 是引用类型
- 使用结构类型来设计以数据为中心的较小类型,这些类型只有很少的行为或没有行为。 例如,
.NET
使用结构类型来表示数字(整数和实数)、布尔值、Unicode
字符以及时间实例。 如果侧重于类型的行为,请考虑定义一个类。类类型具有引用语义。也就是说,类类型的变量包含的是对类型的实例的引用,而不是实例本身。
由于结构类型具有值语义,因此建议定义不可变的结构类型。 - 在
struct
中使用引用类型字段要特别注意。因为struct
是值类型,不可变,引用类型是可变的,将一个struct
类型的变量赋值给另一个变量时,更改引用类型的变量会导致两个变量都变化了,这是很危险的。所以一般不建议在struct
中使用引用类型变量,如果一定要使用的话,建议给变量加上readonly
关键字。具体解释请参考:https://stackoverflow.com/questions/945664/can-structs-contain-fields-of-reference-types 。
用 struct/enum 类型作字典的键
用自定义 struct/enum 类型作为 Dictionary 的 key,即使使用了泛型,也有可能会产生大量 GC。原因是:
- Dictionary 在索引的时候,先会获取哈希值,再去相同的哈希值链表里找对应的元素,这个过程需要调用GetHashCode和Equals方法,如果struct没有重写GetHashCode和实现IEquatable接口,那么这两个过程就会触发装箱操作,所以,准确的说,自定义值类型当然可以用作泛型集合的类型参数,这往往是性能提升的一大优势,但自定义值类型要记得重写GetHashCode和实现IEquatable接口,正确使用值类型才能发挥性能优势。
- 泛型不是单纯为避免装箱拆箱设计的,避免拆箱装箱是.Net类库在泛型设计上的一个额外优势,泛型设计的初始目的或主要目的是解决代码的重复设计,提供通用的代码模板。
- struct 需要实现 IEquatable 的 Equals() 和 GetHashCode() 方法来避免 GC。
Equals
和 ==
的区别
==
比较对象引用;Equals
比较对象内容。Equals
- 如果操作数是引用类型,则它执行对象的
Equals
方法来返回结果。一般地,需要重写类的Equals
方法。 - 如果操作数是值类型,则与
==
运算符不同,它首先检查其类型,如果它们的类型相同,则执行==
运算符,否则返回false
。
- 如果操作数是引用类型,则它执行对象的
==
- 如果操作数是值类型并且它们的值相等,则返回
true
,否则返回false
。 - 如果操作数是引用类型(字符串除外),并且都引用同一实例(同一对象),则返回
true
,否则返回false
。 - 如果操作数是字符串类型且它们的值相等,则返回
true
,否则返回false
。
- 如果操作数是值类型并且它们的值相等,则返回
- C# difference between == and Equals()
IComparable
和 IComparer
两个比较接口的区别和用法
IComparable
和 IComparer
是.NET Framework中用于对象比较和排序的接口。它们之间的区别和用法如下:
IComparable
IComparable
是一个用于比较单个对象不同状态的接口,它定义了一个方法 int CompareTo(object obj)
,接受一个参数 obj
,并返回一个整数,指示该对象与 obj
的相对顺序。该方法返回一个负整数、零或正整数,分别表示当前对象小于、等于或大于 obj
。
如果一个类实现了 IComparable
接口,那么它就可以使用 Array.Sort、List<T>.Sort
等静态方法对其对象进行排序。
IComparer
IComparer
是一个用于比较两个对象之间的差异的接口,它定义了一个方法int Compare(object x, object y)
,接受两个参数 x
和 y
,并返回一个整数,指示它们的相对顺序。该方法返回一个负整数、零或正整数,分别表示 x
小于、等于或大于 y
。
如果一个类没有实现 IComparable
接口,但是需要进行排序,可以通过实现 IComparer
接口来实现排序。在这种情况下,可以使用 Array.Sort
、List<T>.Sort
等静态方法的重载方法之一,并将该类的实例作为第二个参数传递。
使用示例:
1 | // 使用IComparable进行排序 |