C# 面试手册

进阶

题目1

请实做 Convert 方法并告知原因及其思路

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
 
public class Teacher
{
    public string Name { get; set; }
    public int Age { get; set; }
}
 
public class Student
{
    public string Name { get; set; }
    public int Age { get; set; }
}
 
public static void Action()
{
    var teacher = new Teacher()
    {
        Name = "John",
        Age = 26
    };
 
    Student a = Convert<Teacher, Student>(teacher);
    Person b = Convert<Teacher, Person>(teacher);
}
 
public static T2 Convert<T1, T2>(T1 obj)
{
    throw new NotImplementedException();
}

解析如下

  1. 反射

            if (obj == null) return default;
     
            // 获取目标类型的空参构造函数
            var targetType = typeof(T2);
            var constructor = targetType.GetConstructor(Type.EmptyTypes);
            if (constructor == null)
                throw new InvalidOperationException($"{targetType.Name} 必须有无参构造函数");
     
            // 创建目标对象
            var result = (T2)constructor.Invoke(null);
     
            // 复制同名属性
            var sourceProperties = typeof(T1).GetProperties(BindingFlags.Public | BindingFlags.Instance);
            var targetProperties = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(p => p.CanWrite);
     
            foreach (var sourceProp in sourceProperties)
            {
                var targetProp = targetProperties.FirstOrDefault(p => p.Name == sourceProp.Name);
                if (targetProp != null)
                {
                    var value = sourceProp.GetValue(obj);
                    targetProp.SetValue(result, value);
                }
            }
     
            return result;
  2. 序列化

            var json = JsonSerializer.Serialize(obj);
            return JsonSerializer.Deserialize<T2>(json);

题目2

请简述下述程序打印结果,以及原因?

    public static void Action()
    {
        int filterCount = 0;
        var colors = new List<string> { "Red", "Green", "Blue" }
            .Where(x =>
            {
                filterCount++;
                return x.StartsWith("G");
            }).OrderBy(x => x);
        
        Console.WriteLine(filterCount);
        Console.WriteLine(colors.Count());
        Console.WriteLine(filterCount);
    }

输出

0
1
3

核心原因在于 LINQ 的延迟执行。查询定义时 (Where, OrderBy) 不执行 lambda,只有在需要结果(如调用 Count(), ToList(), foreach 等)时,查询才会被执行,导致 Where 中的 filterCount++ 在 colors.Count() 被调用时才真正对原始列表的每个元素执行。

题目3

请优化 Action 中的方法逻辑并告知其思路

public class Booking
{
    public int Id { get; set; }
    
    public decimal Price { get; set; }
    
    public DateTime CreateTime { get; set; }
}
 
/// <summary>
/// 打印月销售额
/// </summary>
/// <param name="bookings">假设 bookings 有 50w 的元素,如何优化?</param>
public static void Action(List<Booking> bookings)
{
    for (int i = 1; i <= 12; i++)
    {
        decimal totalPrice = bookings
            .Where(x => x.CreateTime.Month == i)
            .Sum(x => x.Price);
 
        Console.WriteLine($"{i}月总销售额:{totalPrice}");
    }
}
  1. LINQ with GroupBy

    public static void ActionOptimize(List<Booking> bookings)
    {
        var dictionary = bookings
            .GroupBy(b => b.CreateTime.Month)
            .ToDictionary(g => g.Key, g => g.Sum(b => b.Price));
     
        for (int i = 1; i <= 12; i++)
        {
            Console.WriteLine(dictionary.TryGetValue(i, out var totalPrice) ? $"{i}月总销售额:{totalPrice}" : $"{i}月总销售额:0");
        }
    }
  2. 并行计算

    public static void ActionParallel(List<Booking> bookings)
    {
        var monthlyTotals = new ConcurrentDictionary<int, decimal>();
     
        // 并行分组计算
        Parallel.ForEach(
            bookings,
            booking =>
            {
                int month = booking.CreateTime.Month;
                monthlyTotals.AddOrUpdate(month, booking.Price, (_, existing) => existing + booking.Price);
            });
     
        for (int month = 1; month <= 12; month++)
        {
            decimal totalPrice = monthlyTotals.TryGetValue(month, out var sum) ? sum : 0;
            Console.WriteLine($"{month}月总销售额:totalPrice");
        }
    }

题目4

请简述下述程序打印结果,以及原因?

public class Caching<T>
{
    private static ConcurrentDictionary<string, object> CacheDict = new();
 
    public void SetValue(string key, T value)
    {
        CacheDict.AddOrUpdate(key, value, (key, lastValue) =>
        {
            return value;
        });
    }
 
    public object this[string key]
    {
        get { return CacheDict[key];}
        set { CacheDict[key] = value;}
    }
}
 
public static void Action()
{
    var names = new Caching<string>();
    names.SetValue("John", "John");
 
    var emails = new Caching<string>();
    emails.SetValue("John", "John@cc.com");
 
    var ages = new Caching<int>();
    ages.SetValue("John", 26);
 
    Console.WriteLine(names["John"]);
    Console.WriteLine(emails["John"]);
    Console.WriteLine(ages["John"]);
}

输出如下

John@cc.com
John@cc.com
26

关键在于理解 C# 泛型类的静态成员特性:每个具体的泛型类型实例化(如 Caching<string>Caching<int>)拥有自己独立的静态成员副本。因此,names 和 emails 共享一个字典,而 ages 使用另一个完全不同的字典,导致了最终的输出结果。

题目5

请简述下述程序打印结果,以及原因?

delegate double MyDelegate(int num);
 
public static void Exec()
{
    var myDelegate = new MyDelegate(Run1);
    myDelegate += Run2;
    Console.WriteLine($"结果: {myDelegate(100)}");
}
 
static double Run1(int num)
{
    var result = num * 0.1; 
    Console.WriteLine($"Run1-{result}");
    return result;
}
 
static double Run2(int num)
{
    var result = num * 0.2;
    Console.WriteLine($"Run1-{result}");
    return result;
}

输出

Run1-10
Run1-20
结果: 20

多播委托会按照方法添加的顺序依次执行所有绑定的方法。每个方法都会执行其内部的逻辑(包括 Console.WriteLine)。但是,整个委托调用的返回值仅仅是最后一个被调用的方法的返回值。

题目6

请简述下述程序打印结果,以及原因?

public static void Action()
{
    var person = new Person()
    {
        Name = "John",
        Age = 1
    };
 
    var actions = new List<Action>();
 
    for (int i = 0; i < 3; i++)
    {
        actions.Add(() =>
        {
            person.Age = person.Age * i;
            Console.WriteLine($"age: {person.Age}");
        });
    }
    
    foreach (var action in actions)
    {
        action.Invoke();
    }
}

输出

age: 3
age: 9
age: 27

所有添加到 actions 列表中的 Lambda 表达式在执行时,都会使用循环变量 i 的最终值(即 3),并且它们都会操作同一个 person 对象的 Age 属性,导致 Age 的值在前一次计算的基础上进行累积性的乘法。

On this page