C#基础面试题

1、反射

System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFrom(info.filePath);
IModule iModule = (IModule)assembly.CreateInstance(info.className);
iModule.DoModule(this, info.moduleGroupID, info.param);

2、值类型和引用类型

2.1 值类型(System.ValueType)

  • 整型、浮点型、布尔、char、struct、enum
  • 变量存储实际的数据
  • 实际数据存储在栈上,操作系统负责管理分配释放

2.2 引用类型(System.Reference)

  • object、string、array、class、delegate、dynamic
  • 变量存储的是数据的地址
  • 实际数据存储在堆上,GC负责管理分配释放

2.3 装箱和拆箱

将值类型=>引用类型:装箱;引用类型=>值类型:拆箱。装箱和拆箱都需要进行大量的运算,装箱还需要分配和构造一个新对象,性能消耗更多。建议使用泛型。

3、dynamic和object的区别

C#中所有的类型(引用类型、值类型、预定义)都继承自Object。所以任何类型的值都可以用object来接收,编译时会进行类型检查。

dynamic动态类型会在编译时绕过类型检查,运行时解析。所以只存在于编译时,不存在于运行时。

    public class TestDynamicAndObjectType
    {
        public static void ConsoleWrite(string inputArg)
        {
            Console.WriteLine(inputArg);
        }
        static void Main()
        {
            // object 类型演示 
            object obj = "追逐时光者";
            // 编译错误 除非转化为 string 类型 
            ConsoleWrite(obj);// compile error 
            ConsoleWrite((string)obj); // correct 
            Console.ReadKey();
            
            // dynamic 类型演示 
            dynamic dyn = "追逐时光者";
            ConsoleWrite(dyn); // correct 
            dyn = 1;
            // 编译时不报错, 运行时会报错 
            ConsoleWrite(dyn);// Runtime Error 
            Console.ReadKey();
        }
    }

4、多线程实现的几种方式

并行:同一时刻有多条指令在多个处理器上同时运行,无论宏观还是微观都是同时发生

并发:同一时段内,宏观上多个任务同时进行,微观上快速切换执行,同一时刻可能只有一条指令在执行

4.1 Thread 类

Thread 是 .NET 中最基础的多线程实现方式,直接创建一个新的线程来执行任务。

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread thread = new Thread(DoWork);
        thread.Start();
        Console.WriteLine("Main thread is running.");
    }

    static void DoWork()
    {
        Console.WriteLine("Worker thread is running.");
    }
}

特点

  • 需要手动控制线程的创建、启动和停止,管理线程的生命周期。

  • 适用于简单的多线程任务。


4.2 ThreadPool

ThreadPool 是一个线程池,用于管理和复用线程,避免频繁创建和销毁线程的开销。

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        ThreadPool.QueueUserWorkItem(DoWork);
        Console.WriteLine("Main thread is running.");
    }

    static void DoWork(object state)
    {
        Console.WriteLine("Worker thread is running.");
    }
}

特点

  • 自动管理线程的生命周期。
  • 需要执行大量短时间任务。

  • 避免频繁创建和销毁线程。


4.3 Task 和 Task<T>

Task 是 .NET 中推荐的多线程实现方式,基于线程池,支持异步编程和任务并行。

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        Task task = Task.Run(DoWork);
        await task;
        Console.WriteLine("Main thread is running.");
        
        string result = await DownloadContentAsync("https://example.com");
        Console.WriteLine(result);
    }

    static void DoWork()
    {
        Console.WriteLine("Worker thread is running.");
    }

    static async Task<string> DownloadContentAsync(string url)
    {
        using HttpClient client = new HttpClient();
        return await client.GetStringAsync(url);
    }
}

特点

  • 支持异步编程(async/await),避免阻塞主线程,提高响应性。

  • 支持任务并行和任务链。

  • 自动管理线程池。

  • 适用于 I/O 密集型任务(如文件读写、网络请求)。


4.4 Parallel 类

Parallel 类提供了简单的并行编程模型,用于并行执行循环或任务。

using System;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Parallel.For(0, 10, i =>
        {
            Console.WriteLine($"Task {i} is running.");
        });
        Console.WriteLine("Main thread is running.");
    }
}

特点

  • 自动分配任务到多个线程。

  • 适用于数据并行(如并行处理数组或集合)。

  • 适用于 CPU 密集型任务(如计算、图像处理)。
 

