C#中的接口(interface)

2023-11-15

接口的命名规范

I+名词

接口与抽象类的区别

接口是由抽象类演变而来的。

抽象类是未完全实现逻辑的类,其内部可以有抽象成员,也可以有非抽象成员;且子类在覆写抽象成员时,需要修饰符override。

而接口是完全未实现逻辑的,其内部只允许存在抽象成员而不能包含非抽象成员,即不能包含数据成员和静态成员;且成员是隐式默认public和absolute的,不允许显式地写出来;子类在实现接口的方法时,不需要修饰符override。

接口声明只能包含如下类型的非静态成员函数的声明:

方法、属性、事件、索引器

实现接口

只有类和结构才能实现接口,在实现时应满足:

  • 在基类列表中包括接口名称。
  • 为每一个接口成员提供实现。

一个类可以同时继承一个基类和多个接口,不过在基类列表中基类名称应放置在接口名称之前。

如果在一个类实现的多个接口中,存在两个或多个接口的成员具有相同的签名和返回值类型,那么类可以实现单个成员来满足所有包含重复成员的接口。

接口的作用

让一个方法可以接受各种类型的对象,并且不管该类有什么样的结构。

比如,存在一个方法PrintIHuman(),如果要让其接受Student和Programmer这两个不同的类的对象,就需要让这两个类继承同一个接口:

namespace TestConsole
{
    //声明接口
    interface IHuman
    {
        void Action();
    }
    //声明Student类,其需要实现接口IHuman的方法Action()
    class Student : IHuman
    {
        public void Action()
        {
            Console.WriteLine("The student are studying...");
        }
    }
	//声明Programmer类,其与Student类有着不同的结构,实现Action()方法的方式也不相同
    class Programmer : IHuman
    {
        public String Name { get; set; }
        public void Action()
        {
            Console.WriteLine(Name + " is writing code...");
        }
    }
    //测试:
    class Program
    {
        static void PrintIHuman(IHuman ih)
        {
            ih.Action();
        }
        static void Main(string[] args)
        {
            Student stu = new Student();
            PrintIHuman(stu);

            Programmer prom = new Programmer();
            prom.Name = "Ivan";
            PrintIHuman(prom);
        }
    }
}

运行结果:

The student are studying…
Ivan is writing code…

我们可以这样理解:接口就是一种契约,有些方法只接收遵循某种契约的参数,以上面的代码为例,IHuman接口是一种契约,而PrintIHuman()这个方法只接收遵循IHuman这个契约的参数,当Student和Programmer这两个类都遵循IHuman这个契约时,就可以使用PrintIHuman()方法了。

可以认为,接口是为解耦而生的。例如,当一个方法需要某种契约的参数时,而此时遵守这个契约的类出了bug,在修复期间,方法就可以先去使用其他遵守这个契约的类;如果没有接口的话,这个方法和这个类之间的耦合度就很大了,当类出了问题,方法也会随之瘫痪,加大了维护难度和成本。

使用系统提供的接口

比如Array的sort方法,能对数组进行排序,但如果是我们自己写的类,想让sort方法对我们的类对象根据成员值大小进行排序,那sort是做不到的,除非我们的类实现了 IComparable 接口。

namespace TestConsole
{
    //实现接口:IComparable,要实现其方法:CompareTo
    class MyTest : IComparable
    {
        public int MyVar { get; set; }
        public int CompareTo(object obj)
        {
            MyTest mt = (MyTest)obj;
            if (this.MyVar < mt.MyVar) return -1;
            if (this.MyVar > mt.MyVar) return 1;
            return 0;
        }
    }  
    
    class Program
    {
        //打印数组元素
        static void printArray(MyTest[] mt)
        {
            foreach (var item in mt)
            {
                Console.Write(item.MyVar+" ");
            }
            Console.WriteLine("");
        }
        //测试
        static void Main(string[] args)
        {
            var myInt = new[] { 20, 4, 47, 6, 1 };
            MyTest[] amt = new MyTest[5];
            for (int i = 0; i < 5; i++)
            {
                amt[i] = new MyTest();
                amt[i].MyVar = myInt[i];
            }
            Console.WriteLine("排序前:");
            printArray(amt);
            Array.Sort(amt);
            Console.WriteLine("排序后:");
            printArray(amt);
        }
    } 
}

