[C#1] 11-接口

接口与继承

CLR规定一个类型只能有一个基类型,这种继承成为单继承;

接口继承是指一个类型继承的是接口中的方法签名,而非方法实现,通常称为实现接口; 接口仅仅是含有一组虚方法的抽象类型,不含有任何实现。CLR允许接口包含静态方法、静态字段、常量、以及静态构造器, 但是CLS兼容的接口类型是不允许有任何静态成员的,因为一些编程语言不能定义或者访问它们。 C#语言就是如此,C#编译器不允许接口中有任何静态成员。

约定接口名称第一个字母是大写的I;接口可以多继承,实际上实现了多个接口的类型允许我们将它的对象看作这个接口中的任意一个。 就好比是一个人有很多能力,他会游泳[可以看多是运动员],他会编程[程序员]。 值类型也可以实现接口,当我们把一个值类型实例转为接口类型时,会被装箱,因为接口总被认为是引用类型,并且它们的方法总是虚方法。未装箱的值类型是没有方法表指针的,执行装箱将使CLR可以查询类型的方法表,便可以调用其虚方法了。

抽象类:is-a的关系;接口:can-do的关系。

使用接口改变已装箱值类型中的字段

public struct Location
{
    public int x, y;
 
    public void Change(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
    public override string ToString()
    {
        return string.Format("[{0}-{1}]", x.ToString(), y.ToString());
    }
}
public class Test
{
    static void Main()
    {
        Location l = new Location();
        l.x = l.y = 6;
        Console.WriteLine(l);//[6-6]
        l.Change(5, 5);
        Console.WriteLine(l);//[5-5]
        object o = l;
        Console.WriteLine(o);//[5-5]
 
        //o对Change方法一无所知,所以先转型为Location
        //然而这样会执行拆箱操作,在线程堆栈上生成一个
        //临时的Location,当改变它的字段时,原有的已装
        //箱的<o>则不受这样的影响
        ((Location)o).Change(9, 9);
        //[5-5]
        Console.WriteLine(o);
    }
}

上述代码我们无法改变以装箱的值类型中字段,我们可以用接口来欺骗C#使其改变值类型的字段[Location : IChangeBoxedLocation]:

 1 public interface IChangeBoxedLocation
 2 {
 3     void Change(int x, int y);
 4 }
 5 public class Test
 6 {
 7     static void Main()
 8     {
 9         Location l = new Location();
10         l.x = l.y = 6;
11         object o = l;
12         //转型为接口须装箱,改变已装箱的对象,最后丢弃改变
13         ((IChangeBoxedLocation)l).Change(5, 5);
14         Console.WriteLine(l);//[6-6]
15  
16         //因为o已经是引用类型,接口方法Change允许我们修改o中的字段
17         ((IChangeBoxedLocation)o).Change(9, 9);
18         Console.WriteLine(o);//[9-9]
19     }
20 }

我的通俗理解是:我是一个人[值类型Location],我有编程的能力[实现IChangeBoxedLocation接口], 所以在必要的时候你可以把我当作程序员来使用。

实现有多个相同方法的接口

 1 public interface IWindow
 2 {
 3     void Print();
 4 }
 5  
 6 public interface IConsole
 7 {
 8     void Print();
 9 }
10  
11 public class MyClass : IWindow, IConsole
12 {
13     void IWindow.Print()
14     {
15         //....
16     }
17     void IConsole.Print()
18     {
19         //....
20     }
21     public void Print()
22     {
23         //....
24     }
25 }

MyClass实现了多了Print方法,所以我们要告诉C#编译器我们的哪一个Print实现了哪个接口,C#中通过在方法名前面加上接口名来告诉C#编译器。public void Print()则是一个普通的方法而已,与接口没有任何关系【如果我们把void IWindow.Print()删除,则这个方法[public void Print()]将是实现IWindow接口的方法,C#编译器在辨析接口成员实现是,会按照先完全限定接口成员后非完全限定成员的顺序进行辨析】。

上面的两个完全限定接口方法没有声明为public,这是因为这些方法会有双重身份,有时共有[类型转型为该接口类型时:MyClass转为 IWindow或者 IConsole时],有时私有[MyClass实例时]。在一个类型中用完全限定名定义接口方法时,该方法被认为是私有的,因为类型本身无法调用它,当转型为一个接口时,这个方法将可以被调用,这时又是一个共有方法

显示接口成员实现

显示实现接口成员正是用到了上面的用完全限定名来实现接口。显示实现接口成员为应用程序提供了更多的类型安全。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏博客园

.NET Core中延迟单例另一种写法【.NET Core和.NET Framework的beforefieldinit差异】

   前段时间在反编译代码时无意间看到在类中有一个BeforeFieldInit特性,处于好奇的心态查了查这个特性,发现这是一个关于字段初始化时间的特性【提前初...

1854
来自专栏跟着阿笨一起玩NET

一道小小面试题的细节分析

581
来自专栏Road

Redis 设计 --- 高效数据结构实现剖析

即使有链表来处理键冲突,但是当节点数量远远大于 size 时,如果不扩充哈希表规模,请自行想象。这也是 rehash 的存在意义,笔者认为这也是 redis 扩...

1503
来自专栏jessetalks

由浅入深表达式树(一)创建表达式树

  为什么要学习表达式树?表达式树是将我们原来可以直接由代码编写的逻辑以表达式的方式存储在树状的结构里,从而可以在运行时去解析这个树,然后执行,实现动态的编辑和...

3284
来自专栏技术博客

C#泛型委托Predicate、Action、Func

Predicate泛型委托:表示定义一组条件并确定指定对象是否符合这些条件的方法。此委托由 Array 和 List 类的几种方法使用,用于在集合中搜索元素。 ...

1472
来自专栏技术博客

设计模式之三(工厂方法模式)

工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化那一个类。工厂方法使一个类的实例化延迟到其子类。

1672
来自专栏蘑菇先生的技术笔记

探索c#之递归APS和CPS

2987
来自专栏逸鹏说道

Python3 与 C# 基础语法对比(Function专栏)

汇总系列:https://www.cnblogs.com/dunitian/p/4822808.html#ai

883
来自专栏分布式系统和大数据处理

.Net中的反射(查看类型信息) - Part.2

简单来说,反射提供这样几个能力:1、查看和遍历类型(及其成员)的基本信息和程序集元数据(metadata);2、迟绑定(Late-Binding)方法和属性。3...

1233
来自专栏GreenLeaves

C# lambda表达式

学了N多久的委托,终于告一段落,现在可以开始lambda的学习之旅了,但是在说lambda之前必须先说下C#中的匿名方法. 1、匿名方法 下面是一个字符串拼接的...

2176

扫码关注云+社区

领取腾讯云代金券