C#如何面向对象

在这篇文章中我们将要讨论一些面向对象在C#中的具体实践的关键概念。我们将要讨论一下面向对象的基础包括接口(Interface)、访问修饰符(Access Modifier)、继承(Inheritance)、多态(Polymorphism)等等。

面向对象的关键概念
    抽象(Abstraction)
    封装(Encapsulation)
    多态(Polymorphism)
    继承(Inheritance)



抽象


    抽象是一种将一个对象归纳为一个数据类型的能力,这个数据类型具有特定的一组特征(Characteristic)和能够执行的一组行为(Action)。

    面向对象的(Object-oriented)语言通过类(Class)来提供抽象。类为一个对象(Object)的类型定义了属性(Porperty)和方法(Method)。

    例如:
  • 你可以为狗狗建立一个抽象,它具有一些特征,如颜色、身高[译注:狗狗也要记录身高?]和重量;还有一些行为,如跑和咬。我们称这些特征为属性,称这些行为为方法。
  • 一个记录集(Recoredset)对象是对一组数据的集合的抽象表示。


类是对象的蓝图。
对象是类的实例(Instance)。

C#中类的例子:

public class Draw {
    // 类代码
}

对象引用

    当我们在操作一个对象时,我们需要使用对该对象的一个引用(Reference)。相反,当我们操作简单数据类型如整型时,我们操作的是实际的值而不是引用。

    当我们使用new关键字建立一个新的对象时,我们便将该对象的一个引用存贮在了一个变量中。看下面的例子:

Draw MyDraw = new Draw();

    这段代码建立了Draw类的一个新的实例。我们通过MyDraw变量来获得对这个新对象的访问。这个变量保存了对这个对象的引用。

    现在我们还有另外一个变量,它具有对同一个对象的引用。我们可以随意使用这两个变量,因为他们都引用了同一个对象。我们需要记得的是我们的这个变量并不是对象本身,它只是对对象本身的一个引用或指针。

    早绑定(Early binding)是指我们的代码通过直接调用对象的方法来直接与对象进行交互。由于编译器事先知道对象的数据类型,它可以直接编译出调用对象方法的代码。早绑定还允许IDE通过使用智能感知(IntelliSense)来帮助我们进行开发工作;它还能够使编译器确保我们所调用的方法确实存在,以及我们确实提供了恰当的参数值。

    迟绑定(Late Binding)是指我们的代码在运行时动态地与对象进行交互。这提供了很大的灵活性,因为我们的代码不必知道它所交互的对象的具体类型,只要这个对象提供了我们需要调用的方法就可以了。由于IDE和编译器无法知道对象的具体类型,也就无法进行智能感知和编译期间语法检查;但是,相较而言,我们却得到了空前的灵活性。

    如果我们通过在我们的代码模块的顶部指定“Option Strict On”[译注:指的是Visual Basic .NET]来打开严格类型检查,则IDE和编译器将强制进行早绑定。默认的情况下,严格类型检查被设置为关闭状态,所以我们可以在我们的代码中使用迟绑定。

访问修饰符

    访问修饰符是一组关键字,用于指定在一个类型中声明的成员的可访问性。

    公有(Public)成员对于任何人都是可见的。我们可以在类内部的和类的子孙的代码中通过使用类的实例来访问一个公有的成员。

    私有(Private)成员是隐藏的,只有对类本身是可用的。所有使用了一个类的实例的代码都不能直接访问一个私有成员,这个类的子类也不允许。

    受保护(Protected)成员和私有成员类似,只能由包含它的类访问。然而,受保护成员可以由一个类的子类所使用。如果类中的一个成员可能被该类的子类访问,它应该声明未受保护的。

    内部/友元(Internal/Friend)成员对整个应用程序是公有的,但对于其他的外部应用程序是私有的。当我们希望其他应用程序能够使用当前应用程序中的包含的一个类,但又希望这个类为当前应用程序保留一定的功能时,就要用到内部/友元成员。在C#中我们使用internal,而在Visual Basic .NET中我们使用Friend。

    受保护内部(Protected Internal)成员只能由包含了基类的应用程序中的从该基类派生的子类所访问。当你希望一个类中的成员只能由其子类访问,并且拒绝其他应用程序访问该类的这个成员的时候,你就要将其声明未受保护内部成员。


对象的成分

    我们使用接口来获得对对象的数据和行为的访问。对象的数据和行为包含在对象内部,因此,一个客户应用程序可以将对象视为黑盒,只有通过它的接口才能获得可访问性。这是面向对象的关键概念,称为封装。这意味着任何使用这个对象的应用程序不能直接访问它的行为和数据——必须使用对象的接口。

    对象有三个主要部分:
  • 接口(Interface)
  • 实现(Implementation)或行为(Behavior)
  • 成员(Member)或实例变量(Instance variable)


    接口

    接口定义了一组方法(Method,子程序或函数例程[译注:指Visual Basic .NET中的Sub和Function])、属性(Property)、事件(Event)和域(Field,变量或特性),这些都被声明为公有。

    实现或行为

    一个方法之内的代码称为实现。有的时候由于这些代码可以使对象作一些有用的工作,而称之为行为。

    尽管我们改变了实现,客户程序仍然可以使用我们的对象——只要我们没有改变接口。只要我们的方法的名字和它的参数列表以及返回值类型没有改变,我们可以随意地改变它的实现。因此方法签名(Method Signature)取决于:
  • 方法名称
  • 参数的数据类型
  • 参数是按值传递还是按引用传递[译注:Visual Basic .NET中的ByVal或ByRef]
  • [译注:还有参数的出现顺序]


    很重要的需要我们紧记的是,封装只是一个语法工具——它允许我们现有的代码可以无需修改而继续运行。然而,这不是语义的——这意味着尽管我们现有的代码可以运行,但它不一定继续作我们希望它做的工作。

    成员或实例变量

    一个对象的第三个关键部分是数据(Data),或状态(State)。一个类的每一个实例都具有绝对相同的接口和实现——唯一可以不同的就是特定对象中所包含的数据。

    成员变量正是为此而声明,它对于我们的类总的所有的代码都是可用的。典型的成员变量通常被声明为私有的——只有对我们的类本身的代码有效。它们有时也被称作实例变量或特性。.NET Framework也称它们为域。

    我们不要将实例变量与属性相混淆。属性是一种特殊的方法,用来获取或设置数据;而实例变量是类中的一个变量,用来保存被属性所暴露的数据。

    接口看起来像一个类,但没有实现。其中只包含事件、索引器(Indexer)、方法和/或属性的定义。接口中只提供定义的原因是因为它们必须被类和结构继承,这些类和结构必须对接口中定义的每个成员提供实现。那么,没有实现任何功能的接口有什么好处呢?他们可以装配出一个“即插即用”的架构,其中的所有组件可以随意替换!由于所有可替换的组件都实现了相同的接口,因此可以不用任何扩展程序就可以使用它们。接口强制每个组件暴露用于特定途径的公有成员。

    因为接口必须有继承的类和结构所定义,因此它们定义了一个契约。对于实例,如果类foo是从接口IDisposable继承而来的,这将形成一个条款,确保它具有Dispose()方法,这是IDisposable接口中唯一的一个成员。所有希望使用foo类的代码都会检查foo类是否继承自IDisposable接口。如果答案为真,代码就知道了它可以调用foo.Dispose()。

    定义一个接口:MyInterface.cs

