文章目录
继承
继承的类型
- 单重继承:表示一个类可以派生自一个基类。C#就采用这种继承。
- 多重继承:多重继承允许一个类派生自多个类。C#不支持类的多重继承,但允许接口的多重继承
- 多层继承:多层继承允许继承有更大的层次结构。类B派生自类A,类C又派生自类B。其中,类B也称为中间基类,C#支持它,也很常用。
- 接口继承:定义了接口的继承。这里允许多重继承。
继承的实现
如果要声明派生自另一个类的一个类,就可以使用下面的语法:
public class MyDerivedclass: MyBaseClass
{
//members
}
对于结构,语法如下(只能用于接口继承):
publie struct MyDerivedstruct: IInterfacel, IInterface2
{
//members
}
如果在类定义中没有指定基类,C#编译器就假定System.Object是基类。因此,派生自Object类(或使用object关键字),与不定义基类的效果是相同的。
public class MyClass // implicitly derives from System.Object
{
//members
}
虚方法
把一个基类方法声明为virtual,就可以在任何派生类中重写该方法
父类的方法要加上virtual关键字
子类的方法要加上override关键字表明这是方法的重写
如果不加的话则构成方法的隐藏 此时执行那种程序将由实例决定
构成重写后每次调用 无论引用变量为何值都会去寻找此函数的最终版本
使用override修饰的方法也可以在其子类的方法中再重写不用单独加virtual
public class Position
{
public int X { get; set; }
public int Y { get; set; }
public override string ToString() => $"X: {X}, Y: {Y}";
}
public class Size
{
public int Width { get; set; }
public int Height { get; set; }
public override string ToString() => $"Width: {Width}, Height: {Height}";
}
public abstract class Shape
{
public Position Position { get; } = new Position();
public Size Size { get; } = new Size();
public virtual void Draw() => WriteLine($"Shape with {Position} and {Size}");
public virtual void Move(Position newPosition)
{
Position.X = newPosition.X;
Position.Y = newPosition.Y;
WriteLine($"moves to {Position}");
}
public abstract void Resize(int width, int height);
}
public class Rectangle : Shape
{
public override void Draw() =>
Console.WriteLine($"Rectangle with {Position} and {Size}");
public override void Move(Position newPosition)
{
Console.Write("Rectangle ");
base.Move(newPosition);
}
public override void Resize(int width, int height)
{
throw new NotImplementedException();
}
}
多态
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。换句话说,实际上就是同一个类型的实例调用"相同"的方法,产生的结果是不同的。这里的"相同"打上双引号是因为这里的相同的方法仅仅是看上去相同的方法,实际上它们调用的方法是不同的。
public class Shape
{
public int X { get; private set; }
public int Y { get; private set; }
public int Height { get; set; }
public int Width { get; set; }
// 虚方法
public virtual void Draw()
{
Console.WriteLine("执行基类的画图任务");
}
}
class Circle : Shape
{
public override void Draw()
{
base.Draw();
Console.WriteLine("画一个圆形");
}
}
class Circle : Shape
{
public override void Draw()
{
base.Draw();
Console.WriteLine("画一个圆形");
}
}
class Rectangle : Shape
{
public override void Draw()
{
base.Draw();
Console.WriteLine("画一个长方形");
}
}
class Triangle : Shape
{
public override void Draw()
{
base.Draw();
Console.WriteLine("画一个三角形");
}
}
class Program
{
static void Main(string[] args)
{
Shape circle = new Circle();
circle.Draw();
Shape rectangle = new Rectangle();
rectangle.Draw();
Shape triangle = new Triangle();
triangle.Draw();
}
}
抽象类和抽象方法
C#允许把类和方法声明为abstract。
- 抽象类不能实例化
- 抽象方法不能直接实现,必须在非抽象的派生类中重写。显然,抽象方法本身也是虚拟的。
- 如果类包含抽象方法,则该类也是抽象的,也必须声明为抽象的。
密封类和密封方法
如果不应创建派生自某个自定义类的类,该自定义类就应密封。给类添加sealed修饰符,就不允许创建诊类的子类。密封一个方法,表示不能重写该方法。
public sealed class Finalclass
{
//
}
在把类或方法标记为sealed时,最可能的情形是:如果在库、类或自己编写的其他类的操作中,类或方法是内部的,则任何尝试重写它的一些功能,都可能导致代码的不稳定。例如,也许没有测试继承,就对继承的设计决策投资。如果是这样,最好把类标记为sealed。
派生类的构造函数
在创建派生类的实例时,实际上会有多个构造函数起作用。要实例化的类的构造函数本身不能初始化类,还必须调用基类中的构造函数。
public class Rectangle : Shape
{
public Rectangle(int width, int height, int x, int y)
: base(width, height, x, y) { }
public Rectangle()
: base(width: 0, height: 0, x: 0, y: 0) { }
public override void Draw() =>
Console.WriteLine($"Rectangle with {Position} and {Size}");
public override void Move(Position newPosition)
{
Console.Write("Rectangle ");
base.Move(newPosition);
}
}
这个过程非常简洁,设计也很合理。每个构造函数都负责处理相应变量的初始化。在这个过程中,正确地实例化了类,以备使用。如果在为类编写自己的构造函数时遵循同样的规则,就会发现,即便是最复杂的类也可以顺利地初始化,并且不会出现任何问题。
修饰符
修饰符可以指定方法的可见性,如public或private;还可以指定一个项的本质,如方法是virtual或abstract。
访问修饰符
其他修饰符
接口
接口的声明
声明接口在语法上与声明抽象类完全相同,但不允许提供接口中任何成员的实现方式。
般情况下,接口只能包含方法、属性、索引器和事件的声明。
接口和抽象类
- 抽象类可以有实现代码或没有实现代码的抽象成员。然而,接口不能有任何实现代码
- 因为接口的成员总是抽象的,所以接口不需要abstract关键字。
- 接口和抽象类一样,永远不能实例化接口,它只能包含其成员的签名。此外,可以声明接口类型的变量
- 接口既不能有构造函数(如何构建不能实例化的对象?也不能有字段(因为这隐含了某些内部的实现方式)。
- 接口定义也不允许包含运算符重载。
- 在接口定义中还不允许声明成员的修饰符。接口成员总是隐式为public,不能声明为virtual。
定义和实现接口
public interface IBankAccount
{
void PayIn(decimal amount);
bool Withdraw(decimal amount);
decimal Balance { get; }
}
public interface ITransferBankAccount : IBankAccount
{
bool TransferTo(IBankAccount destination, decimal amount);
}
public class GoldAccount : IBankAccount
{
private decimal _balance;
public void PayIn(decimal amount) => _balance += amount;
public bool Withdraw(decimal amount)
{
if (_balance >= amount)
{
_balance -= amount;
return true;
}
Console.WriteLine("Withdrawal attempt failed.");
return false;
}
public decimal Balance => _balance;
public override string ToString() =>
$"Venus Bank Saver: Balance = {_balance,6:C}";
}
public class CurrentAccount : ITransferBankAccount
{
private decimal _balance;
public void PayIn(decimal amount) => _balance += amount;
public bool Withdraw(decimal amount)
{
if (_balance >= amount)
{
_balance -= amount;
return true;
}
Console.WriteLine("Withdrawal attempt failed.");
return false;
}
public decimal Balance => _balance;
public bool TransferTo(IBankAccount destination, decimal amount)
{
bool result = Withdraw(amount);
if (result)
{
destination.PayIn(amount);
}
return result;
}
public override string ToString() =>
$"Jupiter Bank Current Account: Balance = {_balance,6:C}";
}
public class SaverAccount : IBankAccount
{
private decimal _balance;
public void PayIn(decimal amount) => _balance += amount;
public bool Withdraw(decimal amount)
{
if (_balance >= amount)
{
_balance -= amount;
return true;
}
Console.WriteLine("Withdrawal attempt failed.");
return false;
}
public decimal Balance => _balance;
public override string ToString() =>
$"Venus Bank Saver: Balance = {_balance,6:C}";
}
SaverAccount派生自一个接口IBankAocount,我们没有显式指出任何其他基类(当然这表示SaverAccount直接派生自System.Object)。另外,从接口中派生完全独立于从类中派生。SaverAccount派生自IBankAccount,表示它获得了IBankAccount的所有成员,但接口实际上并不实现其方法,所以SaverAccount必须提供这些方法的所有实现代码。如果缺少实现代码,编译器就会产生错误。接口仅表示其成员的存在性,类负责确定这些成员是虚拟还是抽象的(但只有在类本身是抽象的,这些函数才能是抽象的)。
派生的接口
接口可以彼此继承,其方式与类的继承方式相同,下面通过定义一个新的ITransferBankAccount接口来说明这个概念,该接口的功能与IBankAccount相同,只是又定义了一个方法,把资金直接转到另一个账户上
public interface ITransferBankAccount:IBanknccount
{
bool TransferTo (IBankAccount estinatior,decimal amount)
}
因为ITransferBankAccount派生自IBankAccount,所以它拥有IBankAccount的所有成员和它自己的成员。这表示实现(派生自)ITransferBankAccount的任何类都必须实现IBankAccount的所有方法和在ITransferBankAccount中定义的新方法TransferToO。没有实现所有这些方法就会产生一个编译错误。
注意,TransferTo0方法对于目标账户使用了IBankAccount接口引用。这说明了接口的用途:在实现并调用这个方法时,不必知道转账的对象类型,只需要知道该对象实现IBankAccount即可下面说明ITransfcrBankAccount:假定PlanetaryBankofJupiter还提供了一个当前账户。CumrentAccount类的大多数实现代码与SaverAccount和GoldAccount的实现代码相同.
public class CurrentAccount : ITransferBankAccount
{
private decimal _balance;
public void PayIn(decimal amount) => _balance += amount;
public bool Withdraw(decimal amount)
{
if (_balance >= amount)
{
_balance -= amount;
return true;
}
Console.WriteLine("Withdrawal attempt failed.");
return false;
}
public decimal Balance => _balance;
public bool TransferTo(IBankAccount destination, decimal amount)
{
bool result = Withdraw(amount);
if (result)
{
destination.PayIn(amount);
}
return result;
}
public override string ToString() =>
$"Jupiter Bank Current Account: Balance = {_balance,6:C}";
}
is 和 as 运算符
is
is 运算符检查对象是否与给定类型兼容,并返回一个Bool值,如果一个对象是某个类型或是其父类型的话就返回为true,否则的话就会返回为false,永远不会抛出异常。如果对象引用为null,那么is操作符总是返回为false。
只适用于引用类型转换、装箱转换和拆箱转换,而不支持值类型转换
class Program
{
static void Main(string[] args)
{
object o = new object();
if (o is string)
{
string str = (string)o;
Console.WriteLine(str);
}
else
{
Console.WriteLine("转换类型失败");
}
Console.ReadKey();
}
}
as
as 运算符用于在可兼容的引用类型之间执行类似于强制类型转换的操作。与强转不同的是,当转换失败时,as 运算符将返回NULL空,而不是引发异常。因此转换是否成功可以通过结果是否为null进行判断。
aS运算符有一定的适用范围,他只适用于引用类型或可以为null的类型。
class Program
{
static void Main(string[] args)
{
object o = new object();
string str = o as string;
if (str == null)
{
Console.WriteLine("转换类型失败");
}
else
{
Console.WriteLine("转换类型成功");
}
Console.ReadKey();
}
}
在类层次结构内部的类型转换不会抛出基于类型转换的异常,且使用is和as运算符都是可行的。