C#接口新语法详解

C#接口新语法详解

接口新语法概览

C#接口不仅仅可以含有普通的不带访问修饰符的方法签名了,还可以包含带默认实现的方法、私有方法等等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public interface IOverall
{
// 最普通的方法
void Foo();

// 属性
string Name { get; set; }

// 索引器
int this[int index] { get; set; }

// 事件
event EventHandler OnNameChanged;

// 带默认实现的方法
void Bar() => Console.WriteLine("Bar");

// 私有方法(需要带默认实现)
private void NonPublicMethod1()
{
}

// 受保护方法(需要带默认实现)
protected void NonPublicMethod2()
{
}

// 静态方法(需要带默认实现)
static void StaticMethod()
{
Console.WriteLine("StaticMethod");
}

// 抽象静态方法
static abstract void AbstractStaticMethod();

// 虚静态方法(需要带默认实现)
static virtual void VirtualStaticMethod()
{
Console.WriteLine("VirtualStaticMethod");
}
}

属性和事件

其实属性的本质是含有get和set的方法,但是接口中的属性和事件与类中的并不相同:

  • 接口中的属性和实现其实只是在声明属性的 getter/setter 以及事件的 add/remove
  • 类中的属性和事件则会后台生成对应的私有字段

索引器则类似:

  • 接口中只是在声明索引器的 getter/setter
  • 类中的索引器除非被标记为了 abstract,否则必须给出默认实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface IPropertyAndEvent
{
string Name { get; set; }

string this[string key] { get; set; }

event EventHandler? OnNameChanged;
}

public class ClassWithPropertyAndEvent
{
public string Name { get; set; } = "";

public string this[string key]
{
get => "";
set { }
}

public event EventHandler? OnNameChanged;
}

接口中默认实现和显示实现

在C# 8.0 中引入了接口中的默认实现,而实现了接口的类,可以对接口中的方法进行重写。

  1. 先定义两个接口,并给接口中的方法默认实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface IFoo1
{
void Foo()
{
Console.WriteLine($"这是接口{nameof(IFoo1)}中的一个包含默认实现的方法");
}

void Bar()
{
Console.WriteLine($"这是接口{nameof(IFoo1)}中的另一个包含默认实现的方法");
}
}

public interface IFoo2
{
void Foo()
{
Console.WriteLine($"这是接口{nameof(IFoo2)}中的一个包含默认实现的方法");
}
}
  1. 新建一个类,实现上面的接口,实现了接口后,该类不必实现接口中包含默认实现的方法。如果实现,那么接口中的默认实现将会直接被忽略
1
2
3
4
5
6
7
public class DemoClass1 : IFoo1, IFoo2
{
public void Bar()
{
Console.WriteLine($"这是{nameof(DemoClass1)}类对于接口中带有默认实现的方法的实现");
}
}
  1. 使用默认方法‘

    var demo = new DemoClass1();

    • 错误用法-demo.Foo()这样是无法调用Foo()的,因为DemoClass1中没有Foo方法的实现,必须这样正确用法-((IFoo1)demo).Foo();
    • demo.Bar()-结果为:这是DemoClass1类对于接口中带有默认实现的方法的实现
    • ((IFoo1)demo).Bar() -结果为:这是DemoClass1类对于接口中带有默认实现的方法的实现

可以发现由于类实现了接口中的方法,因此即便显式使用接口对象,也依旧会调用自己的实现,可以将接口中的默认实现看成带有virtual

非公开方法

非公开方法一般有两个用途:

  1. 不让类外面的代码使用
  2. 让类的公开方法来调用

在接口中使用非公开方法一般需要带有默认实现,因为其他地方也无法去实现这些公开方法。为什么要加上一般,那是因为方法还有partialextern

静态方法

接口中的静态方法是属于接口的,而不是属于实现了该接口的某个类的

1
2
3
4
5
6
7
8
9
10
11
interface IStaticMethod
{
static void StaticMethod()
{
Console.WriteLine("IStaticMethod.Foo");
}
}

class DemoClass : IStaticMethod
{
}

这样做事错误的DemoClass.StaticMethod();,必须使用IStaticMethod.StaticMethod();