interface ImyInterface {
    void MethodToImplement();
}

    上面的代码定义了一个名为IMyInterface的接口。一个通用的命名约定是在所有的接口名字前面添加前缀字母“I”,但这不是强制的。这个接口有一个单独的名为MethodToImplement()的方法。接口可以拥有任何类型的、带有不同参数和不同返回值类型 的方法。注意这个方法没有实现(花括号“{}”之间的指令),而是直接以分号“;”结束。这是因为接口仅指定方法的签名而必须由继承它的类或结构去实现。

    接口中的所有方法都默认为公有的,而且对于任何方法或接口[译注:指的是接口中声明的嵌套接口]不允许出现访问修饰符(如private、public)。

    使用一个接口:InterfaceImplementer.cs

class InterfaceImplementer : IMyInterface {
    public void MethodToImplement() {
        Console.WriteLine(“MethodToImplement() called.”);
    }
}

    上面代码中的InterfaceImplement类实现了IMyInterface接口。指定一个类继承了一个接口和指定它继承了一个类是一样的,都用到了下面的语法:

class InterfaceImplementer : IMyInterface

    注意,既然这个类继承了IMyInterface接口,它就必须实现所有的成员。当实现接口中的方法时,所有这些方法都必须且仅能声明为公有的。这个类通过实现MethodToImplement()方法来完成这些。注意这个方法和接口中定义的方法具有完全相同的签名、参数和方法名称。任何的不一致都会产生编译错误。接口同样还可以继承自其它接口。下面的代码显示了如何实现继承的接口。

    接口继承:InterfaceInheritance.cs

using System;

interface IParentInterface {
    void ParentInterfaceMethod();
}

interface IMyInterface : IParentInterface {
    void MythodToImplement();
}

class InterfaceIplementer : IMyInterface {
    public void MethodToImplement() {
        Console.WriteLine(“MethodToImplement() called.”);
    }

    public void ParentInterfaceMethod() {
        Console.WriteLine(“ParentInterfaceMethod() called.”);
    }
}

    上面的代码中有两个接口:IMyInterface接口和它所继承的IParentInterface接口。当一个接口继承了另一个,实现它的类或结构必须实现整个接口继承链中所有的接口成员。由于上面代码中的InterfaceImplementer类继承了IMyInterface,它同时也继承了IParentInterface。因此,InterfaceImplementer类必须实现IMyInterface接口中指定的MethodToImplement()方法和IParentInterface接口中指定的ParentInterfaceMethod()方法。

    总的来说,你可以实现一个接口并在类中使用它。接口也可以被另一个接口继承。任何继承了一个接口的类或结构都必须实现接口继承链中所有接口所定义的成员。



继承


    继承是指一个类——称为子类[译注:亦称派生类],可以基于另一个类——称为基类[译注:亦称父类、超类]。继承提供了一种建立对象层次的机制。

    继承使得你能够在你自己的类中使用另外一个类的接口和代码。

    标准的基类继可以是第一次声明的,也可以是[从其它类]继承的。派生类(Derived class)可以继承基类中具有受保护或更高访问性的成员。除了父类所提供的功能之外,派生类还提供了更多专门的功能。在派生类中继承基类成员不是强制的。

访问关键字

    base->访问基类的成员。
    this->引用调用一个方法的当前对象。

    base关键字用于在一个派生类中访问基类的成员:在基类上调用一个以被其它方法重写了的方法、在建立派生类的一个实例的时候指定哪一个基类构造器应该被调用。对基类的访问只允许出现在构造器、实例方法或实例属性访问器中。

    在下面的例子中,基类Person和派生类Employee斗殴一个名为Getinfo的方法。通过使用base关键字,可以从派生类中调用基类的Getinfo方法。

// Accessing base class members
using System;

public class Person {
    protected string ssn = “444-55-6666”;
    protected string name = “John L. Malgraine”;

    public virtual void GetInfo() {
        Console.WriteLine(“Name: {0}”, name);
        Console.WriteLine(“SSN: {0}”, ssn);
    }
}

class Employee : Person {
    public string id = “ABC567EFG”;

    public override void GetInfo() {
        // Calling the base class GetInfo method:
        base.GetInfo();
        Console.WriteLine(“Employee ID: {0}”, id);
    }
}

class TestClass {
    public static void Main() {
        Employee E = new Employee();
        E.GetInfo();
    }
}

输出为:

Name: John L. Malgraine
SSN: 444-55-6666
Employee ID: ABC567EFG

    基类构造器也可以在派生类中调用。要调用一个基类构造器,可以使用base()引用构造器。当需要恰当地初始化一个积累的时候这就非常必要了。

    下面的例子展示了一个带有address参数的派生类构造器:

abstract public class Contact {
    private string address;

    public Contact(string b_address) {
        this.address = b_address;
    }
}

public class Customer : Contact {
    public Customer(string c_address) : base(c_address) {
    }
}

    而在这段代码中,Customer类并没有address,因此它通过在其声明添加一个冒号和带有参数的base关键字将参数传递给它的基类构造器。这会调用Contact的带有address参数的构造器,并将Contact的address域初始化。

    下面是另外一个例子,也展示了当建立派生类实例时是如何调用基类构造器的:

