CodeGym /课程 /C# SELF /.NET标准库里的接口

.NET标准库里的接口

C# SELF
第 24 级 , 课程 3
可用

1. 关键接口分类

如果你觉得接口只是架构师和“洁癖代码”爱玩的东西,实际开发可以不用,那我要让你大吃一惊:任何严肃的C#项目都离不开接口。为啥?因为.NET标准库几乎每个部分都是靠接口搭起来的!没有它们你连集合都用不了,文件都读不了,LINQ里都没法过滤集合。

接口是.NET里多态和可扩展性的基石,正是它们决定了整个框架的“积木”怎么拼起来。

.NET标准库里接口多到爆。为了不被淹没,我给你分个类(当然不全——全讲完得活几辈子!):

类别 接口 干啥用的?
集合
IEnumerable
,
IEnumerator
,
IList
,
ICollection
,
IDictionary
遍历、修改、按索引访问、键值对操作
资源操作
IDisposable
释放资源(文件、连接、流)
比较
IComparable
,
IComparer
,
IEquatable
排序、比较、对象唯一性
序列化
ISerializable
对象转字节流(和反过来)
LINQ和查询
IQueryable
,
IQueryProvider
支持复杂查询(比如查数据库)
异步
IAsyncEnumerable
,
IAsyncDisposable
异步遍历和资源清理
事件和通知
INotifyPropertyChanged
,
INotifyCollectionChanged
属性/集合变化时响应
日期和时间
IFormattable
自定义字符串格式化
数据结构
IStructuralComparable
,
IStructuralEquatable
集合和元组的深度比较
流/输入输出
IStream
,
IAsyncDisposable
,
IObserver
,
IObservable
流操作,push/pull通知

注意! 上面很多接口都有类型参数:List<string>。一般用在集合和集合接口里Int[] → List<int>。后面讲集合时会细说。

2. 集合接口

IEnumerableIEnumerator —— 你的foreach通行证

.NET里几乎每个集合都实现了IEnumerable或者它的泛型版IEnumerable<T>。正是这个接口让你能用神奇的foreach语法:

List<int> numbers = new List<int> { 1, 2, 3 };
foreach (int n in numbers) // 能用是因为List<int>实现了IEnumerable<int>
{
    Console.WriteLine(n);
}

接口本身长这样,极简版:

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

IEnumerator就是那个“遍历器”接口,专门负责在你的集合里溜达:

public interface IEnumerator
{
    bool MoveNext();
    object Current { get; }
    void Reset();
}

实际开发里你超级常用IEnumerable<T>做参数和返回值。比如返回数组里所有偶数的方法:

public IEnumerable<int> GetEvenNumbers(int[] array)
{
    foreach (var x in array)
        if (x % 2 == 0) yield return x;
}

对,yield关键字就是魔法——它帮你自动实现接口,后面会细讲。

ICollectionIList —— 支持索引和修改的集合

如果你不光要遍历,还想加/删元素,甚至按索引拿元素,就得用更专业的接口:

  • ICollection<T> —— 多了AddRemove方法,还有Count属性。
  • IList<T> —— 支持更多操作,包括按索引访问(this[int index])。
public void PrintFirstItem(IList<string> list)
{
    if (list.Count > 0)
        Console.WriteLine(list[0]);
}

List<T>同时实现了IEnumerable<T>ICollection<T>IList<T>。超方便——你既能当普通可遍历列表用,也能用它所有高级方法。

IDictionary<TKey, TValue> —— “键-值”对

喜欢用字典(其实大家都离不开!)就用IDictionary<TKey, TValue>接口。它保证你能通过key拿到value,反过来也行。

public void PrintAllPairs(IDictionary<string, int> ages)
{
    foreach (var pair in ages)
        Console.WriteLine($"{pair.Key}: {pair.Value}");
}

这里ages可以是Dictionary<string, int>,也可以是SortedDictionary<string, int>啥的——只要实现了这个接口就行!

3. IDisposable接口:正确释放资源

所有输入输出、文件操作、网络连接、数据库在.NET里都离不开IDisposable接口。它规定了一个超重要的约定:对象有非托管资源就必须“清理”用完的东西。对,就是它让using语法成为可能:

using (StreamReader reader = new StreamReader("file.txt"))
{
    // 用文件,出了using自动关掉!
    string line = reader.ReadLine();
}

接口本身其实超简单:

public interface IDisposable
{
    void Dispose();
}

但所有“正经”库都实现它!想看最佳实践,去官方文档