4.5 BackgroundWorker

BackgroundWorker 是一个组件,用于在后台执行耗时操作,同时支持进度报告和取消操作。

using System;
using System.ComponentModel;
using System.Threading;

class Program
{
    static void Main()
    {
        BackgroundWorker worker = new BackgroundWorker();
        worker.DoWork += DoWork;
        worker.RunWorkerCompleted += WorkCompleted;
        worker.RunWorkerAsync();
        Console.WriteLine("Main thread is running.");
    }

    static void DoWork(object sender, DoWorkEventArgs e)
    {
        Console.WriteLine("Worker thread is running.");
        Thread.Sleep(2000); // 模拟耗时操作
    }

    static void WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        Console.WriteLine("Worker thread completed.");
    }
}

特点

  • 需要在 UI 应用程序中执行后台任务。适用于 WinForms 或 WPF 应用程序。

  • 支持进度更新和任务取消。


4.6 Timer 类

Timer 用于定期执行任务,适用于定时任务或周期性任务。

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Timer timer = new Timer(DoWork, null, 0, 1000);
        Console.WriteLine("Main thread is running.");
        Console.ReadLine(); // 防止主线程退出
    }

    static void DoWork(object state)
    {
        Console.WriteLine("Worker thread is running.");
    }
}

特点

  • 适用于后台任务或定时任务(如定时任务、心跳检测)。


4.7 Parallel LINQ (PLINQ)

PLINQ 是 LINQ 的并行版本,用于并行处理集合数据。

using System;
using System.Linq;

class Program
{
    static void Main()
    {
        var numbers = Enumerable.Range(1, 100);
        var result = numbers.AsParallel()
                           .Where(n => n % 2 == 0)
                           .Select(n => n * n)
                           .ToList();
        Console.WriteLine($"Result: {string.Join(", ", result)}");
    }
}

特点

  • 适用于数据并行处理。
  • 适用于 CPU 密集型任务。

5、异步模型

5.1 基于任务的异步模型TAP(推荐)

TAP(Task-based Asynchronous Pattern)是.net framework4 引入的,是 .NET 中推荐使用的异步编程模型,基于 Task 和 Task<T> 类型,结合 async 和 await 关键字,提供了一种简洁、高效的异步编程方式。

示例:通过Task和Task<T>实现异步编程,可利用多核处理器,执行多个并发任务

        /// <summary>
        /// 任务并行库(TPL, Task Parallel Library TAP模式)
        /// </summary>
        public static void TestTaskParallel()
        {
            var task1 = Task.Run(() =>
            {
                Console.WriteLine("Task 1 Completed.");
            });

            var task2 = Task.Run(() =>
            {
                Console.WriteLine("Task 2 Completed.");
            });

            Task<int> task3 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Task 3 Completed.");
                return 20; // 返回一个整数值
            });

            //等待所有任务完成
            Task.WaitAll(task1, task2, task3);
        }

5.2 异步编程模型AMP

是使用IAsyncResult接口提供异步行为的旧模型,需要手动创建回调函数。在这种模式下,需要通过Begin和End方法实现同步操作(如BeginWrite和EndWrite实现异步写入操作)。不建议新的开发使用这种模式。.net core以后BeginEnvoke已摒弃使用。

Begin启动异步操作,返回IAsyncResult接口,可用于检测异步调用的过程。

End在异步操作完成后执行,检测异步调用的结果。如果异步调用尚未完成,EndInvoke将阻塞调用线程,直到它完成。

       public void ReadFileAsync(string filePath)
       {
           // 创建文件流
           FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
           // 创建缓冲区
           byte[] buffer = new byte[1024];
           // 开始异步读取
           fs.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(ReadCompleted),
               new AsyncState { FileStream = fs, Buffer = buffer });//IAsyncResult.AsyncState:用户自定义的状态对象。
       }

       private class AsyncState
       {
           public FileStream FileStream { get; set; }
           public byte[] Buffer { get; set; }
       }

       private void ReadCompleted(IAsyncResult ar)
       {
           AsyncState state = (AsyncState)ar.AsyncState;
           try
           {
               // 完成读取操作
               int bytesRead = state.FileStream.EndRead(ar);
               if (bytesRead > 0)
               {
                   // 处理读取到的数据
                   string content = Encoding.UTF8.GetString(state.Buffer, 0, bytesRead);
                   Console.WriteLine($"读取的内容: {content}");
               }
           }
           finally
           {
               // 清理资源
               state.FileStream.Close();
           }
       }
   }