using System;

public class MyBase {
    int num;

    public MyBase() {
        Console.WriteLine(“In MyBase()”);
    }

    public MyBase(int i) {
        num = i;
        Console.WriteLine(“in MyBase(int i)”);
    }
}

public class MyDerived : MyBase {
    static int i = 32;

    // This constructor will call MyBase.MyBase()
    public MyDerived(int ii) : base() {
    }

    // This constructor will call MyBase.MyBase(int i)
    public MyDerived() : base(i) {
    }

    public static void Main() {
        MyDerived md = new MyDerived(); // calls public MyDerived() : base(i)
        // and passes i = 32 in base class
        MyDerived md1 = new MyDerived(1); // calls public MyDerived() : base()
    }
}

输出为:

in MyBase(int i)
in MyBase()

    下面的例子不会通过编译。它详细地说明了一个类定义如果不包括默认构造器后果:

abstract public class Contact {
    private string address;

    public Contact(string address) {
        this.address = address;
    }
}

public class Customer : Contact {
    public Customer(string address) {
    }
}

    在这个例子中,Customer的构造器没有调用基类构造器。这很明显是个Bug,因为address域从未被初始化。

    当一个类没有一个显式的构造器时,系统会为它指派一个默认构造器。默认构造器自动地调用一个默认的或无参的基类构造器。下面的例子是上述例子中将会出现的一个自动生成的默认构造器:

public Customer() : Contact() {
}

    当一个类没有声明任何构造器时,这个例子中的代码会自动地生成。当没有定义派生类构造器时,默认的基类构造器会被隐式地调用。一旦定义了一个派生类构造器,无论其是否带有参数,都不会自动定义上例中出现的默认构造器。

调用基类成员

    如果基类的某些成员具有受保护或更高的可访问性,则派生类可以访问这些成员。这只需简单地在恰当的上下文环境中使用成员的名字,就好像这个成员是派生类自己的一样。下面是一个例子:

abstract public class Contact {
    private string address;
    private string city;
    private string state:
    private string zip:

    public string FullAddress() {
        string fullAddress = address + ‘/n’ + city + ‘,’ + state + ‘ ’ + zip;
        return fullAddress;
    }
}

public class Customer : Contact {
    public string GenerateReport() {
        string fullAddress = FullAddress();
        // do some other stuff...
        return fullAddress;
    }
}

    在上面的例子中,Customer类的GenerateReport()方法调用了其基类Contact中的FullAddress()方法。所有类对其自身的成员都具有完全的访问,而无须限定词。限定词(Qualification)由用圆点分开的类名字和其成员名字组成——如MyObject.SomeMethod()。这个例子说明派生类可以和访问其自身成员一样访问基类成员。

关于继承的更多提示

    不能将一个静态成员标记为重写(override)、虚拟(virtual)或抽象(abstract)的。因此,下面的一行语句是错误的:

public static virtual void GetSSN()

    你不能在派生类中使用base关键字来调用父类的静态方法。

    如果上面的例子中我们声明了下面静态方法:

public class Person {
    protected string ssn = “444-55-6666”;
    protected string name = “John L. Malgraine”;

    public static void GetInfo() {
        // Implementation
    }
}

    现在你就不能使用base.GetInfo()来调用这个方法了,而必须用Person.GetInfo()来调用。在静态成员中我们只能访问静态域、静态方法等。

    下面的例子会出错,因为在GetInfo()中我们不能访问name,因为name是非静态的。