4. 比较和唯一性接口

IComparable<T>IComparer<T>

想让你的对象集合能排序,对象就得能互相比较。为此有IComparable<T>接口:

public class Student : IComparable<Student>
{
    public string Name { get; set; }
    public int Score { get; set; }

    public int CompareTo(Student? other)
    {
        // 按分数降序排
        if (other == null) return 1;
        return other.Score.CompareTo(this.Score);
    }
}

// 现在可以直接排序学生了:
var students = new List<Student> { ... };
students.Sort();

看微软文档了解更多

IComparer<T>让你在类外自定义比较——比如有时按名字排,有时按分数排:

public class NameComparer : IComparer<Student>
{
    public int Compare(Student? x, Student? y)
    {
        return string.Compare(x?.Name, y?.Name);
    }
}

IEquatable<T> —— 判断相等

想让你的对象能进HashSet<T>(也就是在集合里“唯一”)?实现IEquatable<T>

public class Person : IEquatable<Person>
{
    public string Name { get; set; }
    public bool Equals(Person? other)
    {
        if (other == null) return false;
        return this.Name == other.Name;
    }
}

5. 事件和通知接口

你有没有想过让程序“知道”对象属性变了?比如用户改了名字,界面自动刷新?这就是INotifyPropertyChanged接口的用武之地。它在图形界面程序(比如WPF、Xamarin)里超常用。

接口签名很简单:

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler? PropertyChanged;
}

实现例子:

public class User : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    private string name;
    public string Name
    {
        get => name;
        set
        {
            if (name != value)
            {
                name = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
            }
        }
    }
}

现在界面上所有绑定到这个对象的地方,Name一变就自动刷新。想深入了解——去官方文档

6. 其他有用接口

IFormattable接口:灵活格式化

想让你的对象能漂亮地输出成字符串(比如小数点后几位),就实现IFormattable接口:

public class Temperature : IFormattable
{
    public double Celsius { get; }
    public Temperature(double celsius) => Celsius = celsius;

    public string ToString(string? format, IFormatProvider? formatProvider)
    {
        // 不复杂,按格式显示'C'或'F'
        if (format == "F")
            return $"{Celsius * 9 / 5 + 32} F";
        return $"{Celsius} C";
    }
}

现在你可以temp.ToString("F", null)temp.ToString("C", null)。这就是为啥DateTimedoubledecimal等内置类型都能灵活格式化。

异步相关接口

C#有了异步后,异步世界也需要新接口。比如你的对象要异步释放资源——实现IAsyncDisposable

public interface IAsyncDisposable
{
    ValueTask DisposeAsync();
}

现在你可以用await using代替using

异步枚举(await foreach)用IAsyncEnumerable<T>,它让你不阻塞主线程地遍历元素。比如分块读大文件、处理网络数据流啥的。详细见第58关 :P

序列化接口:ISerializable

要把对象变成字节流保存/传输(比如发到网络),.NET有ISerializable接口。现在用得不多了,因为有更方便的机制(比如属性和现成的序列化器),但还是值得知道。接口签名如下:

public interface ISerializable
{
    void GetObjectData(SerializationInfo info, StreamingContext context);
}

7. 实战:接口在真实应用里的用法

假设你在写一个图书馆图书管理的控制台程序(别笑,总得有人写吧!)。你想能输出图书列表、排序、筛选、加载和保存数据。会用.NET接口你就能:

  • 通过IEnumerable<Book>返回各种集合类型,用户不用关心是数组还是列表。
  • 支持多种排序:实现IComparable<Book>按书名排,单独写IComparer<Book>按作者排。
  • 高效释放资源,比如文件操作用IDisposable
  • 用LINQ按类型或作者筛选图书,LINQ支持所有实现IEnumerable<T>的集合。
  • 让程序易扩展——比如以后把文件存储换成数据库,只要你的类用接口(IBookStorage)而不是具体实现就行。

8. 常见错误和注意点

新手最常犯的错之一——用具体实现(“List”)声明变量和方法参数,而不是接口(“IEnumerable”、“IList”)。记住:“面向接口编程”你的代码才灵活、好扩展。

有时候要注意选对接口层级——比如你写的方法只需要遍历功能,就用IEnumerable<T>,别用IList<T>,别给别人加不必要的限制。

还有个细节:如果类实现了很多接口,接口里有重名方法或属性,就得用显式接口实现(explicit implementation),不然编译器会懵逼。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION