在.NET面试中,你很可能会遇到各种各样的C#问题,这些问题构成了.NET开发的核心内容。这些问题通常涵盖设计模式、语言特性、语言集成查询(LINQ)、委托等多个方面。在本文中,我们将逐一梳理这些问题,提供详细的解释以及经过改写的示例来帮助理解。无论你是一位有抱负的开发人员,还是经验丰富的C#专业人士,本指南都旨在成为一份全面的参考资料,帮助你为应对各种具有挑战性的C#面试做好准备。
public classVehicle
{
publicVehicle(int yearManufactured)
{
Console.WriteLine($"The vehicle was manufactured in {yearManufactured}");
}
}
publicclassCar:Vehicle
{
publicCar(string model,int yearManufactured):base(yearManufactured)
{
Console.WriteLine($"The car model is {model}");
}
staticCar()
{
Console.WriteLine("Initializing the Car class...");
}
}
classProgram
{
staticvoidMain(string[] args)
{
var car1 =newCar("Sedan",);
var car2 =newCar("SUV",);
}
}
答案:
构造函数的顺序及执行行为:
当代码执行时,操作的顺序由继承结构以及静态构造函数的存在与否来决定:
Car
类中的静态构造函数在首次实例化Car
对象时执行一次。它会输出“Initializing the Car class...”,并且即便后续创建更多的Car
对象,它也不会再次运行。Car
对象,在执行Car
构造函数中的任何内容之前,都会先调用基类Vehicle
的构造函数。这确保了对象中属于Vehicle
的部分能首先被正确初始化。Car
通过处理其自身的成员(比如打印汽车型号)来完成初始化过程。输出结果如下:
Initializing the Car class...
The vehicle was manufactured in 2015
The car model is Sedan
The vehicle was manufactured in 2020
The car model is SUV
using System;
// ** 1. 一个类能否同时从一个普通类和一个抽象类继承? **
// 解释:这是不正确的,因为C#不支持多重继承。
publicclassVehicle
{
publicvoidStart()=> Console.WriteLine("Vehicle started");
}
publicabstractclassFlyingMachine
{
publicabstractvoidFly();
}
// 取消下面这行代码的注释将会导致编译时错误。
// public class Airplane : Vehicle, FlyingMachine // C#中不允许多重继承
// {
// public override void Fly() => Console.WriteLine("Airplane flying");
// }
publicclassCar:Vehicle// 正确:单继承
{
publicvoidDrive()=> Console.WriteLine("Car is driving");
}
// ** 2. 一个接口能否从一个抽象类继承? **
// 解释:这是不正确的,因为接口只能从其他接口继承。
publicabstractclassMachine
{
publicabstractvoidOperate();
}
publicinterfaceIFlyable
{
voidFly();
}
// 取消下面这行代码的注释将会导致编译时错误。
// public interface IFlyableMachine : Machine // 接口不能从抽象类继承
// {
// }
publicinterfaceIFlyableMachine:IFlyable// 正确:接口可以从其他接口继承
{
}
// ** 3. 一个抽象类能否从另一个抽象类继承? **
// 解释:这是正确的,因为抽象类可以从其他抽象类继承。
publicabstractclassAnimal
{
publicabstractvoidMakeSound();
}
publicabstractclassMammal:Animal
{
publicabstractvoidWalk();
}
publicclassDog:Mammal// 实现抽象成员的具体类
{
publicoverridevoidMakeSound()=> Console.WriteLine("Dog barking");
publicoverridevoidWalk()=> Console.WriteLine("Dog walking");
}
// ** 4. 一个类能否从两个或更多的基类继承? **
// 解释:这是不正确的,因为C#不支持类的多重继承。
publicclassEngine
{
publicvoidRun()=> Console.WriteLine("Engine running");
}
publicclassTransmission
{
publicvoidShift()=> Console.WriteLine("Shifting gears");
}
// 取消下面这行代码的注释将会导致编译时错误。
// public class SportsCar : Engine, Transmission // C#中不允许多重继承
// {
// }
publicclassSportsCar:Engine// 正确:从一个基类进行单继承
{
publicvoidDriveFast()=> Console.WriteLine("SportsCar driving fast");
}
// ** 5. 一个类能否从两个或更多的抽象类继承? **
// 解释:这是不正确的,因为C#不支持抽象类的多重继承。
publicabstractclassAbstractClass1
{
publicabstractvoidMethod1();
}
publicabstractclassAbstractClass2
{
publicabstractvoidMethod2();
}
// 取消下面这行代码的注释将会导致编译时错误。
// public class CombinedClass : AbstractClass1, AbstractClass2 // C#中不允许多重抽象继承
// {
// }
publicclassCombinedClass:AbstractClass1// 正确:从一个抽象类继承
{
publicoverridevoidMethod1()=> Console.WriteLine("Implementing Method1");
}
// ** 6. 一个类能否在从一个基类继承的同时实现多个接口? **
// 解释:这是正确的,因为C#支持单继承以及实现多个接口。
publicinterfaceISwimmable
{
voidSwim();
}
publicclassAmphibiousVehicle:Vehicle, IFlyable, ISwimmable// 正确:从Vehicle进行单继承并实现多个接口
{
publicvoidFly()=> Console.WriteLine("Amphibious vehicle flying");
publicvoidSwim()=> Console.WriteLine("Amphibious vehicle swimming");
}
从上述代码片段及解释中,我们可以得出以下结论:
public classVehicle
{
publicstringGetInfo()=>"Generic Vehicle";
}
publicclassCar:Vehicle
{
publicnewstringGetInfo()=>"Car";
}
classProgram
{
staticvoidMain(string[] args)
{
Vehicle myVehicle =newCar();
Console.WriteLine(myVehicle.GetInfo());
}
}
答案:
方法隐藏(使用new
关键字):派生类定义了一个新方法,该方法隐藏了基类的方法。当通过基类引用访问时,会调用基类中的方法。
方法重写(使用override
关键字):派生类替换了基类的方法。即便使用基类引用,调用的也是派生类中被重写的方法。
输出结果:
// 输出:"Generic Vehicle"
由于方法隐藏,这里调用的是基类的方法。
答案:
在LINQ中,通过使用DefaultIfEmpty()
可以实现左外连接,以确保左表(或集合)中的所有记录都被包含进来,即便在右表中没有匹配项也不例外。
var employees =newList<Employee>
{
newEmployee{ Id =, Name ="Alice"},
newEmployee{ Id =, Name ="Bob"}
};
var departments =newList<Department>
{
newDepartment{ EmployeeId =, DeptName ="HR"}
};
var result =from emp in employees
join dept in departments on emp.Id equals dept.EmployeeId
into empDept
from d in empDept.DefaultIfEmpty()
selectnew{ emp.Name, Dept = d?.DeptName??"No Department"};
这个查询确保了所有员工(包括那些没有所属部门的员工)都会被包含在结果中。
答案:匿名类型是在编译时创建的,一旦定义就无法修改。
var items =newList<dynamic>
{
new{ Id =, Name ="Item1"},
new{ Id =, Name ="Item2"}
};
// 以下代码会导致编译时错误:
items.Add(new{ Id =, Name ="Item3"});
匿名类型一旦创建就是不可变的,并且你无法在定义它们的方法之外添加相同匿名类型的新项。
public classElevator
{
publicdelegatevoidGoToFloorDelegate(int floor);
publicGoToFloorDelegate GoToFloor {get;set;}
publicvoidGoToReception()
{
GoToFloor?.Invoke();
Console.WriteLine("I am at the reception now...");
}
}
publicstaticvoidGoFast(int floor)
{
Console.WriteLine($"Going fast to floor {floor}");
}
答案:可以不使用自定义委托,而是利用lambda表达式以及内置的Action
委托(它可以接收参数并且无返回值)来简化代码:
public classElevator
{
publicAction<int> GoToFloor {get;set;}
publicvoidGoToReception()
{
GoToFloor?.Invoke();
Console.WriteLine("I am at the reception now...");
}
}
使用lambda表达式可以简化方法调用:
elevator.GoToFloor = floor => Console.WriteLine($"Moving fast to floor {floor}");
Action
,何时应该使用Func
?答案:
Action
用于那些不返回值但最多可以接收16个参数的委托。
Action<int> printNumber = n => Console.WriteLine(n);
Func
用于那些会返回值的委托。最后一个类型参数定义了返回类型。
Func<int, string> numberToString = n => n.ToString();
public interfaceIVehicle
{
int NumberOfWheels {get;set;}
}
// 在此处使用泛型实现代码
publicclassFactory
{
}
publicclassCar:IVehicle
{
publicint NumberOfWheels {get;set;}
}
publicclassMotorcycle:IVehicle
{
publicint NumberOfWheels {get;set;}
}
// 演示
classProgram
{
staticvoidMain()
{
// 创建一个有4个轮子的汽车
Car car = Factory.BuildFrame<Car>();
Console.WriteLine($"Car with {car.NumberOfWheels} wheels.");
// 创建一个有2个轮子的摩托车
Motorcycle motorcycle = Factory.BuildFrame<Motorcycle>();
Console.WriteLine($"Motorcycle with {motorcycle.NumberOfWheels} wheels.");
}
}
答案:
BuildFrame
方法旨在创建遵循IVehicle
接口的任何类型的车辆(比如汽车或摩托车)。通过使用带有约束的泛型,我们确保BuildFrame
只能处理那些实现了IVehicle
接口并且有无参构造函数(意味着可以使用new T()
来创建)的类型。在BuildFrame
方法内部,我们创建类型T
的一个实例,设置它的NumberOfWheels
属性,然后返回该实例。这种设计使得BuildFrame
能够创建并设置任何遵循IVehicle
接口的车辆类型,使其对于各种车辆类来说既灵活又可复用。
public static T BuildFrame<T>(int wheelsNumber) where T : IVehicle, new()
{
T vehicle = new T(); // 使用new()约束创建T的一个新实例
vehicle.NumberOfWheels = wheelsNumber; // 设置NumberOfWheels属性
return vehicle;
}
点击下方卡片关注DotNet NB