从这个使用上可以看出,这种使用方式局限性很大。作为接口,你没有办法知道你的子类,所以无法实现特定的实现,所以一般是在泛型中使用静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//定义泛型接口,约束T必须实现当前接口
interface IDeserializable<T> where T : IDeserializable<T>
{
static T? Deserialize(string json) => JsonSerializer.Deserialize<T>(json);
}
//实现了该接口
class Student : IDeserializable<Student>
{
public int Id { get; set; }
public string Name { get; set; }

public override string ToString() => $"Id: {Id}, Name: {Name}";
}
var student = IDeserializable<Student>.Deserialize("{\"Id\":42,\"Name\":\"Tom\"}");

抽象静态方法

静态方法因为是属于接口的方法,局限性很高,所以引出了抽象静态方法。

接口中的方法本身就类似于抽象类中的抽象方法,要求子类必须实现

接口中允许存在静态方法,而静态方法又必须在实现的类中给出实现,所以如果希望接口中存在可以不在接口中实现的静态方法,就需要声明为抽象方法

1
2
3
4
5
6
7
8
9
10
11
12
interface IAbstractStaticMethod
{
static abstract string Foo();
}

class DemoClass : IAbstractStaticMethod
{
public static string Foo()
{
return nameof(DemoClass);
}
}

调用DemoClass.Foo();

经典用法1 单例:

1
2
3
4
5
6
7
8
9
10
interface ISingleton<T> where T : ISingleton<T>
{
static abstract T Instance { get; }
}

class SingletonClass : ISingleton<SingletonClass>
{
private static readonly Lazy<SingletonClass> _instanceHolder = new(() => new SingletonClass());
public static SingletonClass Instance => _instanceHolder.Value;
}

经典用法2 操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface IOperators<T> where T : IOperators<T>
{
static abstract T operator +(T left, T right);
static abstract T operator -(T left, T right);
}

class MyNumber : IOperators<MyNumber>
{
public int Value { get; }

public MyNumber(int value)
{
Value = value;
}

public static MyNumber operator +(MyNumber left, MyNumber right)
{
return new MyNumber(left.Value + right.Value);
}

public static MyNumber operator -(MyNumber left, MyNumber right)
{
return new MyNumber(left.Value - right.Value);
}
}

经典用法3 工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface IFactory<T>
{
static abstract T Create();
}
class ClassWithFactoryMethod : IFactory<ClassToBeCreated>
{
private ClassWithFactoryMethod()
{
}
public static ClassToBeCreated Create()
{
return new ClassToBeCreated();
}
}

静态虚方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface IVirtualStaticMethod
{
/// <summary>
/// 一个接口中的静态虚方法(带有默认实现)
/// </summary>
static virtual void Foo()
{
Console.WriteLine("Foo from interface");
}
}

class DemoClass : IVirtualStaticMethod
{
public static void Foo() // 无法使用 override 关键字
{
Console.WriteLine("Foo from class");
}
}
//调用
DemoClass.Foo();

//错误,接口的静态虚方法无法直接通过接口来调用
IVirtualStaticMethod.Foo()//错误用法

这样看感觉静态虚方法好像并没有什么实际用处,仅仅是给出建议,并不强求实现接口的类必须实现。

其实,虽然接口静态虚方法无法直接通过接口.来获取,但可以通过泛型标记T来获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface ITestInterface
{
static virtual string TestString1()
{
return "接口中的方法";
}
}
public interface ITestInterfaceGeneric<T> where T: ITestInterfaceGeneric<T>
{
static virtual string TestString2()
{
return "泛型接口中的方法2";
}
//这样就可以调用到静态虚方法
static void TestCallGeneric()
{
Console.WriteLine(T.TestString2());
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

public class A: ITestInterface,ITestInterfaceGeneric<A>
{
public static string TestString2()
{
return "类A中的方法2";
}
}
public class B : ITestInterface, ITestInterfaceGeneric<B>
{
public static string TestString1()
{
return "类B中的方法1";
}
}
static void TestCallInstance<T>(T t) where T : ITestInterface
{
//这样也可以调用到静态虚方法
Console.WriteLine(T.TestString1());
}

依次调用

1
2
3
4
TestCallInstance(new A());
TestCallInstance(new B());
ITestInterfaceGeneric<A>.TestCallGeneric();
ITestInterfaceGeneric<B>.TestCallGeneric();

image-20240504123928223

作者

步步为营

发布于

2024-05-04

更新于

2025-03-15

许可协议