pulic class Person {
    protected string ssn = “444-55-6666”;
    protected string name = “John L. Malgraine”;

    public static void GetInfo() {
        Console.WriteLine(“Name: {0}”, name);
        Console.WriteLine(“SSN: {0}”, ssn);
    }
}

    虚拟的或抽象的成员不能是私有的。

    如果你没有在派生类中重写基类中的虚拟方法,你就不能在派生类中使用base关键字来调用基类方法。同时如果你建立了派生类的实例,你只能调用派生类的方法;如果你要访问基类中的方法,只有建立基类的实例。

    当你在派生了中重写基类中的方法时,你不能降低方法的访问级别;反之却可以。也就是说在派生类中你可以将基类中的受保护方法标记为公有的[译注:在.NET Framework 1.1和C#编译器版本7.10.3052.4中是不允许改变重写方法的访问修饰符的]。

    “this”关键字代表:
    调用一个方法的当前实例。静态成员函数中没有this指针。this关键字只能用于构造器、实例方法和实例属性访问器中。

    下面是this的一般用法:

    用来限定具有相同名字的成员,例如:

public Employee(string name, string alias) {
    this.name = name;
    this.alias = alias;
}

    上面的例子中,this.name代表类中的私有变量name。如果我们写name = name,这表示的是构造器的参数name而不是类中的私有变量name。这种情况下私有变量name是不会被初始化的。

    用来将该对象传递给其它方法,例如:

CalcTax(this);

    用于索引器,例如:

public int this[int param] {
    get {
        return array[param];
    }

    set {
        array[param] = value;
    }
}

    在静态方法、静态属性访问器和域声明中的变量初始化器中使用this是错误的。

    下面的例子使用了this来限制同名的类成员name和alias。同时还将一个对象传递给另一个类中的方法。

// keywords_this.cs
// this example
using System;

public class Employee {
    public string name;
    public string alias;
    public decimal salary = 3000.00m;

    // Constructor:
    public Employee(string name, string alias) {
        // Use this to qualify the fields, name and alias:
        this.name = name;
        this.alias = alias;
    }

    // Printing method:
    public void printEmployee() {
        Console.WriteLine("Name: {0}/nAlias: {1}", name, alias);
        // Passing the object to the CalcTax method by using this:
        Console.WriteLine("Taxes: {0:C}", Tax.CalcTax(this));
    }
}

public class Tax {
    public static decimal CalcTax(Employee E) {
        return (0.08m * (E.salary));
    }
}

public class MainClass {
    public static void Main() {
        // Create objects:
        Employee E1 = new Employee ("John M. Trainer", "jtrainer");

        // Display results:
        E1.printEmployee();
    }
}

输出为:

Name: John M. Trainer
Alias: jtrainer
Taxes: $240.00

抽象类

    抽象类是一种特殊的基类。除了通常的类成员,它们还带有抽象类成员。抽象类成员是指没有实现而只有声明的方法和属性。所有直接从抽象类派生的类都必须实现所有这些抽象方法和属性。

    抽象方法不能实例化。这样做[译注:指实例化一个抽象类]是不合逻辑的,因为那些抽象成员没有实现。那么,不能实例化一个类有什么好处呢?很多!抽象类稳坐类继承树的顶端。它们确定了类的结构和代码的意图。用它们可以得到更易搭建的框架。这是可能的,因为抽象类具有这个框架中所有基类的一般信息和行为。看看下面的例子:

abstract public class Contact { // Abstract Class Contact
    protected string name;

    public Contact() {
        // statements...
    }

    public abstract void generateReport();

    abstract public string Name {
        get;
        set;
    }
}

    Contact是一个抽象类。Contact有两个抽象成员,其中有一个是抽象方法,名为generateReport()。这个方法使用了abstract修饰符进行声明,这个声明没有实现(没有花括号)并以分号结束。属性Name也被声明为抽象的。属性访问器也是以分号结束。

public class Customer : Contact { // Customer Inherits Abstract Class Contact
    string gender;
    decimal income;
    int nuberOfVisits;

    public Customer() {
        // statements
    }

    public override void generateReport() {
        // unique report
    }

    public override string Name {
        get {
            numberOfVisits++;
            return name;
        }

        set {
            name = value;
            nuberOfVisits = 0;
        }
    }
}

public class SiteOwner : Contact {
    int siteHits;
    string mySite;

    public SiteOwner() {
        // statements...
    }

    public override void generateReport() {
        // unique report
    }

    public override string Name {
        get {
            siteHits++;
            return name;
        }

        set {
            name = value;
            siteHits = 0;
        }
    }
}

    抽象基类有两个派生类——Customer和SiteOwner。这些派生类都实现了基类Contact中的抽象成员。每个派生类中的generateReport()方法声明中都有一个override修饰符。同样,Customer和SiteOwner中的Name属性的声明也都带有override修饰符。当重写方法时,C#有意地要求显式的声明。这种方法可以跳代码的安全性,因为它可以防止意外的方法重写,这在其他语言中确实发生过。省略override修饰符是错误的。同样,添加new修饰符也是错误的。抽象方法必须被重写,不能隐藏。因此既不能使用new修饰符,也不能没有修饰符。

    所有抽象类中最出名的就是Object类[译注:.NET Framework中的Object类并不是抽象类]。它可以写作object或Object[译注:object是C#中的关键字,用于声明Object类的一个对象;Object是指.NET Framework类库中的System.Object类],但它们都是同一个类。Object类是C#中所有其他类的基类。它同时也是没有指定基类时的默认基类。下面的这些类声明产生同样的结果:

abstract public class Contact : Object {
    // class members
}

abstract public class Contact {
    // class members
}

    如果没有声明基类,Object会隐式地成为基类。除了将C#类框架中的所有类联系在一起,Object类还提供了一些内建的功能,这些需要派生类来实现。

接口和抽象类之间的区别

    接口和抽象类关系很紧密,它们都具有对成员的抽象。

    对于一个抽象类,至少一个方法是抽象方法既可,这意味着它也可以具有具体方法[译注:Concrete Method,这只是相对于抽象方法而言,面向对象中并没有这个概念]。

    对于一个接口,所有的方法必须都是抽象的。

    实现了一个接口的类必须为接口中的所有方法提供具体的实现,否则只能声明为抽象类。

    在C#中,多重继承(Multiple Inheritance)只能通过实现多个接口得到。抽象类只能单继承[译注:C#中的单继承是指所有类作为基类的时候都只能是派生类声明中唯一的基类,而不仅仅是抽象类]。

    接口定义的是一个契约,其中只能包含四种实体,即方法、属性、事件和索引器。因此接口不能包含常数(Constant)、域、操作符、构造器、析构器、静态构造器或类型[译注:指嵌套的类型]。

    同时,一个接口还不能包含任何类型的静态成员。修饰符abstract、public、protected、internal、private、virtual、override都是不允许出现的,因为它们在这种环境中是没有意义的。

    类中实现的接口成员必须具有公有的可访问性。

重写概述

    派生类可以通过override关键字来重写基类中的虚拟方法。这样做必须遵守下面的约束:
  • 关键字override用于子类方法的定义,说明这个方法将要重写基类中的虚拟方法。
  • 返回值类型必须与基类中的虚拟方法一致。
  • 方法的名字必须相同。
  • 参数列表中的参数顺序、数量和类型必须一致。


    重写的方法的可访问性不能比基类中的虚拟方法具有更多的限制。可访问性应具有相同或更少的限制[译注:实际上,在C#中重写的方法的可访问性必须与基类中的虚拟方法一致]。

    子类中重写的虚拟方法可以通过sealed关键字声明为封闭(Sealed)的,以防止在以后的派生类中改变虚拟方法的实现。

隐藏基类成员

    有的时候派生了的成员和基类中相应的成员具有相同的名字。这时,我们称派生类需要“隐藏(Hiding)”基类成员。

    当发生隐藏时,派生类的成员将取代基类成员的功能。派生类的使用者将无法看到被隐藏的成员,他们只能看到派生类的成员。下面的例子显示了如何隐藏一个基类成员。

abstract public class Contact {
    private string address;
    private string city;
    private string state;
    private string zip:

    public string FullAddress() {
        string fullAddress = address + ‘/n’ + city + ‘,’ + state + ‘ ’ + zip;
        return fullAddress;
    }
}

public class SiteOwner : Contect {
    public string FullAddress() {
        string fullAddress;

        // create an address ...
        return fullAddress;
    }
}

    在这个例子中,SiteOwner和他的基类——Contact——都有一个名为FullAddress()的方法。StieOwner类中的FullAddress()方法隐藏了Contact类中的FullAddress()方法。这意味着当调用一个SiteOwner类的实例的FullAddress()时,调用的是SiteOwner类的FullAddress()方法,而不是Contact类的FullAddress()方法。

    尽管基类中的成员可以被隐藏,派生类还是可以通过base关键字来访问它。有的时候这样做是值得的。这对于既要利用基类的功能又要添加派生类的代码是很有用的。下面的例子展示了如何在派生类中引用基类中(被隐藏的)成员。

abstract public class Contact {
    private string address;
    private string city;
    private string state;
    private string zip;

    public string FullAddress() {
        string fullAddress = address + '/n' + city + ',' + state + ' ' + zip;
        return fullAddress;
    }
}

public class SiteOwner : Contact {
    public string FullAddress() {
        string fullAddress = base.FullAddress();

        // do some other stuff...
        return fullAddress;
    }
}

    在这个特定的例子中,SiteOwner类中的FullAddress()方法调用了Contact类中的FullAddress()方法。这通过一个对基类的引用来完成。这提供了另一种重用代码的途径,并添加了用户的行为。

版本

    版本——继承中的一个环境——在C#中是一种机制,可以修正类(建立新版本)但不致意外地改变了代码的意图。上面的隐藏基类成员方法的例子会得到编译器的一个警告,这正是由于C#的版本政策。它[译注:指版本机制]被设计用于消除修正基类时所带来的一些问题。

    考虑这样一幕:一个开发人员建立了一个类,从第三方的库中的类继承而来。最为讨论,我们不妨假设Contact类就是第三方类库中的类。看下面的例子:

public class Contact {
    // does not include FullAddress() method
}

public class SiteOwner : Contact {
    public string FullAddress() {
        string fullAddress = mySite.ToString();
        return fullAddress;
    }
}

    在这个例子中,基类中并没有FullAddress()方法。这还没有问题。稍后,第三方库的建立者更新了他们的代码。这些更新的部分中就包括了和派生类中的成员同名的成员:

public class Contact {
    private string address;
    private string city;
    private string state;
    private string zip;

    public string FullAddress() {
        string fullAddress = address + '/n' + city + ',' + state + ' ' + zip;
        return fullAddress;
    }
}

public class SiteOwner : Contact {
    public string FullAddress() {
        string fullAddress = mySite.ToString();
        return fullAddress;
    }
}

    在这段代码中,基类中的FullAddress()方法和派生类中的方法具有不同的功能。在其他语言中,由于隐式的多态性,这种情形将会破坏代码。然而,在C#中这不会破坏任何代码,因为当在SiteOwner上调用FullAdress()时,仍然调用的是SiteOwner类中的方法。

    但是这种情况会得到一个警告信息。消除这一警告消息的方法是在派生类的方法名字前面放置一个new修饰符,如下面例子所示:

using System;

public class WebSite {
    public string SiteName;
    public string URL;
    public string Description;

    public WebSite() {
    }

    public WebSite (
        string strSiteName,
        string strURL,
        string strDescription
    ) {
        SiteName = strSiteName;
        URL = strURL;
        Description = strDescription;
    }

    public override string ToString() {
        return SiteName + ", " + URL + ", " + Description;
    }
}

public class Contact {
    public string address;
    public string city;
    public string state;
    public string zip;

    public string FullAddress() {
        string fullAddress = address + '/n' + city + ',' + state + ' ' + zip;
        return fullAddress;
    }
}

public class SiteOwner : Contact {
    int siteHits;
    string name;
    WebSite mySite;

    public SiteOwner() {
        mySite = new WebSite();
        siteHits = 0;
    }

    public SiteOwner(string aName, WebSite aSite) {
        mySite = new WebSite (
            aSite.SiteName,
            aSite.URL,
            aSite.Description
        );

        Name = aName;
    }

    new public string FullAddress() {
        string fullAddress = mySite.ToString();
        return fullAddress;
    }

    public string Name {
        get {
            siteHits++;
            return name;
        }

        set {
            name = value;
            siteHits = 0;
        }
    }
}

public class Test {
    public static void Main() {
        WebSite mySite = new WebSite (
            "Le Financier",
            " http://www.LeFinancier.com'> http://www.LeFinancier.com",
            "Fancy Financial Site"
        );

        SiteOwner anOwner = new SiteOwner("John Doe", mySite);
        string address;

        anOwner.address = "123 Lane Lane";
        anOwner.city = "Some Town";
        anOwner.state = "HI";
        anOwner.zip = "45678";

        address = anOwner.FullAddress(); // Different Results
        Console.WriteLine("Address: /n{0}/n", address);
    }
}

输出为:

Address:
Le Financier, http://www.LeFinancier.com'> http://www.LeFinancier.com, Fancy Financial Site

    这具有让编译器知道开发者意图的效果。将new修饰符放到基类成员声明的前面表示开发者知道基类中有一个同名的方法,并且他们确实想隐藏这个成员。这可以保护那些依赖于基类成员实现的现存代码不受破坏。在C#中,当使用一个派生类对象时,调用的是基类的方法。同样,当调用基类成员的方法时,调用的也是基类的方法。但这会出现一个问题,就是如果基类中添加了一个重要的新特性,则这些新特性对派生类是无效的。

    要想使用这些新特性,就需要一些不同的途径。一种选择就是重命名派生类的成员,以允许程序通过派生类成员来使用一个基类方法。这种做法的缺点是,另一个依赖这个派生类实现的类可能具有同名的成员。这会破坏代码,因此,这是一种不好的形式。

    另一个选择是在派生类中定义一个新的方法来调用基类方法。这允许派生类的用户能够获得基类的新功能,同时保留了派生类现有的功能。尽管这可以工作,但会关系到派生类的可维护性。

封闭类

    封闭类是不可以继承的类。将一个类声明为封闭类可以保护这个类不被其他类继承。这样做有很多有益的原因,包括优化和安全性。

    封闭一个类可以皮面虚拟方法带来的系统开销。这允许编译器进行一些优化,这些优化对普通的类是无效的。

    使用封闭类的另一个有益的原因是安全性。继承,由于它的本质,可能以某些受保护成员能够访问到基类的内部。封闭一个类可以排除派生类的这些缺陷。关于封闭类的一个很好的例子是String类。下面的例子显示了如何建立一个封闭类:

public sealed class CustomerStats {
    string gender;
    decimal income;
    int numberOfVisits;

    public CustomerStats() {
    }
}

public class CustomerInfo : CustomerStats { // Error
}

    这个例子将会产生编译错误。由于CustomerStats类是封闭的,它不能被CustomerInfo类继承。不过CustomerStats类可以用作一个类内部的对象。下面的就是在Customer类中声明了一个CustomerStats类的对象。

public class Customer {
    CustomerStats myStats; // OK
}



多态性



    多态性反映了能够在多于一个类的对象中完成同一事物的能力——用同一种方法在不同的类中处理不同的对象。例如,如果Customer和Vendor对象都有一个Name属性,则我们可以写一个事物来调用Name属性而不管我们所使用的是Customer对象还是Vendor对象,这就是多态性。

    交通工具是多态性的一个很好的例子。一个交通工具接口可以只包括所有交通工具都具有的属性和方法,还可能包括颜色、车门数、变速器和点火器等。这些属性可以用于所有类型的交通工具,包括轿车、卡车和挂车。

    多态性不在交通工具的属性和方法背后实现代码。相反,多态性只是实现接口。如果轿车、卡车和挂车都实现了同样的交通工具接口,则所有这三个类的客户代码是完全一样的。

    C#通过继承来为我们提供多态性。C#提供了virtual关键字用于定义一个支持多态的方法。

    子类对于虚拟方法可以自由地提供它自己的实现,这称为重写。下面是一些关于虚拟方法的要点:

要点:
  • 如果方法是非虚拟的,编译器简单地使用所引用的类型来调用适当的方法。
  • 如果方法是虚拟的,编译器将产生代码来在运行时检查所引用的类,来从适当的类型中调用适当的方法。
  • 当一个虚拟方法被调用时,运行时会进行检查(方法迟绑定)来确定对象和调用适当的方法,所有这些都是在运行时进行的。
  • 对于非虚拟方法,这些信息在编译期间都是无效的,因此不会引起运行时检查,所以调用非虚拟方法的效率会略微高一些。但在很多时候虚拟方法的行为会更有用,损失的那些性能所换来的功能是很值得的。


实现多态性

    实现多态性的关键因素是基于对象的类型动态地调用类的方法。本质上,一个程序会有一组对象,它会检查每一个对象的类型,并执行适当的方法。下面是一个例子:

using System;

public class WebSite {
    public string SiteName;
    public string URL;
    public string Description;

    public WebSite() {
    }

    public WebSite (
        string strSiteName,
        string strURL,
        string strDescription
    ) {
        SiteName = strSiteName;
        URL = strURL;
        Description = strDescription;
    }

    public override string ToString() {
        return SiteName + ", " + URL + ", " + Description;
    }
}

    当我们继承了上述的类,我们有两种方法来调用这个类的构造器。因此,这是一个设计时的多态。这时,我们必须在设计时决定在继承的类中调用哪一个方法。

    多态性是程序通过一个一般基类的引用来完成实现在多个派生类中的方法的能力。多态性的另一个定义是通过同一种方法来处理不同对象的能力。这意味着检测一个对象的行为是通过运行时类型,而不是它在设计时所引用的类型。



总结


    上面我尝试通过一些实际的例子解释了C#中面向对象的基本概念,但这将是一个长期的旅程。

 

什么是静态?

在程序开发时,我们经常希望一个变量和方法不随对象的改变而改变,甚至在没有创建对象时也能访问数据和方法,这时就可以在数据和方法上加上Static关键字,被Static修饰的数据就叫做静态变量(数据)而方法就叫静态方法。静态变量在内存中的地址是相同的,所以对于同一类的不同对象,它们静态变量的值肯定是相同的。


  发表于  21:07:46  |   阅读全文  |   评论(0)  |   引用(0)  |   编辑


2006-01-05

static关键字

  通常,我们创建类时会指出那个类的对象的外观与行为。除非用new创建那个类的一个对象,否则实际上并未得到任何东西。只有执行了new后,才会正式生成数据存储空间,并可使用相应的方法。

  但在两种特殊的情形下,上述方法并不堪用。一种情形是只想用一个存储区域来保存一个特定的数据——无论要创建多少个对象,甚至根本不创建对象。另一种情形是我们需要一个特殊的方法,它没有与这个类的任何对象关联。也就是说,即使没有创建对象,也需要一个能调用的方法。为满足这两方面的要求,可使用static(静态)关键字。一旦将什么东西设为static,数据或方法就不会同那个类的任何对象实例联系到一起。所以尽管从未创建那个类的一个对象,仍能调用一个static方法,或访问一些static数据。而在这之前,对于非static数据和方法,我们必须创建一个对象,并用那个对象访问数据或方法。这是由于非static数据和方法必须知道它们操作的具体对象。当然,在正式使用前,由于static方法不需要创建任何对象,所以它们不可简单地调用其他那些成员,同时不引用一个已命名的对象,从而直接访问非static成员或方法(因为非static成员和方法必须同一个特定的对象关联到一起)。

void 关键字

表示方法返回的值的类型为空。如果返回特定的类型的话,则该方法实际上就是函数,否则就是一个过程而已.


  发表于 09:35:40  |  阅读全文  |  评论(0)  |  引用(0)  |  编辑


2006-01-04

C#中的一些访问修饰符

声明的可访问性      意义 
public                       访问不受限制。 
protected                 访问仅限于包含类或从包含类派生的类型。 
internal                     访问仅限于当前项目。 
protected internal   访问仅限于从包含类派生的当前项目或类型。 
private                       访问仅限于包含类型。

虚属性

   1. 使用virtual 修饰符声明的属性为虚属性。

   2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。

   抽象属性

   1. 使用abstract 修饰符声明的属性为抽象属性。

   2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。

   3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。

   4. 抽象属性只允许在抽象类中声明。

   5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。


  发表于 12:11:51  |  阅读全文  |  评论(0)  |  引用(0)  |  编辑


2005-12-30

一. 继承基础知识

   为了提高软件模块的可复用性和可扩充性,以便提高软件的开发效率,我们总是希望能够利用前人或自己以前的开发成果,同时又希望在自己的开发过程中能够有足够的灵活性,不拘泥于复用的模块。C#这种完全面向对象的程序设计语言提供了两个重要的特性--继承性inheritance 和多态性polymorphism。

   继承是面向对象程序设计的主要特征之一,它可以让您重用代码,可以节省程序设计的时间。继承就是在类之间建立一种相交关系,使得新定义的派生类的实例可以继承已有的基类的特征和能力,而且可以加入新的特性或者是修改已有的特性建立起类的新层次。

   现实世界中的许多实体之间不是相互孤立的,它们往往具有共同的特征也存在内在的差别。人们可以采用层次结构来描述这些实体之间的相似之处和不同之处。


图1 类图

   上图反映了交通工具类的派生关系。最高层的实体往往具有最一般最普遍的特征,越下层的事物越具体,并且下层包含了上层的特征。它们之间的关系是基类与派生类之间的关系。

   为了用软件语言对现实世界中的层次结构进行模型化,面向对象的程序设计技术引入了继承的概念。一个类从另一个类派生出来时,派生类从基类那里继承特性。派生类也可以作为其它类的基类。从一个基类派生出来的多层类形成了类的层次结构。

   注意:C#中,派生类只能从一个类中继承。这是因为,在C++中,人们在大多数情况下不需要一个从多个类中派生的类。从多个基类中派生一个类这往往会带来许多问题,从而抵消了这种灵活性带来的优势。
C#中,派生类从它的直接基类中继承成员:方法、域、属性、事件、索引指示器。除了构造函数和析构函数,派生类隐式地继承了直接基类的所有成员。看下面示例:

using System ;
class Vehicle //定义交通工具(汽车)类
{
protected int wheels ; //公有成员:轮子个数
protected float weight ; //保护成员:重量
public Vehicle( ){;}
public Vehicle(int w,float g){
wheels = w ;
weight = g ;
}
public void Speak( ){
Console.WriteLine( "交通工具的轮子个数是可以变化的! " ) ;
}
} ;
class Car:Vehicle //定义轿车类:从汽车类中继承
{
int passengers ; //私有成员:乘客数
public Car(int w , float g , int p) : base(w, g)
{
wheels = w ;
weight = g ;
passengers=p ;
}
}

  Vehicle 作为基类,体现了"汽车"这个实体具有的公共性质:汽车都有轮子和重量。Car 类继承了Vehicle 的这些性质,并且添加了自身的特性:可以搭载乘客。
二、C#中的继承符合下列规则:

   1、继承是可传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员,同样也继承了A中的成员。Object 类作为所有类的基类。

   2、派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去已经继承的成员的定义。

   3、构造函数和析构函数不能被继承。除此以外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式只能决定派生类能否访问它们。

   4、派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖已继承的成员。但这并不因为这派生类删除了这些成员,只是不能再访问这些成员。

   5、类可以定义虚方法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,从而实现类可以展示出多态性。

   6、派生类只能从一个类中继承,可以通过接吕实现多重继承。

   下面的代码是一个子类继承父类的例子:

using System ;
public class ParentClass
{
public ParentClass( )
{ Console.WriteLine("父类构造函数。"); }
public void print( )
{ Console.WriteLine("I'm a Parent Class。") ; }
}
public class ChildClass : ParentClass
{
public ChildClass( )
{ Console.WriteLine("子类构造函数。") ; }
public static void Main( ) {
ChildClass child = new ChildClass( ) ;
child.print( ) ;
}
}

  程序运行输出:

   父类构造函数。子类构造函数。I'm a Parent Class。

   上面的一个类名为ParentClass, main函数中用到的类名为ChildClass。要做的是创建一个使用父类ParentClass现有代码的子类ChildClass。

   1.首先必须说明ParentClass是ChildClass的基类。

   这是通过在ChildClass类中作出如下说明来完成的:"public class ChildClass : ParentClass"。在派生类标识符后面,用分号":" 来表明后面的标识符是基类。C#仅支持单一继承。因此,你只能指定一个基类。

   2.ChildClass的功能几乎等同于ParentClass。

   因此,也可以说ChildClass "就是" ParentClass。在ChildClass 的Main( )方法中,调用print( ) 方法的结果,就验证这一点。该子类并没有自己的print( )方法,它使用了ParentClass中的 print( )方法。在输出结果中的第三行可以得到验证。

   3.基类在派生类初始化之前自动进行初始化。ParentClass 类的构造函数在ChildClass的构造函数之前执行。

三. 访问与隐藏基类成员

   (1) 访问基类成员

   通过base 关键字访问基类的成员:

    调用基类上已被其他方法重写的方法。
    指定创建派生类实例时应调用的基类构造函数。
    基类访问只能在构造函数、实例方法或实例属性访问器中进行。
    从静态方法中使用 base 关键字是错误的。

   示例:下面程序中基类 Person 和派生类 Employee 都有一个名为 Getinfo 的方法。通过使用 base 关键字,可以从派生类中调用基类上的 Getinfo 方法。
using System ;
public class Person
{
protected string ssn = "111-222-333-444" ;
protected string name = "张三" ;
public virtual void GetInfo() {
Console.WriteLine("姓名: {0}", name) ;
Console.WriteLine("编号: {0}", ssn) ;
}
}
class Employee: Person
{
public string id = "ABC567EFG23267" ;
public override void GetInfo() {
// 调用基类的GetInfo方法:
base.GetInfo();
Console.WriteLine("成员ID: {0}", id) ;
}
}
class TestClass {
public static void Main() {
Employee E = new Employee() ;
E.GetInfo() ;
}
}

  程序运行输出:

    姓名: 张三
    编号: 111-222-333-444
    成员ID: ABC567EFG23267
    示例:派生类同基类进行通信。

using System ;
public class Parent
{
string parentString;
public Parent( )
{ Console.WriteLine("Parent Constructor.") ; }
public Parent(string myString) {
parentString = myString;
Console.WriteLine(parentString) ;
}
public void print( )
{ Console.WriteLine("I'm a Parent Class.") ; }
}
public class Child : Parent
{
public Child( ) : base("From Derived")
{ Console.WriteLine("Child Constructor.") ; }
public void print( ) {
base.print( ) ;
Console.WriteLine("I'm a Child Class.") ;
}
public static void Main( ) {
Child child = new Child( ) ;
child.print( ) ;
((Parent)child).print( ) ;
}
}

  程序运行输出:

From Derived
Child Constructor.
I'm a Parent Class.
I'm a Child Class.
I'm a Parent Class.

   说明:

   1.派生类在初始化的过程中可以同基类进行通信。

   上面代码演示了在子类的构造函数定义中是如何实现同基类通信的。分号":"和关键字base用来调用带有相应参数的基类的构造函数。输出结果中,第一行表明:基类的构造函数最先被调用,其实在参数是字符串"From Derived"。

   2.有时,对于基类已有定义的方法,打算重新定义自己的实现。

   Child类可以自己重新定义print( )方法的实现。Child的print( )方法覆盖了Parent中的 print 方法。结果是:除非经过特别指明,Parent类中的print方法不会被调用。

   3.在Child类的 print( ) 方法中,我们特别指明:调用的是Parent类中的 print( ) 方法。

   方法名前面为"base",一旦使用"base"关键字之后,你就可以访问基类的具有公有或者保护权限的成员。 Child类中的print( )方法的执行结果出现上面的第三行和第四行。

   4.访问基类成员的另外一种方法是:通过显式类型转换。

   在Child类的Main( )方法中的最后一条语句就是这么做的。记住:派生类是其基类的特例。这个事实告诉我们:可以在派生类中进行数据类型的转换,使其成为基类的一个实例。上面代码的最后一行实际上执行了Parent类中的 print( )方法。

   (2) 隐藏基类成员

   想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果?类的层次结构体系将变得十分庞,大类之间的关系杂乱无章,对类的理解和使用都会变得十分困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类已经没有再被继承的必要。C#提出了一个密封类(sealed class)的概念,帮助开发人员来解决这一问题。

   密封类在声明中使用sealed 修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#将提示出错。理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。

   在哪些场合下使用密封类呢?密封类可以阻止其它程序员在无意中继承该类。而且密封类可以起到运行时优化的效果。实际上,密封类中不可能有派生类。如果密封类实例中存在虚成员函数,该成员函数可以转化为非虚的,函数修饰符virtual 不再生效。

   让我们看下面的例子:
bstract class A
{
public abstract void F( ) ;
}
sealed class B: A
{
public override void F( )
{ // F 的具体实现代码 }
}

  如果我们尝试写下面的代码

class C: B{ }

   C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。

   (3) 密封方法

   我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。

   不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:

using System ;
class A
{
public virtual void F( )
{ Console.WriteLine("A.F") ; }
public virtual void G( )
{ Console.WriteLine("A.G") ; }
}
class B: A
{
sealed override public void F( )
{ Console.WriteLine("B.F") ; }
override public void G( )
{ Console.WriteLine("B.G") ; }
}
class C: B
{
override public void G( )
{ Console.WriteLine("C.G") ; }
}

  类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。

   (4) 使用 new 修饰符隐藏基类成员

   使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。

   请看下面的类:

public class MyBase
{
public int x ;
public void MyVoke() ;
}

  在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:

public class MyDerived : MyBase
{ new public void MyVoke (); }

  但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。

   通过继承隐藏名称采用下列形式之一:

   a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。

   b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。

   c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。

   注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。

   示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。

using System ;
public class MyBase
{
public static int x = 55 ;
public static int y = 22 ;
}
public class MyDerived : MyBase
{
new public static int x = 100; // 利用new 隐藏基类的x
public static void Main()
{
// 打印x:
Console.WriteLine(x);
//访问隐藏基类的 x:
Console.WriteLine(MyBase.x);
//打印不隐藏的y:
Console.WriteLine(y);
}
}

  输出: 100 55 22

   如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:

The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.

   如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。

四、多级继承

   一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。

   只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ;
//定义一个描述点的接口
interface IPoint
{
int x {
get ;
set ;
}
int y {
get ;
set ;
}
}
interface IPoint2
{
int y {
get ;
set ;
}
}
//在point中继承了两个父类接口,并分别使用了两个父类接口的方法
class Point:IPoint,IPoint2
{
//定义两个类内部访问的私有成员变量
private int pX ;
private int pY ;
public Point(int x,int y) {
pX=x ;
pY=y ;
}
//定义的属性,IPoint接口方法实现
public int x
{
get
{ return pX ; }
set
{ pX =value ; }
}
//IPoint1接口方法实现
public int y
{
get
{ return pY ; }
set
{ pY =value ; }
}
}
class Test
{
private static void OutPut( IPoint p )
{ Console.WriteLine("x={0},y={1}",p.x,p.y) ; }
public static void Main( ) {
Point p =new Point(15,30) ;
Console.Write("The New Point is:") ;
OutPut( p ) ;
string myName =Console.ReadLine( ) ;
Console.Write("my name is {0}", myName) ;
}
}
五、继承与访问修饰符

   访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal protected private。
声明的可访问性 意义
public 访问不受限制。
protected 访问仅限于包含类或从包含类派生的类型。
internal 访问仅限于当前项目。
protected internal 访问仅限于从包含类派生的当前项目或类型。
private 访问仅限于包含类型。

  1、继承中关于可访问域的一些问题

   基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中

class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}

  类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。

   2、继承中关于属性的一些问题

   和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:

   属性的重载

   1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。

   2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。

   3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。

   注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。

   虚属性

   1. 使用virtual 修饰符声明的属性为虚属性。

   2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。

   抽象属性

   1. 使用abstract 修饰符声明的属性为抽象属性。

   2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。

   3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。

   4. 抽象属性只允许在抽象类中声明。

   5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。

   密封属性

   1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。

   2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。

   从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get 访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value 参数、返回类型为void 的方法。看下面的程序:

using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}

  上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:

class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}

  在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People 中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People 中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer 类中没有抽象成员的存在,Customer可以是非虚的。

   3、继承中对使用可访问性级别的限制

   声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error

  下表汇总了对使用声明的可访问性级别的限制。

上下文 备注
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数 常数的类型必须至少与常数本身具有同样的可访问性。
字段 字段的类型必须与至少字段本身具有同样的可访问性。
方法 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性 属性的类型必须至少与属性本身具有同样的可访问性。
事件 事件的类型必须至少与事件本身具有同样的可访问性。
索引器 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。

  示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。

using System ;
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值