5.3 基于事件的异步模式EAP

EAP模式需要后缀为Async的方法,以及一个或多个的事件、事件委托类型和EventArg派生类型,避免了手动处理回调函数等细节工作。在.net framework 2.0引入,不建议新的开发使用此模式。

        /// <summary>
        /// Event-based Asynchronous Pattern(EAP模式)
        /// </summary>
        static void Main(string[] args)
        {
            var asyncObj = new MyAsyncClass();

            // 订阅异步操作完成事件
            asyncObj.OperationNameCompleted += AsyncObjOperationNameCompleted;

            // 启动异步操作
            asyncObj.DoWorkAsync(10);

            Console.ReadLine();
        }

        /// <summary>
        /// 异步操作完成事件的处理方法
        /// </summary>
        /// <param name="result">result</param>
        private static void AsyncObjOperationNameCompleted(int result)
        {
            Console.WriteLine($"异步操作完成,结果为: {result}");
        }

        public class MyAsyncClass : Component
        {
            /// <summary>
            /// 声明一个委托类型,用于定义异步操作的方法签名
            /// </summary>
            /// <param name="arg"></param>
            /// <returns></returns>
            public delegate void MyAsyncDelegate(int arg);

            /// <summary>
            /// 声明一个事件,用于通知异步操作的完成
            /// </summary>
            public event MyAsyncDelegate OperationNameCompleted;

            /// <summary>
            /// 异步执行方法,接受一个参数 arg
            /// </summary>
            /// <param name="arg"></param>
            public void DoWorkAsync(int arg)
            {
                // 将异步操作放入线程池中执行
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), arg);
            }

            /// <summary>
            /// 真正的异步操作
            /// </summary>
            /// <param name="obj"></param>
            private void DoWork(object obj)
            {
                int arg = (int)obj;
                int res = arg + 1;

                // 触发事件,传递异步操作的结果
                OperationNameCompleted?.Invoke(res);
            }
        }

6、实现线程同步

在多线程编程中,线程同步是确保多个线程能够正确、有序地访问共享资源的关键机制。如果没有适当的同步机制,可能会导致数据竞争、死锁、资源争用等问题。

6.1 锁(Lock)

