C#是单根继承还是多根继承?
C# 是一种单根继承语言,一个类不能直接从两个类派生。
值类型和引用类型的区别?
值类型: 就是一个包含实际数据的对象。即当定义一个值类型的变量时,C#会根据它所声明的类型,以栈方式分配一块大小相适应的存储区域给这个变量,随后对这个变量的读或写操作就直接在这块内存区域进行;
引用类型: 一个引用类型的变量不存储它们所代表的实际数据,而是存储实际数据的引用。引用类型分两步创建:首先在栈上创建一个引用变量,然后在堆上创建对象本身,再把这个内存的句柄(也是内存的首地址)赋给引用变量;
参数传递 ref 与 out 的区别?
1. ref指定的参数在函数调用时必须先初始化,而out不用;
2. out指定的参数在进入函数时会清空自己,因此在返回前必须在函数内部进行赋值操作,而ref不用。
总结:ref可以把值传到方法里,也可以把值传到方法外;out只可以把值传到方法外。
面向对象编程的三个主要特征?
封装(encapsulation),继承(Inheritance),多态(polymorphism)。
重载(overload)与重写(override)的区别?
重载:是方法的名称相同,参数或参数类型不同;重载是面向过程的概念。
重写:是对基类中的虚方法进行重写。重写是面向对象的概念。
virtual 关键字的作用?
1.
基类的所有成员都在派生类中继承,构造函数和析构函数除外。然而,有时基类没有特定成员的最佳实现。也就是说,基类属性声明适用于派生类,但实现并不总是有效的。需要一种机制来用派生类中的自定义实现重写基类实现。
C#支持重写实例方法和属性,但不支持重写字段或任何静态成员。基类必须将允许重写的每个成员标记为 virtual。如果公共成员或受保护成员没有 virtual 修饰,则子类将无法覆盖这些成员。
注:默认情况下,Java中的方法是 virtual 的,如果不想被子类重写,则必须显式密封这些方法。相比之下,C# 默认为非 virtual。
2.
重写方法的签名必须与要重写的基方法的签名匹配。但从C#9.0开始,进行了改进,允许重写方法在返回类型与基方法的返回类型兼容的情况下指定与基方法不同的返回类型。该特性称为协变返回类型。
从下面可以看出,如果后者是前者返回类型的派生类型,则返回类型可能会有所不同。
1 public class Base 2 { 3 public virtual Base Create() => new(); 4 } 5 6 public class Derived : Base 7 { 8 public override Derived Create() => new(); 9 }
new 关键字的作用?
1.
创建对象。
2.
用于向基类成员隐藏继承成员。new 修饰符将派生类的重新声明成员隐藏在基类之外。基类的成员不是调用派生程度最高的成员,而是在继承链中具有new 修饰符的成员之前的成员。
如果派生类重写了基类的方法但没有修饰 override 或者 new,会报警告,但会默认用 new 修饰,从而保持所需的版本安全性。
3.
用于在泛型声明中约束可能用作类型参数的参数的类型。
const 和 readonly 的区别?
1.
const 字段只能在该字段的声明中初始化;而 readonly 字段可以在声明或构造函数中初始化。
2.
const 字段是编译时常数,而 readonly 字段是运行时常数,是运行时确认的,也就是说,一个在编译时就确定了,一个在运行时才确定。
3.
const 默认就是静态的,而 readonly 如果设置成静态的就必须显式声明。
4.
const 对于引用类型的常数,可能的值只能是 string 和 null;而 readonly 可以是任何类型。
访问修饰符有哪些及对应的封装级别(the level of encapsulation)?
有七种访问修饰符可用:public、private、protected、internal、protected internal、private protected 和 file。(Essential C#)
如果没有在类成员上放置访问修饰符,则声明默认为private。
暂时未找到 file 修饰符的可访问性。
抽象类和接口的和区别?
抽象类 |
接口 |
不能直接实例化,只能通过实例化非抽象派生类来实例化。 |
不能直接实例化,只能通过实例化实现类型来实例化。 |
派生类本身必须是抽象的,或者必须实现所有抽象成员。 |
实现类型必须实现所有抽象接口成员。 |
可以添加所有派生类都可以继承的其他非抽象成员,而不会破坏跨版本兼容性(cross-version compatibility)。 |
可以在C#8.0/.NET Core 3.0中添加其他默认接口成员,所有派生类都可以继承这些成员,而不会破坏跨版本兼容性。 |
可以声明方法、属性和字段(以及所有其他成员类型,包括构造函数和终结器)。 |
实例成员仅限于方法和属性,而不是字段、构造函数或终结器。所有静态成员都是可能的,包括静态构造函数、静态事件和静态字段。 |
成员可以是实例或静态的,并且可选地是抽象的,并且可以提供可由派生类使用的非抽象成员的实现。 |
从C#8.0/.NET Core 3.0开始,成员可以是实例、抽象或静态的,并且可以提供可由派生类使用的非抽象成员的实现。 |
成员可以声明为虚拟成员,也可以不声明为虚拟。不应该被覆盖的成员(参见清单8.13)不会被声明为虚拟的。 |
所有(非密封)成员都是虚拟的,无论是否明确指定为虚拟成员;因此,接口无法阻止重写行为。 |
派生类只能从单个基类派生。 |
一个实现类型可以任意地实现许多接口。 |
不支持静态抽象成员。 |
C#11引入了静态抽象成员。 |
总之,假设NET Core 3.0框架是可接受的,那么C#8.0(或更高版本)定义的接口除了声明实例字段之外,还具有抽象类的所有功能。假设实现类型可以覆盖接口的属性以提供存储,然后接口利用该属性,那么接口实际上提供了抽象类提供的超集。此外,除了多重继承之外,接口还支持更封装的受保护访问版本。无论如何,默认接口成员是用于版本控制而非多态性的,因此在将它们用于多态性时要小心。
C#11增加了对静态抽象成员的支持,这些成员将包含在实现接口所需实现的契约中。
接口相关知识(Microsoft Learn):
接口可以包含实例方法、属性、事件、索引器或这四种成员类型的任意组合。 接口可以包含静态构造函数、字段、常量或运算符。 从 C# 11 开始,非字段接口成员可以是 static abstract。 接口不能包含实例字段、实例构造函数或终结器。 接口成员默认是公共的,可以显式指定可访问性修饰符(如 public、protected、internal、private、protected internal 或 private protected)。 private 成员必须有默认实现。
若要实现接口成员,实现类的对应成员必须是公共、非静态,并且具有与接口成员相同的名称和签名。
在类型中声明的静态成员不会覆盖接口中声明的静态成员。
当接口声明方法的默认实现时,实现该接口的任何类都会继承该实现(你需要将类实例强制转换为接口类型,才能访问接口成员上的默认实现)。