在C#中,new
关键字有三种主要用法:
- new 运算符 (New Operator): 用于创建对象和调用构造函数。这是
new
关键字最常见的用法,用于实例化类和调用构造函数。 - new 修饰符 (New Modifier): 作为修饰符时,
new
关键字用于在派生类中隐藏从基类继承的成员,这样新的实现将会隐藏基类中的同名方法或属性。 - new 约束 (New Constraint): 用于在泛型声明中,指定泛型类型参数必须具有公共的无参数构造函数。这种用法确保了在泛型类型参数被实例化时,会调用该类型的默认构造函数。
new关键字在创建对象时做了哪些事情?
- 分配内存空间:
new
关键字会在堆内存中分配足够的内存空间来存储对象的数据。 - 调用构造函数: 构造函数是用于初始化对象的特殊方法。当使用
new
关键字创建对象时,相应类的构造函数会被调用,以便对对象进行初始化。如果类没有定义构造函数,系统会提供一个默认的构造函数。 - 初始化对象数据: 构造函数的代码会执行,用于设置对象的初始状态,包括成员变量的默认值等。
- 返回对象引用:
new
关键字会返回一个指向新创建对象的引用,允许在后续的代码中操作和访问该对象。
这些步骤确保了在创建对象时,对象的内存空间被正确分配,构造函数被调用以初始化对象,然后返回一个可以操作的对象引用。
new关键字在派生类中隐藏从基类继承的成员,这个隐藏怎么理解?
new
关键字被用来显式隐藏从基类继承的成员。当派生类中的成员(方法、属性等)与基类中的成员同名时,通过使用new
关键字,派生类可以隐藏基类的同名成员,即使它们的签名相同。这种隐藏并不影响基类中的成员,但在派生类的范围内,派生类的成员会覆盖基类的同名成员,从而隐藏它。使用new
关键字可以让编译器知道你是故意隐藏了基类的成员。
例如,在基类中有一个名为Print
的方法,在派生类中也定义了一个同名的Print
方法,通过使用new
关键字,派生类的Print
方法将会隐藏基类的Print
方法。但需要注意的是,这种隐藏是静态的,也就是说,在编译时就确定了使用哪个版本的方法,而不是在运行时动态决定。
new 泛型约束,是怎么实现约束的?
这里参考和搬运沉睡的木木夕的文章:https://www.cnblogs.com/ms27946/p/Note-About-The-Generic-Constraint-Of-New.html
先来看一段泛型约束的示例代码:
代码语言:javascript复制public static T CreateInstance<T>() where T: new() => new T();
如果说我们不知道泛型底下到底做了什么操作,我们也不用急,我们可以用 ILSpy 来看查看一下,代码片段如下:
代码语言:javascript复制.method public hidebysig static
!!T CreateInstance<.ctor T> () cil managed
{
// Method begins at RVA 0x2053
// Code size 6 (0x6)
.maxstack 8
IL_0000: call !!0 [System.Private.CoreLib]System.Activator::CreateInstance<!!T>()
IL_0005: ret
} // end of method C::CreateInstance
在 IL_0000 就能明显看出泛型约束 new() 的底层实现是通过反射来实现的。至于 System.Activator.CreateInstance<T>
方法实现我在这里就不提了。只知道这里用的是它就足够了。不知道大家看到这里有没有觉得一丝惊讶,我当时是有被惊到的,因为我的第一想法就是觉得这么简单肯定是直接调用无参 .ctor,居然是用到的反射。毕竟编译器拥有在编译器就能识别具体的泛型类了。现在可以马后炮的讲:正因为是编译器只有在编译期才确定具体泛型类型,所以编译器无法事先知道要直接调用哪些无参构造函数类,所以才用到了反射。
关于 System.Activator.CreateInstance<T>()
的方法描述,在微软官网api中的remark部分有提到。
如果本文仅仅只是这样,那我肯定没有勇气写下这片文章的。因为其实已经有人早在 04 年园子里就提到了这一点。但是我查到的资料也就止步于此。
试想一下 ,如果你的框架中有些方法用到了无参构造函数泛型约束,并且处于调用的热路径上,其实这样性能是大打折扣的,因为反射 Activator.CreateInstance
性能肯定是远远不如直接调用无参构造函数的。