C# Academy · 课时

设计优质扩展

应用最佳实践并避开常见问题。

第 4 / 4 课13 个步骤

设计优质扩展 是 CoddyKit 上的免费 C# Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。

设计令人喜爱的扩展

扩展方法功能强大,但强大的功能也容易被滥用。优秀的扩展用起来像类型本身自然具备的功能,易于发现,并且绝不会让调用者感到意外。本课将介绍实用的指导原则。

扩展行为,而非身份

对于在管道中读起来顺畅的实用行为,优先使用扩展。如果某个方法确实属于您拥有的类型,就应将它实现为真正的成员。

using System;

public static class TimeExtensions
{
    // A pleasant, focused helper
    public static bool IsWeekend(this DayOfWeek day)
        => day == DayOfWeek.Saturday || day == DayOfWeek.Sunday;
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(DayOfWeek.Sunday.IsWeekend());
        Console.WriteLine(DayOfWeek.Monday.IsWeekend());
    }
}

尽可能保持方法纯粹

返回新值且避免副作用的扩展最容易理解和链接。请尽量实现输入值、输出值的形式。

using System;

public static class StringExtensions
{
    // Pure: depends only on input, returns a new string
    public static string Truncate(this string s, int max)
        => s.Length <= max ? s : s.Substring(0, max) + "...";
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Extension methods are great".Truncate(10));
    }
}

防范 null 输入

由于调用者可以在 null 接收对象上调用扩展,请有意决定应如何处理这种情况。您可以平稳地容忍 null,也可以抛出清晰的 ArgumentNullException。

using System;

public static class StringExtensions
{
    public static string SafeUpper(this string s)
    {
        if (s is null) throw new ArgumentNullException(nameof(s));
        return s.ToUpper();
    }
}

public class Program
{
    public static void Main()
    {
        try { ((string)null).SafeUpper(); }
        catch (ArgumentNullException ex) { Console.WriteLine("Caught: " + ex.ParamName); }
        Console.WriteLine("ok".SafeUpper());
    }
}

以便于发现为目标命名

开发人员通常通过输入句点并查看 IntelliSense 来查找扩展。清晰的、以动词为基础且能描述结果的名称,可以让您的辅助方法更容易被发现,也能做到自我说明。

using System;
using System.Collections.Generic;

public static class CollectionExtensions
{
    public static bool HasItems<T>(this ICollection<T> c)
        => c != null && c.Count > 0;
}

public class Program
{
    public static void Main()
    {
        var list = new List<int> { 1 };
        Console.WriteLine(list.HasItems());
    }
}

将相关扩展统一分组

请将同一概念的扩展放在一个命名良好的静态类和命名空间中。这样可以让导入更有意义,也让用户能够准确引入所需的辅助方法。

using System;
using System.Collections.Generic;

public static class EnumerableExtensions
{
    public static IEnumerable<T> TakeFirst<T>(this IEnumerable<T> source, int n)
    {
        int taken = 0;
        foreach (var item in source)
        {
            if (taken++ >= n) yield break;
            yield return item;
        }
    }
}

public class Program
{
    public static void Main()
    {
        var nums = new List<int> { 1, 2, 3, 4 };
        Console.WriteLine(string.Join(",", nums.TakeFirst(2)));
    }
}

不要隐藏令人意外的行为

一个看起来开销很小、实际上却执行昂贵操作或有状态操作的扩展,会违背使用者的预期。请让开销显而易见;如果它查询数据库或修改状态,就应在名称中体现出来。

using System;

public static class MathExtensions
{
    // Cheap and obvious: no hidden cost
    public static int ClampMin(this int value, int min)
        => value < min ? min : value;
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine((-3).ClampMin(0));
        Console.WriteLine(5.ClampMin(0));
    }
}

避免扩展 object

扩展 object 会让您的方法污染程序中的每一种类型,使 IntelliSense 到处变得杂乱。请改为针对合理的最具体类型或接口。

using System;

public static class IntExtensions
{
    // Specific to int, not object - far less noisy
    public static string ToOrdinal(this int n)
    {
        if (n % 10 == 1 && n % 100 != 11) return n + "st";
        if (n % 10 == 2 && n % 100 != 12) return n + "nd";
        if (n % 10 == 3 && n % 100 != 13) return n + "rd";
        return n + "th";
    }
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(1.ToOrdinal());
        Console.WriteLine(22.ToOrdinal());
    }
}

序列优先返回 IEnumerable

对于序列运算符,请返回 IEnumerable<T> 并使用延迟执行。这样便于组合,也可以避免强迫调用者立即生成可能并不需要的数据。

using System;
using System.Collections.Generic;

public static class SeqExtensions
{
    public static IEnumerable<int> Doubled(this IEnumerable<int> source)
    {
        foreach (var n in source) yield return n * 2;
    }
}

public class Program
{
    public static void Main()
    {
        var nums = new List<int> { 1, 2, 3 };
        foreach (var n in nums.Doubled()) Console.WriteLine(n);
    }
}

优秀扩展的检查清单

发布扩展前,请问自己:名称是否清晰?它是否是纯操作,或者副作用是否显而易见?是否处理了 null?目标类型是否足够具体?是否进行了合理分组?如果所有答案都是肯定的,您就拥有了其他人会乐于使用的辅助方法。

using System;

public static class StringExtensions
{
    public static string OrDefault(this string s, string fallback)
        => string.IsNullOrEmpty(s) ? fallback : s;
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine("".OrDefault("N/A"));
        Console.WriteLine("value".OrDefault("N/A"));
    }
}

亲自尝试

应用这份检查清单:指定具体的目标类型,使用清晰的名称,容忍 null,并返回纯结果。这个小型扩展符合每一项指导原则。

using System;
using System.Collections.Generic;

public static class DictionaryExtensions
{
    // Clear name, specific type, null-safe, pure
    public static TValue GetOrDefault<TKey, TValue>(
        this IReadOnlyDictionary<TKey, TValue> dict, TKey key, TValue fallback)
    {
        if (dict == null) return fallback;
        return dict.TryGetValue(key, out var value) ? value : fallback;
    }
}

public class Program
{
    public static void Main()
    {
        var ages = new Dictionary<string, int> { ["Ann"] = 30 };
        Console.WriteLine(ages.GetOrDefault("Ann", -1));
        Console.WriteLine(ages.GetOrDefault("Bob", -1));
    }
}

快速检查

应用设计指导原则。

回顾

设计良好的扩展用起来自然且可预测。

  • 优先使用纯的、返回值且便于链接的辅助方法。
  • 决定并记录对 null 的处理方式。
  • 使用清晰的、以动词为基础的名称,以便于发现。
  • 针对具体类型或接口,而不是 object。
  • 将相关辅助方法分组,并为序列返回 IEnumerable<T>。
免费开始

用 AI 导师学习 C# — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
93
课程
346

常见问题解答

「设计优质扩展」课时是免费的吗?

是的 — 「设计优质扩展」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。

「设计优质扩展」这节课中我会学到什么?

应用最佳实践并避开常见问题。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 C# Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「设计优质扩展」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 C# Academy 课中编写并运行代码吗?

能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 定义扩展方法
  2. 扩展接口与泛型
  3. 扩展方法解析
  4. 设计优质扩展
← 返回 C# Academy