锁是最常用的线程同步机制,通过限制同一时间只有一个线程访问共享资源来实现同步。

  • lock 关键字(C#)

lock 是 C# 中最简单的锁机制,基于 Monitor 类实现。简单易用,适用于临界区较小的场景。

private static readonly object _lock = new object();
private static int _counter = 0;

public static void IncrementCounter()
{
    lock (_lock)
    {
        _counter++;
    }
}
  • Monitor 类

Monitor 是 lock 关键字的基础,提供了更灵活的控制(如超时机制),比 lock 更灵活。

private static readonly object _lock = new object();

public static void IncrementCounter()
{
    bool lockTaken = false;
    try
    {
        Monitor.Enter(_lock, ref lockTaken);
        _counter++;
    }
    finally
    {
        if (lockTaken)
        {
            Monitor.Exit(_lock);
        }
    }
}

6.2 信号量(Semaphore)

Semaphore 是一个计数信号量,可以限制同时访问资源的线程数量(如数据库连接池、线程池)。支持跨进程同步。

private static Semaphore _semaphore = new Semaphore(initialCount: 2, maximumCount: 2);

public static void AccessResource()
{
    _semaphore.WaitOne(); // 请求信号量
    try
    {
        // 访问共享资源
    }
    finally
    {
        _semaphore.Release(); // 释放信号量
    }
}

6.3 互斥体(Mutex)

Mutex 是一个系统级别的锁,可以跨进程使用(如多个应用程序访问共享资源)。比 lock 更重量级,性能较低。

查看代码
private static Mutex _mutex = new Mutex();

public static void AccessResource()
{
    _mutex.WaitOne(); // 请求互斥体
    try
    {
        // 访问共享资源
    }
    finally
    {
        _mutex.ReleaseMutex(); // 释放互斥体
    }
}
示例
static void Main(string[] args)
{
    bool createdNew;
    System.Threading.Mutex instance = new System.Threading.Mutex(true, "MutexName", out createdNew);
    if (!createdNew) //已经有应用程序副本执行
    {
        Application.ExitThread();
        return;
    }
}

6.4 读写锁(ReaderWriterLock)

读写锁允许多个线程同时读取共享资源,但写操作需要独占访问。适用于读多写少的场景(如缓存、配置管理),需要提高读操作的并发性能。

ReaderWriterLockSlim 是轻量级的读写锁,性能优于 ReaderWriterLock。

private static ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
private static int _sharedData = 0;

public static int ReadData()
{
    _rwLock.EnterReadLock();
    try
    {
        return _sharedData;
    }
    finally
    {
        _rwLock.ExitReadLock();
    }
}

public static void WriteData(int value)
{
    _rwLock.EnterWriteLock();
    try
    {
        _sharedData = value;
    }
    finally
    {
        _rwLock.ExitWriteLock();
    }
}

6.5 事件(Event)

事件用于线程间的通知机制,允许一个线程等待另一个线程的信号。

  • ManualResetEvent

需要手动重置事件状态,每次 Set 只能释放一个等待的线程。适合一对多的场景。

查看代码
 using System;
using System.Threading;

class Program
{
    private static AutoResetEvent _event = new AutoResetEvent(initialState: false);

    static void Main()
    {
        Thread workerThread = new Thread(DoWork);
        workerThread.Start();

        Console.WriteLine("Main thread is waiting for a signal.");
        _event.WaitOne(); // 等待信号
        Console.WriteLine("Main thread received the signal.");

        workerThread.Join();
    }

    static void DoWork()
    {
        Console.WriteLine("Worker thread is doing some work.");
        Thread.Sleep(2000); // 模拟耗时操作
        Console.WriteLine("Worker thread is sending a signal.");
        _event.Set(); // 发送信号
    }
}
/*
Main thread is waiting for a signal.
Worker thread is doing some work.
Worker thread is sending a signal.
Main thread received the signal.*/
  • AutoResetEvent

AutoResetEvent自动重置事件状态,每次 Set 会释放所有等待的线程。适合一对一的场景。ManualResetEventSlim 是 ManualResetEvent 的轻量级版本,适用于短时间等待的场景。

查看代码
 using System;
using System.Threading;

class Program
{
    private static ManualResetEventSlim _event = new ManualResetEventSlim(initialState: false);

    static void Main()
    {
        Thread workerThread = new Thread(DoWork);
        workerThread.Start();

        Console.WriteLine("Main thread is waiting for a signal.");
        _event.Wait(); // 等待信号
        Console.WriteLine("Main thread received the signal.");

        workerThread.Join();
    }

    static void DoWork()
    {
        Console.WriteLine("Worker thread is doing some work.");
        Thread.Sleep(2000); // 模拟耗时操作
        Console.WriteLine("Worker thread is sending a signal.");
        _event.Set(); // 发送信号
    }
}
/*
Main thread is waiting for a signal.
Worker thread is doing some work.
Worker thread is sending a signal.
Main thread received the signal.
*/

6.6 屏障(Barrier)

Barrier 允许多个线程在某个点等待,直到所有线程都到达后再继续执行。

private static Barrier _barrier = new Barrier(participantCount: 3);

public static void ThreadWork()
{
    Console.WriteLine("Thread reached the barrier.");
    _barrier.SignalAndWait(); // 等待其他线程
    Console.WriteLine("Thread passed the barrier.");
}

6.7 原子操作(Atomic Operations)

原子操作是不可分割的操作,确保在多线程环境下不会被打断。

Interlocked 提供了一系列原子操作方法,如 Increment、Decrement、Exchange 等。适用于简单的计数器场景,性能高,无需锁。

private static int _counter = 0;

public static void IncrementCounter()
{
    Interlocked.Increment(ref _counter);
}

6.8 线程安全的集合

.NET 提供了多种线程安全的集合类,如 ConcurrentQueue、ConcurrentDictionary 等。

ConcurrentQueue 是一个线程安全的队列。适用于多线程环境下的数据共享,无需手动加锁。

private static ConcurrentQueue<int> _queue = new ConcurrentQueue<int>();

public static void EnqueueData(int value)
{
    _queue.Enqueue(value);
}

public static bool TryDequeueData(out int value)
{
    return _queue.TryDequeue(out value);
}

 

posted @ 2025-03-10 13:46  茜茜87  阅读(203)  评论(0)    收藏  举报