什么是扩展方法
扩展方法是C# 3.0引入的一项强大功能,它允许开发者在不修改原始类定义、不创建派生类的情况下,”添加”方法到现有类型。这种技术特别适用于以下几种场景:
- 为密封类(sealed class)添加功能
- 为接口添加默认实现
- 扩展现有类型的功能而不需要重新编译原始程序集
- 创建更流畅的API接口
扩展方法的基本语法
定义扩展方法需要满足以下条件:
- 必须是静态类中的静态方法
- 第一个参数使用
this
关键字修饰,指定要扩展的类型 - 命名空间必须被使用代码引用
基本语法结构:
public static class ExtensionClass
{
public static ReturnType MethodName(this ExtendedType extendedObj, [其他参数])
{
// 方法实现
}
}
扩展方法示例
1. 基本值类型扩展
public static class IntExtensions
{
// 判断整数是否是偶数
public static bool IsEven(this int number)
{
return number % 2 == 0;
}
// 将整数转为指定范围的循环值
public static int Cycle(this int value, int min, int max)
{
if (min >= max) throw new ArgumentException("min must be less than max");
int range = max - min + 1;
if (value < min)
return max - (min - value - 1) % range;
return min + (value - min) % range;
}
}
使用示例:
int num = 10;
bool isEven = num.IsEven(); // true
int value = 15;
int cycled = value.Cycle(0, 10); // 5
2. 字符串扩展
public static class StringExtensions
{
// 将字符串转换为标题格式(每个单词首字母大写)
public static string ToTitleCase(this string str)
{
if (string.IsNullOrEmpty(str))
return str;
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str.ToLower());
}
// 检查字符串是否是有效的电子邮件格式
public static bool IsValidEmail(this string email)
{
try {
var addr = new System.Net.Mail.MailAddress(email);
return addr.Address == email;
}
catch {
return false;
}
}
// 截断字符串并在末尾添加省略号
public static string Truncate(this string value, int maxLength)
{
if (string.IsNullOrEmpty(value)) return value;
return value.Length <= maxLength ? value : value.Substring(0, maxLength) + "...";
}
}
使用示例:
string name = "john doe";
string title = name.ToTitleCase(); // "John Doe"
string email = "test@example.com";
bool isValid = email.IsValidEmail(); // true
string longText = "This is a very long text that needs to be truncated";
string shortText = longText.Truncate(20); // "This is a very lon..."
3. 集合扩展
public static class CollectionExtensions
{
// 判断集合是否为null或空
public static bool IsNullOrEmpty<T>(this IEnumerable<T> collection)
{
return collection == null || !collection.Any();
}
// 对集合中的每个元素执行指定操作
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
foreach (var item in source)
{
action(item);
}
}
// 将集合转换为分页形式
public static IEnumerable<IEnumerable<T>> Page<T>(this IEnumerable<T> source, int pageSize)
{
int pageCount = (int)Math.Ceiling(source.Count() / (double)pageSize);
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++)
{
yield return source.Skip(pageIndex * pageSize).Take(pageSize);
}
}
}
使用示例:
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
bool isEmpty = numbers.IsNullOrEmpty(); // false
numbers.ForEach(n => Console.WriteLine(n)); // 打印每个数字
var pages = numbers.Page(3);
foreach (var page in pages)
{
Console.WriteLine($"Page: {string.Join(",", page)}");
}
4. 日期时间扩展
public static class DateTimeExtensions
{
// 计算年龄
public static int CalculateAge(this DateTime birthDate)
{
var today = DateTime.Today;
var age = today.Year - birthDate.Year;
if (birthDate.Date > today.AddYears(-age)) age--;
return age;
}
// 获取某天的开始时间(00:00:00)
public static DateTime StartOfDay(this DateTime date)
{
return new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, 0);
}
// 获取某天的结束时间(23:59:59.999)
public static DateTime EndOfDay(this DateTime date)
{
return new DateTime(date.Year, date.Month, date.Day, 23, 59, 59, 999);
}
// 判断日期是否是工作日
public static bool IsWeekday(this DateTime date)
{
return date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday;
}
}
使用示例:
DateTime birthDate = new DateTime(1990, 5, 15);
int age = birthDate.CalculateAge(); // 计算年龄
DateTime now = DateTime.Now;
DateTime start = now.StartOfDay(); // 今天的开始时间
DateTime end = now.EndOfDay(); // 今天的结束时间
bool isWeekday = now.IsWeekday(); // 是否是工作日
高级扩展方法应用
1. 链式调用(Fluent API)
public static class FluentExtensions
{
public static StringBuilder AppendFormattedLine(this StringBuilder sb, string format, params object[] args)
{
sb.AppendFormat(format, args).AppendLine();
return sb;
}
public static T With<T>(this T obj, Action<T> action) where T : class
{
action?.Invoke(obj);
return obj;
}
public static TResult Map<TSource, TResult>(this TSource obj, Func<TSource, TResult> func)
{
return func(obj);
}
}
使用示例:
var sb = new StringBuilder();
sb.AppendFormattedLine("Hello, {0}!", "World")
.AppendFormattedLine("Today is {0}", DateTime.Now.DayOfWeek);
var person = new Person()
.With(p => p.Name = "John")
.With(p => p.Age = 30)
.With(p => p.Address = "123 Main St");
string name = "hello".Map(s => s.ToUpper()); // "HELLO"
2. 接口扩展
public interface ILogger
{
void Log(string message);
}
public static class LoggerExtensions
{
public static void LogError(this ILogger logger, string message)
{
logger.Log($"ERROR: {message}");
}
public static void LogWarning(this ILogger logger, string message)
{
logger.Log($"WARNING: {message}");
}
public static void LogInformation(this ILogger logger, string message)
{
logger.Log($"INFO: {message}");
}
}
使用示例:
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
var logger = new ConsoleLogger();
logger.LogInformation("Application started");
logger.LogWarning("Low disk space");
logger.LogError("File not found");
扩展方法的注意事项
- 命名冲突:当多个扩展方法同名时,编译器可能无法确定使用哪一个
- 优先级:实例方法的优先级高于扩展方法
- 可发现性:扩展方法不会出现在类的元数据中,需要通过命名空间导入
- 性能考虑:扩展方法调用有轻微的性能开销,但在大多数情况下可以忽略
- 过度使用:不应滥用扩展方法,特别是当普通方法更合适时
最佳实践
- 清晰的命名:扩展方法类名应以”Extensions”结尾,如
StringExtensions
- 合理的组织:按功能或类型组织扩展方法
- 文档注释:为扩展方法提供完整的XML文档注释
- 空值检查:在扩展方法内部进行null检查
- 避免污染:不要为基本类型添加过多不相关的扩展方法
- 单元测试:为扩展方法编写单元测试,确保其行为正确
实际应用案例
1. ASP.NET Core中的扩展方法应用
// 自定义中间件扩展
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
public static IServiceCollection AddCustomServices(this IServiceCollection services)
{
services.AddTransient<IMyService, MyService>();
return services;
}
}
// 在Startup.cs中使用
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddCustomServices();
}
public void Configure(IApplicationBuilder app)
{
app.UseRequestCulture();
}
}
2. Entity Framework Core扩展
public static class EfCoreExtensions
{
public static IQueryable<T> WhereIf<T>(this IQueryable<T> query, bool condition, Expression<Func<T, bool>> predicate)
{
return condition ? query.Where(predicate) : query;
}
public static async Task<PagedResult<T>> GetPagedAsync<T>(this IQueryable<T> query, int page, int pageSize)
{
var result = new PagedResult<T>
{
CurrentPage = page,
PageSize = pageSize,
RowCount = await query.CountAsync()
};
var pageCount = (double)result.RowCount / pageSize;
result.PageCount = (int)Math.Ceiling(pageCount);
var skip = (page - 1) * pageSize;
result.Results = await query.Skip(skip).Take(pageSize).ToListAsync();
return result;
}
}
// 使用示例
var pagedData = await dbContext.Products
.WhereIf(!string.IsNullOrEmpty(searchTerm), p => p.Name.Contains(searchTerm))
.GetPagedAsync(pageNumber, pageSize);
总结
C#扩展方法是一种强大的语言特性,它允许开发者以非侵入式的方式扩展现有类型的功能。通过合理使用扩展方法,我们可以:
- 增强代码的可读性和流畅性
- 为第三方库或系统类型添加实用功能
- 创建领域特定语言(DSL)
- 实现更优雅的API设计
- 提高代码的复用性和可维护性
然而,扩展方法也应谨慎使用,避免过度扩展导致代码难以理解和维护。遵循最佳实践,合理组织扩展方法,可以充分发挥这一特性的优势,提升开发效率和代码质量。