一,继承
为了最大限度的实现代码的复用,我们可以把一些有相同特征的类的属性和行为抽象出来放到一个更上层的类中,作为基类。
让这些特殊的类通过某种方式拥有这个基类中的属性和行为。这种方式便是继承。
C#中继承为单一继承,一个类不能从两个类进行派生。
C# 中实现继承就是在类名后添加一个冒号,把基类的名称放到冒号后面。
class BaseA
{
public Name(){}
}
class A : BaseA
{
public A()
{}
}
二,基类型和派生类型的转换
由于派生类建立了属于关系,所以可以将派生类直接赋值给一个基类型。这是隐式的类型转换。
但反之则不能把基类型的变量直接付给派生类,但可以通过显式的类型转换方法进行赋值。
自定义转换:类型间的转换并不限定于单一的继承链中,完全不相关的类型也相互间也能进行转换。
关键字:implicit 和 explicit,可以定义一个类到另一个类隐式类型转换和显示类型转换的操作。
隐式类型转换 代码示例:
class Digit
{
public Digit(double d) { val = d; }
public double val;
// ...other members
// User-defined conversion from Digit to double
public static implicit operator double(Digit d)
{
return d.val;
}
// User-defined conversion from double to Digit
public static implicit operator Digit(double d)
{
return new Digit(d);
}
}
class Program
{
static void Main(string[] args)
{
Digit dig = new Digit(7);
//This call invokes the implicit "double" operator
double num = dig;
//This call invokes the implicit "Digit" operator
Digit dig2 = 12;
Console.WriteLine("num = {0} dig2 = {1}", num, dig2.val);
Console.ReadLine();
}
}
显示类型转换 代码示例:
代码中必须使用显示的类型转换方式进行转换。
class Celsius
{
public Celsius(float temp)
{
degrees = temp;
}
public static explicit operator Fahrenheit(Celsius c)
{
return new Fahrenheit((9.0f / 5.0f) * c.degrees + 32);
}
public float Degrees
{
get { return degrees; }
}
private float degrees;
}
class Fahrenheit
{
public Fahrenheit(float temp)
{
degrees = temp;
}
// Must be defined inside a class called Fahrenheit:
public static explicit operator Celsius(Fahrenheit fahr)
{
return new Celsius((5.0f / 9.0f) * (fahr.degrees - 32));
}
public float Degrees
{
get { return degrees; }
}
private float degrees;
}
class MainClass
{
static void Main()
{
Fahrenheit fahr = new Fahrenheit(100.0f);
Console.Write("{0} Fahrenheit", fahr.Degrees);
Celsius c = (Celsius)fahr;
Console.Write(" = {0} Celsius", c.Degrees);
Fahrenheit fahr2 = (Fahrenheit)c;
Console.WriteLine(" = {0} Fahrenheit", fahr2.Degrees);
}
}
// Output:
// 100 Fahrenheit = 37.77778 Celsius = 100 Fahrenheit
三,类继承的限制
public 修饰符:基类中所有的由public修饰的成员都可以被派生类和其他类使用。
private 修饰符:基类中所有的由private修饰的成员都不可以被派生类和其他类使用。
protected 修饰符:基类中所有的由protected 成员都可以被派生类使用但不能被其他类使用。
sealed 密封类:使用sealed 修饰一个类时,这个则类不能派生出其他任何类。
实例代码:
namespace testSealed
{
public sealed class CommandLine
{
public CommandLine()
{ }
}
public sealed class DosCommandLine : CommandLine
{
public DosCommandLine()
{ }
}
class Program
{
static void Main(string[] args)
{
}
}
}
编译错误:Error 1 'testSealed.DosCommandLine': cannot derive from sealed type 'testSealed.CommandLine'
四,基类的重写
virtual 修饰符
一个基类的 public 和 protected 方法都会在派生类中继承,但某些情况下派生类需要自己定制自己的实现,这时就需要重写基类的方法。
C#支持重写方法和属性,但不支持重写字段和任何静态成员。
在基类中必须将允许重写的成员标记为 virtual,如果没有包含virtual 标记符则不允许派生类重写。
在派生类中要求重写方法显示的用 override 关键字标识。
代码示例:
这里基类定义了两个方法,一个是可以被重载的(getName),另一没(getID),在派生类中都重写了他们。
using System;
using System.Collections.Generic;
using System.Text;
namespace testSealed
{
public class CommandLine
{
public CommandLine(){
m_name = "Base Class CommandLine";
m_id = "Base Class 001";
}
public virtual string getName(){
return m_name;
}
public string getID(){
return m_id;
}
private string m_name;
private string m_id;
}
public class DosCommandLine : CommandLine
{
public DosCommandLine(){
m_name = "Sub Class DosCommandLine";
m_id = "Sub Class 001";
}
public override string getName(){
return m_name;
}
// 这里将出现一个编译告警:'testSealed.DosCommandLine.getID()' hides inherited member
// 'testSealed.CommandLine.getID()'. Use the new keyword if hiding was intended.
public string getID(){
return m_id;
}
private string m_name;
private string m_id;
}
class Program
{
static void Main(string[] args)
{
CommandLine cmd = new DosCommandLine();
Console.WriteLine("Name= {0}\n, ID={1} ", cmd.getName(), cmd.getID());
Console.ReadKey();
}
}
}
测试结果:
Name= Sub Class DosCommandLine, ID=Base Class 001
getName是被virtual 标示的被重写了,测试结果中调用的是派生类的实现,但getID没有被virtual 修饰,测试结果中它还是调用的基类的方法。
new 修饰符
在上一段程序中由于getID没有使用修饰符 virtual,出现了一个编译告警:
Warning 'testSealed.DosCommandLine.getID()' hides inherited member 'testSealed.CommandLine.getID()'. Use the new keyword if hiding was intended.
同时这个告警提供给我们一个解决方法 Use the new keyword if hiding was intended.
我们可以把派生类的程序改为:
public class DosCommandLine : CommandLine
{
public DosCommandLine(){
m_name = "Sub Class DosCommandLine";
m_id = "Sub Class 001";
}
public override string getName(){
return m_name;
}
public new string getID(){
return m_id;
}
private string m_name;
private string m_id;
}
编译告警消除了。
原理:new 操作符仅仅是在基类前隐藏了派生类重新声明的成员。在这种情况下,系统不是调用继承链中派生得最远的成员,
而是基类成员搜索继承链,找到使用 new 操作符成员之前的那个成员,并且调用它。
感觉上是派生类并没有重新那个成员。
测试结果:
Name= Sub Class DosCommandLine, ID=Base Class 001
从测试结果也可以看出 new 并没有实际上改变程序的运行,它的作用主要就是消除告警。
sealed 修饰符
与类使用的 sealed 关键字一样,可以禁止派生类重写声明为virtual 的基类成员。
这里 类B 禁止了派生类去重写 Hello() 方法。
public class A
{
public virtual void Hello()
{ }
}
public class B : A
{
public override sealed void Hello()
{ }
}
public class C : B
{
// 编译错误
// 'testSealed.C.Hello()': cannot override inherited member 'testSealed.B.Hello()' because it is sealed
public override void Hello()
{ }
}
五,抽象类
一种仅供派生的类,这种类本身产生的对象并没有实际的意义。抽象类代表了抽象的实体,它定义了它派生类的对象应该包含什么东西,
但具体实现则是由它的派生的具体类定义的。
abstract 关键字
一个抽象类的 代码示例:
abstract class ShapesClass
{
abstract public int Area();
}
class Square : ShapesClass
{
int side = 0;
public Square(int n)
{
side = n;
}
// Area method is required to avoid
// a compile-time error.
public override int Area()
{
return side * side;
}
static void Main()
{
Square sq = new Square(12);
Console.WriteLine("Area of the square = {0}", sq.Area());
}
}
abstract 也可以用来修饰成员方法,在派生类中用override 来重写该方法。
abstract 和 virtual 都可以用来修饰方法,还是有些区别的:
1) virtual方法(虚方法)
virtual 关键字用于在基类中修饰方法。virtual的使用会有两种情况:
1:在基类中定义了virtual方法,但在派生类中没有重写该虚方法。那么在对派生类实例的调用中,该虚方法使用的是基类定义的方法。
2:在基类中定义了virtual方法,然后在派生类中使用override重写该方法。那么在对派生类实例的调用中,该虚方法使用的是派生重写的方法。
2) abstract方法(抽象方法)
abstract 关键字只能用在抽象类中修饰方法,并且没有具体的实现。抽象方法的实现必须在派生类中使用override关键字来实现。
六,is 和 as 操作符
1) is 运算符:允许验证一个数据项是否属于一个特定的类型。
程序示例:
using System;
using System.Collections.Generic;
using System.Text;
namespace test_is_as
{
class Class1 { }
class Class2 { }
class Class3 : Class2 { }
class IsTest
{
static void Test(object o)
{
Class1 a;
Class2 b;
if (o is Class1)
{
Console.WriteLine("object is Class1");
a = (Class1)o;
// Do something with "a."
}
else if (o is Class2)
{
Console.WriteLine("object is Class2");
b = (Class2)o;
// Do something with "b."
}
else
{
Console.WriteLine("object is neither Class1 nor Class2.");
}
}
static void Main()
{
Class1 c1 = new Class1();
Class2 c2 = new Class2();
Class3 c3 = new Class3();
Console.WriteLine("");
Console.WriteLine("Test class 1");
Test(c1);
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine("Test class 2");
Test(c2);
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine("Test class 3");
Test(c3);
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine("Test a string");
Test("a string");
Console.ReadKey();
}
}
}
测试结果:
2) as 运算符:比 is 运算符更进了一步,它会像一次转型操作那样尝试将对象转换为另一种类型,但与转型操作不同,假如源类型不是继承链内部的类型, as 操作符会将null 赋值给目标。这种操作使程序更加的安全。
as 操作符最大的优势是转型失败后不会引发异常,可以避免使用额外的try - catch 来处理代码。
实例代码:
using System;
using System.Collections.Generic;
using System.Text;
class csrefKeywordsOperators
{
class Base
{
public override string ToString()
{
return "Base";
}
}
class Derived : Base
{
public override string ToString()
{
return "Derived";
}
}
class Program
{
static void Main()
{
Derived d = new Derived();
Base b = d as Base;
if (b != null)
{
Console.WriteLine(b.ToString());
}
Console.ReadKey();
}
}
}
代码中没有 try - catch 来对类型转换进行保护。
测试结果:
Derived