运行结果:

排序前:
20 4 47 6 1
排序后:
1 4 6 20 47

is和as运算符

接口是引用类型,可以通过强制转换运算符来获取对象接口的引用,但这种操作会抛出异常,我们可以使用as运算符来解决这一问题。

is运算符则用来判断某个对象是否为某个类型。

Student stu = new Student(); //“Student”是实现了接口“IHuman”的类
IHuman ih = stu as IHuman; //类型转换
if (ih is null) //判断对象ih是否为空
{
    Console.WriteLine("ih is null");
}
if (ih is IHuman) //判断对象ih是否为IHuman类型
{
    Console.WriteLine("ih is IHuman");
}
if (ih is Student) //判断对象ih是否为Student类型
{
    Console.WriteLine("ih is Student");
}

运行结果:

ih is IHuman
ih is Student

多个接口的独立引用

如果一个类实现了多个接口,我们可以获取每一个接口的独立引用

namespace TestConsole
{
    interface IStone
    {
        String Color { get; set; }
    }
    interface IMonkey
    {
        void Action();
    }
    
    class StoneMonkey : IStone, IMonkey
    {
        public String Color { get; set; }

        public void Action()
        {
            Console.WriteLine("I can subdue demons.");
        }
    }
    //测试:
    class Program
    {
        static void Main(string[] args)
        {
            StoneMonkey sunWuKong=new StoneMonkey();
            IMonkey falseMonkey = sunWuKong as IMonkey; 
            //falseMonkey独立引用了sunWuKong的IMonkey接口
            //falseMonkey可以使用Action()方法,但无法像sunWuKong一样使用Color属性
            falseMonkey.Action();
            sunWuKong.Color = "golden";
            sunWuKong..Action();
        }
    }
}

显式接口成员实现

前面提到过,单个类的单个方法可以满足多个接口的重复成员,但如果想要为每一个接口分离实现,则需要显式实现接口成员。

显示接口成员实现:使用限定接口名称来声明,由接口名称成员名称以及它们中间的点分隔符号构成。此时public是隐式的,不能显式写出。

namespace TestConsole
{
    interface ITest1
    {
        void Action();
    }
    interface ITest2
    {
        void Action();
    }
    
    class MyClass : ITest1, ITest2
    {
        void ITest1.Action()
        {
            Console.WriteLine("1号接口");
        }

        void ITest2.Action()
        {
            Console.WriteLine("2号接口");
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            MyClass mc = new MyClass(); //MyClass对象mc无法直接调用Action方法
            //可以通过类型转换来调用:
            ((ITest1)mc).Action();
            //或者用接口类型创建的MyClass对象来调用:
            ITest2 it2 = new MyClass();
            it2.Action();
        }
    }
}

运行结果:

1号接口
2号接口

注意,以上的“Action()”不是类级别的方法,所以MyClass类的对象mc无法直接调用它。不过,存在显式接口成员实现时,类级别的实现是允许的,即MyClass类可以同时包含显式接口成员实现和类级别的实现:

class MyClass : ITest1, ITest2
{
    //显式接口成员实现
    void ITest1.Action()
    {
        Console.WriteLine("1号接口");
    }
    void ITest2.Action()
    {
        Console.WriteLine("2号接口");
    }
    //类级别的实现
    public void Action()
    {
        Console.WriteLine("类级别的实现");
    }
}
    
class Program
{
    static void Main(string[] args)
    {
        MyClass mc = new MyClass();
        mc.Action();

        ITest1 it1 = new MyClass();
        it1.Action();

        ITest2 it2 = new MyClass();
        it2.Action();
    }
}

运行结果:

类级别的实现
1号接口
2号接口

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C#中的接口(interface) 的相关文章