在.NET面试中,你很可能会遇到各种各样的C#问题,这些问题构成了.NET开发的核心内容。这些问题通常涵盖设计模式、语言特性、语言集成查询(LINQ)、委托等多个方面。在本文中,我们将逐一梳理这些问题,提供详细的解释以及经过改写的示例来帮助理解。无论你是一位有抱负的开发人员,还是经验丰富的C#专业人士,本指南都旨在成为一份全面的参考资料,帮助你为应对各种具有挑战性的C#面试做好准备。
public class Vehicle
{
public Vehicle(int yearManufactured)
{
Console.WriteLine($"The vehicle was manufactured in {yearManufactured}");
}
}
public class Car : Vehicle
{
public Car(string model, int yearManufactured) : base(yearManufactured)
{
Console.WriteLine($"The car model is {model}");
}
static Car()
{
Console.WriteLine("Initializing the Car class...");
}
}
class Program
{
static void Main(string[] args)
{
var car1 = new Car("Sedan", 2015);
var car2 = new Car("SUV", 2020);
}
}
答案:
构造函数的顺序及执行行为:
当代码执行时,操作的顺序由继承结构以及静态构造函数的存在与否来决定:
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#不支持多重继承。
public class Vehicle
{
public void Start() => Console.WriteLine("Vehicle started");
}
public abstract class FlyingMachine
{
public abstract void Fly();
}
// 取消下面这行代码的注释将会导致编译时错误。
// public class Airplane : Vehicle, FlyingMachine // C#中不允许多重继承
// {
// public override void Fly() => Console.WriteLine("Airplane flying");
// }
public class Car : Vehicle // 正确:单继承
{
public void Drive() => Console.WriteLine("Car is driving");
}
// ** 2. 一个接口能否从一个抽象类继承? **
// 解释:这是不正确的,因为接口只能从其他接口继承。
public abstract class Machine
{
public abstract void Operate();
}
public interface IFlyable
{
void Fly();
}
// 取消下面这行代码的注释将会导致编译时错误。
// public interface IFlyableMachine : Machine // 接口不能从抽象类继承
// {
// }
public interface IFlyableMachine : IFlyable // 正确:接口可以从其他接口继承
{
}
// ** 3. 一个抽象类能否从另一个抽象类继承? **
// 解释:这是正确的,因为抽象类可以从其他抽象类继承。
public abstract class Animal
{
public abstract void MakeSound();
}
public abstract class Mammal : Animal
{
public abstract void Walk();
}
public class Dog : Mammal // 实现抽象成员的具体类
{
public override void MakeSound() => Console.WriteLine("Dog barking");
public override void Walk() => Console.WriteLine("Dog walking");
}
// ** 4. 一个类能否从两个或更多的基类继承? **
// 解释:这是不正确的,因为C#不支持类的多重继承。
public class Engine
{
public void Run() => Console.WriteLine("Engine running");
}
public class Transmission
{
public void Shift() => Console.WriteLine("Shifting gears");
}
// 取消下面这行代码的注释将会导致编译时错误。
// public class SportsCar : Engine, Transmission // C#中不允许多重继承
// {
// }
public class SportsCar : Engine // 正确:从一个基类进行单继承
{
public void DriveFast() => Console.WriteLine("SportsCar driving fast");
}
// ** 5. 一个类能否从两个或更多的抽象类继承? **
// 解释:这是不正确的,因为C#不支持抽象类的多重继承。
public abstract class AbstractClass1
{
public abstract void Method1();
}
public abstract class AbstractClass2
{
public abstract void Method2();
}
// 取消下面这行代码的注释将会导致编译时错误。
// public class CombinedClass : AbstractClass1, AbstractClass2 // C#中不允许多重抽象继承
// {
// }
public class CombinedClass : AbstractClass1 // 正确:从一个抽象类继承
{
public override void Method1() => Console.WriteLine("Implementing Method1");
}
// ** 6. 一个类能否在从一个基类继承的同时实现多个接口? **
// 解释:这是正确的,因为C#支持单继承以及实现多个接口。
public interface ISwimmable
{
void Swim();
}
public class AmphibiousVehicle : Vehicle, IFlyable, ISwimmable // 正确:从Vehicle进行单继承并实现多个接口
{
public void Fly() => Console.WriteLine("Amphibious vehicle flying");
public void Swim() => Console.WriteLine("Amphibious vehicle swimming");
}
从上述代码片段及解释中,我们可以得出以下结论:
public class Vehicle
{
public string GetInfo() => "Generic Vehicle";
}
public class Car : Vehicle
{
public new string GetInfo() => "Car";
}
class Program
{
static void Main(string[] args)
{
Vehicle myVehicle = new Car();
Console.WriteLine(myVehicle.GetInfo());
}
}
答案:
方法隐藏(使用new
关键字):派生类定义了一个新方法,该方法隐藏了基类的方法。当通过基类引用访问时,会调用基类中的方法。
方法重写(使用override
关键字):派生类替换了基类的方法。即便使用基类引用,调用的也是派生类中被重写的方法。
输出结果:
// 输出:"Generic Vehicle"
由于方法隐藏,这里调用的是基类的方法。
答案:
在LINQ中,通过使用DefaultIfEmpty()
可以实现左外连接,以确保左表(或集合)中的所有记录都被包含进来,即便在右表中没有匹配项也不例外。
var employees = new List<Employee>
{
new Employee { Id = 1, Name = "Alice" },
new Employee { Id = 2, Name = "Bob" }
};
var departments = new List<Department>
{
new Department { EmployeeId = 1, 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()
select new { emp.Name, Dept = d?.DeptName?? "No Department" };
这个查询确保了所有员工(包括那些没有所属部门的员工)都会被包含在结果中。
答案:匿名类型是在编译时创建的,一旦定义就无法修改。
var items = new List<dynamic>
{
new { Id = 1, Name = "Item1" },
new { Id = 2, Name = "Item2" }
};
// 以下代码会导致编译时错误:
items.Add(new { Id = 3, Name = "Item3" });
匿名类型一旦创建就是不可变的,并且你无法在定义它们的方法之外添加相同匿名类型的新项。
public class Elevator
{
public delegate void GoToFloorDelegate(int floor);
public GoToFloorDelegate GoToFloor { get; set; }
public void GoToReception()
{
GoToFloor?.Invoke(0);
Console.WriteLine("I am at the reception now...");
}
}
public static void GoFast(int floor)
{
Console.WriteLine($"Going fast to floor {floor}");
}
答案:可以不使用自定义委托,而是利用lambda表达式以及内置的Action
委托(它可以接收参数并且无返回值)来简化代码:
public class Elevator
{
public Action<int> GoToFloor { get; set; }
public void GoToReception()
{
GoToFloor?.Invoke(0);
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 interface IVehicle
{
int NumberOfWheels { get; set; }
}
// 在此处使用泛型实现代码
public class Factory
{
}
public class Car : IVehicle
{
public int NumberOfWheels { get; set; }
}
public class Motorcycle : IVehicle
{
public int NumberOfWheels { get; set; }
}
// 演示
class Program
{
static void Main()
{
// 创建一个有4个轮子的汽车
Car car = Factory.BuildFrame<Car>(4);
Console.WriteLine($"Car with {car.NumberOfWheels} wheels.");
// 创建一个有2个轮子的摩托车
Motorcycle motorcycle = Factory.BuildFrame<Motorcycle>(2);
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;
}