C#扩展方法应用


什么是扩展方法

扩展方法是C# 3.0引入的一项强大功能,它允许开发者在不修改原始类定义、不创建派生类的情况下,”添加”方法到现有类型。这种技术特别适用于以下几种场景:

  • 为密封类(sealed class)添加功能
  • 为接口添加默认实现
  • 扩展现有类型的功能而不需要重新编译原始程序集
  • 创建更流畅的API接口

扩展方法的基本语法

定义扩展方法需要满足以下条件:

  1. 必须是静态类中的静态方法
  2. 第一个参数使用this关键字修饰,指定要扩展的类型
  3. 命名空间必须被使用代码引用

基本语法结构:

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");

扩展方法的注意事项

  1. 命名冲突:当多个扩展方法同名时,编译器可能无法确定使用哪一个
  2. 优先级:实例方法的优先级高于扩展方法
  3. 可发现性:扩展方法不会出现在类的元数据中,需要通过命名空间导入
  4. 性能考虑:扩展方法调用有轻微的性能开销,但在大多数情况下可以忽略
  5. 过度使用:不应滥用扩展方法,特别是当普通方法更合适时

最佳实践

  1. 清晰的命名:扩展方法类名应以”Extensions”结尾,如StringExtensions
  2. 合理的组织:按功能或类型组织扩展方法
  3. 文档注释:为扩展方法提供完整的XML文档注释
  4. 空值检查:在扩展方法内部进行null检查
  5. 避免污染:不要为基本类型添加过多不相关的扩展方法
  6. 单元测试:为扩展方法编写单元测试,确保其行为正确

实际应用案例

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#扩展方法是一种强大的语言特性,它允许开发者以非侵入式的方式扩展现有类型的功能。通过合理使用扩展方法,我们可以:

  1. 增强代码的可读性和流畅性
  2. 为第三方库或系统类型添加实用功能
  3. 创建领域特定语言(DSL)
  4. 实现更优雅的API设计
  5. 提高代码的复用性和可维护性

然而,扩展方法也应谨慎使用,避免过度扩展导致代码难以理解和维护。遵循最佳实践,合理组织扩展方法,可以充分发挥这一特性的优势,提升开发效率和代码质量。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注