扩展接口与泛型
一次为多种类型添加行为。
扩展接口与泛型 是 CoddyKit 上的免费 C# Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。
扩展接口
您可以编写一个扩展方法,使其 this 参数为接口。每个实现该接口的类型都会立即获得这个方法。这正是 LINQ 如何为 IEnumerable<T> 添加数十个方法的方式。
在可枚举序列上创建泛型扩展
通过让方法对 T 使用泛型,并扩展 IEnumerable<T>,这个辅助方法就可以适用于任何序列:列表、数组、查询结果等。
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static bool IsEmpty<T>(this IEnumerable<T> source)
{
foreach (var item in source) return false;
return true;
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 1, 2, 3 };
Console.WriteLine(nums.IsEmpty());
Console.WriteLine(new int[0].IsEmpty());
}
}迭代序列
在扩展内部,您可以像处理任何 IEnumerable<T> 一样处理该参数。这里我们不依赖 LINQ 来计算总和。
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static int SumValues(this IEnumerable<int> source)
{
int total = 0;
foreach (var n in source) total += n;
return total;
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 10, 20, 30 };
Console.WriteLine(nums.SumValues());
}
}返回新序列
扩展也可以返回序列。结合 yield return 后,它们就能成为采用 LINQ 风格的延迟执行运算符。
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static IEnumerable<T> EveryOther<T>(this IEnumerable<T> source)
{
bool take = true;
foreach (var item in source)
{
if (take) yield return item;
take = !take;
}
}
}
public class Program
{
public static void Main()
{
var letters = new List<string> { "a", "b", "c", "d", "e" };
foreach (var l in letters.EveryOther())
Console.WriteLine(l);
}
}泛型类型约束
您可以限制泛型参数,使其必须具备特定能力。这里的 where T : IComparable<T> 让我们能够比较元素以找出最大值。
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static T MaxItem<T>(this IEnumerable<T> source) where T : IComparable<T>
{
bool started = false;
T best = default;
foreach (var item in source)
{
if (!started || item.CompareTo(best) > 0) { best = item; started = true; }
}
return best;
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 3, 9, 1, 7 };
Console.WriteLine(nums.MaxItem());
}
}扩展自定义接口
您自己的接口同样可以受益。请先定义接口,然后通过扩展附加共享行为,而不是在每个实现类型中重复编写。
using System;
public interface INamed { string Name { get; } }
public class Dog : INamed { public string Name => "Rex"; }
public static class NamedExtensions
{
public static string Greeting(this INamed n) => "Hello, " + n.Name;
}
public class Program
{
public static void Main()
{
var d = new Dog();
Console.WriteLine(d.Greeting());
}
}多个类型参数
扩展可以使用多个类型参数实现泛型化。这个投影辅助方法使用一个函数映射序列,作用与 LINQ 的 Select 类似。
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static IEnumerable<TResult> Map<TSource, TResult>(
this IEnumerable<TSource> source, Func<TSource, TResult> f)
{
foreach (var item in source) yield return f(item);
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 1, 2, 3 };
foreach (var s in nums.Map(n => "#" + n))
Console.WriteLine(s);
}
}与内置 LINQ 结合
您自己的扩展方法可以与 LINQ 共存。您可以在一个流畅的管道中,将自定义运算符与标准运算符链接起来。
using System;
using System.Collections.Generic;
using System.Linq;
public static class EnumerableExtensions
{
public static IEnumerable<T> EveryOther<T>(this IEnumerable<T> source)
{
bool take = true;
foreach (var item in source) { if (take) yield return item; take = !take; }
}
}
public class Program
{
public static void Main()
{
var nums = Enumerable.Range(1, 10);
var result = nums.Where(n => n > 2).EveryOther().ToList();
Console.WriteLine(string.Join(", ", result));
}
}扩展 IEnumerable 可覆盖多种类型
由于数组、列表、字典、哈希集和查询结果都实现了 IEnumerable<T>,一个扩展就能同时覆盖它们。
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static int CountItems<T>(this IEnumerable<T> source)
{
int n = 0;
foreach (var item in source) n++;
return n;
}
}
public class Program
{
public static void Main()
{
Console.WriteLine(new[] { 1, 2, 3 }.CountItems());
Console.WriteLine(new HashSet<string> { "a", "b" }.CountItems());
}
}此模式为何易于扩展
接口扩展无需继承就能提供多态辅助方法。只需针对接口编写一次逻辑,所有当前和未来的实现类型都可以直接使用。
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static string JoinWith<T>(this IEnumerable<T> source, string sep)
{
return string.Join(sep, source);
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 1, 2, 3 };
Console.WriteLine(nums.JoinWith(" -> "));
}
}亲自尝试
请在 IEnumerable<T> 上构建一个小型泛型运算符,并将它与 LINQ 链接起来。注意它如何适用于任何序列类型。
using System;
using System.Collections.Generic;
using System.Linq;
public static class EnumerableExtensions
{
public static IEnumerable<T> WithoutDuplicatesByKey<T, TKey>(
this IEnumerable<T> source, Func<T, TKey> keySelector)
{
var seen = new HashSet<TKey>();
foreach (var item in source)
if (seen.Add(keySelector(item))) yield return item;
}
}
public class Program
{
public static void Main()
{
var words = new[] { "apple", "avocado", "banana", "cherry", "blueberry" };
var firstPerLetter = words.WithoutDuplicatesByKey(w => w[0]);
Console.WriteLine(string.Join(", ", firstPerLetter));
}
}快速检查
思考一下 LINQ 风格的扩展是如何声明的。
回顾
扩展接口,尤其是 IEnumerable<T>,是采用 LINQ 风格编程的基础。
- 使方法具有泛型,并将接口用作
this类型。 - 使用
yield return构建延迟执行的运算符。 - 应用
where约束,要求类型具备IComparable<T>等能力。 - 一个接口扩展可以覆盖所有实现类型,包括未来的实现类型。
用 AI 导师学习 C# — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 93
- 课程
- 346
常见问题解答
「扩展接口与泛型」课时是免费的吗?
是的 — 「扩展接口与泛型」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。
「扩展接口与泛型」这节课中我会学到什么?
一次为多种类型添加行为。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「扩展接口与泛型」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。