接口新语法概览
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 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 2 3 4 5 6 7
| public class DemoClass1 : IFoo1, IFoo2 { public void Bar() { Console.WriteLine($"这是{nameof(DemoClass1)}类对于接口中带有默认实现的方法的实现"); } }
|
使用默认方法‘
var demo = new DemoClass1();
错误用法-demo.Foo()
这样是无法调用Foo()的,因为DemoClass1中没有Foo方法的实现,必须这样正确用法-((IFoo1)demo).Foo();
。demo.Bar()
-结果为:这是DemoClass1类对于接口中带有默认实现的方法的实现((IFoo1)demo).Bar()
-结果为:这是DemoClass1类对于接口中带有默认实现的方法的实现
可以发现由于类实现了接口中的方法,因此即便显式使用接口对象,也依旧会调用自己的实现,可以将接口中的默认实现看成带有virtual
非公开方法
非公开方法一般有两个用途:
- 不让类外面的代码使用
- 让类的公开方法来调用
在接口中使用非公开方法一般需要带有默认实现,因为其他地方也无法去实现这些公开方法。为什么要加上一般,那是因为方法还有partial
和extern
;
静态方法
接口中的静态方法是属于接口的,而不是属于实现了该接口的某个类的
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
| 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 { static virtual void Foo() { Console.WriteLine("Foo from interface"); } }
class DemoClass : IVirtualStaticMethod { public static void Foo() { 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();
|
