C#抽象类和虚方法的作用是什么?
抽象类 (abstract class
):
-
不能直接实例化,只能被继承。
-
用来定义一套基础框架和规范,强制子类必须实现某些方法(抽象方法)。
-
可用来封装一些共通的逻辑,减少代码重复。
虚方法 (virtual
):
-
表示这个方法可以被子类重写(override)。
-
默认给了一套实现,你可以用,也可以替换掉。
-
避免了子类必须每次都写重复代码(子类用基类实现就好)
总结:
抽象类 + 虚方法组合使用的好处是:
-
提供一个统一的接口和逻辑框架
-
允许子类在不破坏主结构的情况下实现个性化逻辑(比如加缓存、记录日志)
🔗 组合使用的优势
抽象类 + 虚方法 |
---|
✅ 定义统一规范和基础结构 |
✅ 提供默认逻辑(虚方法) |
✅ 允许子类按需定制(重写虚方法) |
✅ 提高代码复用性、可维护性 |
✅ 非侵入式扩展逻辑(如:记录日志、缓存等) |
🔧 举个实际应用场景(例如仓储):
public abstract class BaseRepository<T>
{public virtual void Add(T entity){// 默认实现:记录日志 + 保存Console.WriteLine("添加前记录日志");Save(entity);}protected abstract void Save(T entity); // 强制子类必须实现
}
public class UserRepository : BaseRepository<User>
{protected override void Save(User entity){// 实现具体的保存逻辑Console.WriteLine("保存用户到数据库");}public override void Add(User entity){// 也可以选择重写 Add,增加缓存逻辑等base.Add(entity);Console.WriteLine("添加用户成功");}
}
✅ 示例代码:调用 UserRepository
public class Program
{public static void Main(string[] args){var userRepo = new UserRepository();var newUser = new User { Id = 1, Name = "张三" };userRepo.Add(newUser);/* 日志打印结果添加前记录日志保存用户到数据库添加用户成功*/}
}// 假设 User 类如下:
public class User
{public int Id { get; set; }public string Name { get; set; }
}
🎯 实战目标
构建一个 基于接口 + 抽象类 + 泛型 的通用仓储:
-
支持常规操作(增删改查)
-
支持扩展方法(如分页、条件查询)
-
易于继承 & 复用
🧩 步骤一:定义接口 IRepository<T>
public interface IRepository<T> where T : class
{Task<T> GetByIdAsync(int id);Task<IEnumerable<T>> GetAllAsync();Task AddAsync(T entity);void Update(T entity);void Delete(T entity);
}
🧱 步骤二:实现抽象类 BaseRepository<T>
以 EF Core 为例,注入 DbContext
:
public abstract class BaseRepository<T> : IRepository<T> where T : class
{protected readonly DbContext _context;protected readonly DbSet<T> _dbSet;public BaseRepository(DbContext context){_context = context;_dbSet = _context.Set<T>();}public virtual async Task<T> GetByIdAsync(int id){return await _dbSet.FindAsync(id);}public virtual async Task<IEnumerable<T>> GetAllAsync(){return await _dbSet.ToListAsync();}public virtual async Task AddAsync(T entity){await _dbSet.AddAsync(entity);}public virtual void Update(T entity){_dbSet.Update(entity);}public virtual void Delete(T entity){_dbSet.Remove(entity);}
}
🧪 步骤三:创建具体仓储类 UserRepository
public class UserRepository : BaseRepository<User>
{public UserRepository(MyDbContext context) : base(context){}// 可扩展自定义方法public async Task<User?> GetByEmailAsync(string email){return await _dbSet.FirstOrDefaultAsync(u => u.Email == email);}
}
🧩 步骤四:在服务中使用
public class UserService
{private readonly UserRepository _userRepo;public UserService(UserRepository userRepo){_userRepo = userRepo;}public async Task RegisterUser(User user){await _userRepo.AddAsync(user);// 保存到数据库由 UnitOfWork 或 DbContext 控制}
}
✅ 什么时候用接口 vs 抽象类?
特性 | 接口(interface) | 抽象类(abstract class) |
---|---|---|
目的 | 定义行为规范 | 定义基本结构和部分实现 |
支持多继承 | ✅ 支持 | ❌ 不支持 |
可包含字段 | ❌ 不行 | ✅ 可以 |
可有构造函数 | ❌ 不行 | ✅ 可以 |
成员默认类型 | 抽象(abstract) | 可以是抽象,也可以有默认实现 |
是否可实例化 | ❌ 不行 | ❌ 不行 |
仅供学习参考,