在C#开发过程中,new是一个多功能的关键字,根据使用场景的不同,它可以承担运算符、修饰符、泛型约束三种完全不同的角色,每种角色对应的语法规则和使用场景都有明确区分,很多初学者容易混淆这些用法,接下来就逐一展开说明。

一、new作为运算符:创建对象与调用构造函数
这是new最常见的用法,用来在堆上分配内存、创建指定类型的实例,并且会自动调用对应类型的构造函数完成对象的初始化工作。使用new创建对象后,会返回该对象的引用,我们需要将这个引用赋值给对应类型的变量才能后续使用。
如果是值类型,使用new创建实例时也会调用对应的构造函数,不过值类型的内存分配通常在栈上,除非它被包含在引用类型中。下面通过代码示例展示new作为运算符的基本用法:
// 定义普通的引用类型类
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
// 自定义构造函数
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
// 定义值类型结构体
public struct Point
{
public int X { get; set; }
public int Y { get; set; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
class Program
{
static void Main()
{
// new作为运算符创建引用类型实例
Person person = new Person("张三", 25);
Console.WriteLine($"姓名:{person.Name},年龄:{person.Age}");
// new作为运算符创建值类型实例
Point point = new Point(10, 20);
Console.WriteLine($"坐标X:{point.X},坐标Y:{point.Y}");
// 也可以不显式调用构造函数,使用默认无参构造
Person defaultPerson = new Person();
defaultPerson.Name = "李四";
defaultPerson.Age = 30;
}
}需要注意,如果类型没有定义无参构造函数,就不能直接使用new 类型名()的方式创建实例,必须传入构造函数要求的参数。另外new运算符创建的对象,在不再被引用时会被垃圾回收器自动回收,不需要开发者手动释放内存。
二、new作为修饰符:隐藏基类可继承成员
当派生类定义了和基类同名的成员(包括方法、属性、字段、事件等)时,如果希望派生类的成员覆盖基类的对应成员,就需要在派生类的成员前加上new修饰符,这种用法叫做成员隐藏。如果不加new修饰符,编译器会给出警告,提示你如果是有意隐藏基类成员,需要添加new关键字。
new修饰符隐藏成员只会在编译阶段生效,运行时如果通过基类类型的引用访问该成员,仍然会调用基类的版本,这和override重写的多态特性有本质区别。下面通过代码示例对比new隐藏和override重写的差异:
// 基类
public class BaseClass
{
// 基类的虚方法,可以被override重写
public virtual void ShowMessage()
{
Console.WriteLine("这是基类的方法输出");
}
// 基类的普通方法,无法被override,只能被new隐藏
public void BaseMethod()
{
Console.WriteLine("这是基类的普通方法");
}
}
// 派生类
public class DerivedClass : BaseClass
{
// 使用new修饰符隐藏基类的虚方法(这里没有用override,所以是隐藏不是重写)
public new void ShowMessage()
{
Console.WriteLine("这是派生类隐藏后的方法输出");
}
// 使用new修饰符隐藏基类的普通方法
public new void BaseMethod()
{
Console.WriteLine("这是派生类隐藏后的普通方法");
}
}
class Program
{
static void Main()
{
DerivedClass derived = new DerivedClass();
// 直接通过派生类引用调用,访问的是派生类隐藏后的成员
derived.ShowMessage(); // 输出:这是派生类隐藏后的方法输出
derived.BaseMethod(); // 输出:这是派生类隐藏后的普通方法
// 将派生类实例赋值给基类类型的引用
BaseClass baseRef = derived;
// 通过基类引用调用,访问的是基类的原始成员,不会触发派生类的隐藏版本
baseRef.ShowMessage(); // 输出:这是基类的方法输出
baseRef.BaseMethod(); // 输出:这是基类的普通方法
}
}使用new修饰符隐藏成员时,派生类成员的可访问性可以和基类成员不同,但通常建议保持一致的访问级别,避免逻辑混乱。如果需要在派生类中访问被隐藏的基类成员,可以通过base.成员名的方式调用。
三、new作为泛型约束:限定类型参数
在定义泛型类型或者泛型方法时,我们可以使用new()作为泛型约束,要求传入的类型参数必须有一个无参的公共构造函数。这样在泛型内部就可以使用new T()的方式创建类型参数的实例,否则编译器会报错,因为无法保证传入的类型支持无参构造。
new()约束必须放在所有泛型约束的最后面,不能和其他约束的顺序颠倒。下面通过代码示例展示new作为泛型约束的用法:
// 定义泛型类,添加new()约束,要求T必须有公共无参构造函数
public class ObjectFactory<T> where T : new()
{
// 泛型方法,创建T类型的实例
public T CreateInstance()
{
// 因为有了new()约束,这里可以合法使用new T()
return new T();
}
}
// 符合约束的类,有公共无参构造函数
public class Product
{
public string ProductName { get; set; }
// 公共无参构造函数
public Product()
{
ProductName = "默认产品";
}
}
// 不符合约束的类,没有公共无参构造函数
public class User
{
public string UserName { get; set; }
// 只有带参构造函数,没有无参构造函数
public User(string name)
{
UserName = name;
}
}
class Program
{
static void Main()
{
// 正确:Product有公共无参构造函数,符合约束
ObjectFactory<Product> productFactory = new ObjectFactory<Product>();
Product product = productFactory.CreateInstance();
Console.WriteLine(product.ProductName); // 输出:默认产品
// 错误:User没有公共无参构造函数,无法满足new()约束,编译会报错
// ObjectFactory<User> userFactory = new ObjectFactory<User>();
}
}需要注意,如果类型参数同时需要继承某个基类和满足new()约束,那么基类约束要写在前面,new()约束放在最后,例如where T : BaseClass, new()这样的写法才是正确的。
三种用法的核心区别总结
为了更清晰地区分new的三种用法,我们可以通过下面的表格做对比:
| 用法角色 | 使用场景 | 作用 | 语法特征 |
|---|---|---|---|
| 运算符 | 创建对象实例 | 分配内存、调用构造函数、返回对象引用 | 后面跟类型名和构造函数参数 |
| 修饰符 | 派生类定义和基类同名的成员 | 显式隐藏基类可继承成员,消除编译警告 | 加在派生类成员声明的最前面 |
| 泛型约束 | 定义泛型类型或泛型方法 | 要求类型参数必须有公共无参构造函数 | 写在where子句的最后,形式为new() |
实际开发过程中,我们可以根据当前的使用场景判断new的角色,避免把不同用法的规则搞混。比如不要试图在泛型约束的场景下用new创建对象却忘记加new()约束,也不要在需要多态重写的时候错误使用new修饰符导致逻辑不符合预期。