// Exercise from Lecture 9, C# Revision: extended bank account
// Additionally uses:
// . refined access modifiers
// . hiding methods of the base-class
// . inheritance to add an overdraft facility
// . automatic properties to implement Overdraft
// . exception to handle insufficient balance
// . overloading (on constructors)
// . static fields (auto generate account number)
//
// For guidance on tags in documentation check:
// http://msdn.microsoft.com/en-us/library/5ast78ax%28v=vs.90%29.aspx
// This version additionally uses Code Contracts for pre- and post-conditions.
// -----------------------------------------------------------------------------
using System;
using System.Diagnostics.Contracts;
// define an exception, triggered by the balance being too low
public class InsufficientBalance : System.Exception {
public InsufficientBalance(string msg) :base(msg) {
}
}
// currency conversion not available for this account
public class NoConversion : System.Exception {
public NoConversion(string msg) :base(msg) {
}
}
// define an exception, triggered by an invariant violation
public class InvariantViolation : System.Exception {
public InvariantViolation(string msg) :base(msg) {
}
}
// the basic accoount
class BankAccount {
// a static field, holding the latest account number
protected static ulong latestAccountNo = 1000; // 29857243
// fields; protected, to be visible in derived classes
protected ulong accountNo;
protected decimal balance;
protected string name;
//
// constructor (overloaded): auto assign account number
//
// Name of the account
public BankAccount(string name) {
latestAccountNo++;
this.accountNo = latestAccountNo;
this.name = name;
this.balance = 0M;
}
//
// constructor (overloaded): fixed account number
//
// Account number
// Name of the account
public BankAccount(ulong no, string name) {
this.accountNo = no;
this.name = name;
this.balance = 0M;
}
//
// depositing money into the account
//
// amount to deposit
// x>=0
public void Deposit(decimal x) {
Contract.Requires( x >= 0 );
this.balance += x;
}
//
// withdrawing money from the account
//
// amount to withdraw from the account
// x>=0
// this.balance>=0
// NB: we want to override this for a ProperBankAccount, hence virtual
public virtual void Withdraw(decimal x) {
Contract.Requires( x >= 0 );
if (this.balance >= x) {
this.balance -= x;
} else {
throw new InsufficientBalance(String.Format("Balance too low: {0}", this.balance));
}
Contract.Ensures( this.balance >= 0 );
}
// read balance field
public decimal GetBalance() { return this.balance; }
// show balance field
public void ShowBalance() {
Console.WriteLine("Current Balance: " + this.balance.ToString());
}
// show details of the account
public virtual void ShowAccount() {
Console.WriteLine("Account Number: {0}\tAccount Name: {1}\tCurrent Balance: {2}",
this.accountNo, this.name, this.balance.ToString());
}
public virtual double ConvertToGBP() { throw new NoConversion("Currency conversion not implemented for a (basic) BankAccount"); }
// Class invariant (using Code Contracts):
// invariant: this.balance >= 0
[ContractInvariantMethod]
public void ObjectInvariant () {
Contract.Invariant ( this.balance >= 0 );
}
// -----------------------------------------------------------------------------
public void RunTrans() { // works on BankAccount and ProperBankAccount
this.ShowAccount();
this.ShowBalance();
Console.WriteLine("Depositing " + 600);
this.Deposit(600);
this.ShowBalance();
Console.WriteLine("Withdrawing " + 400);
try {
this.Withdraw(400);
} catch (InsufficientBalance e) {
Console.WriteLine("InsufficientBalance {0} for withdrawl of {1}", this.GetBalance(), 400);
}
this.ShowBalance();
Console.WriteLine("Withdrawing " + 400);
try {
this.Withdraw(400);
} catch (InsufficientBalance e) {
Console.WriteLine("InsufficientBalance {0} for withdrawl of {1}", this.GetBalance(), 400);
}
this.ShowBalance();
this.ShowAccount();
}
}
// -----------------------------------------------------------------------------
class ProperBankAccount: BankAccount {
// overdraft field, with default get and set properties
public decimal overdraft { get ; set ; }
// constructor (overloaded)
public ProperBankAccount(string name) :base(name) {
// nothing; use set property on overdraft
}
// constructor (overloaded): fixed account number
public ProperBankAccount(ulong no, string name) :base(no,name) {
// nothing; use set property on overdraft
}
// Deposit is inherited from BankAccount
// NB: override Withdraw to account for overdraft
public override void Withdraw(decimal x) {
Contract.Requires( x >= 0 );
if (this.balance+this.overdraft >= x) {
this.balance -= x;
} else {
throw new InsufficientBalance(String.Format("Balance (including overdraft of {0}) too low: {1}", this.overdraft, this.balance));
}
Contract.Ensures( this.balance + this.overdraft >= 0 );
}
// GetBalance and ShowBalance are inherited
// override ShowAccount
public override void ShowAccount() {
base.ShowAccount();
Console.WriteLine("\twith an overdraft of {0}", this.overdraft);
}
public override double ConvertToGBP() { return (double)this.balance; }
// Class invariants (using Code Contracts):
// invariant: this.balance >= - this.overdraft
[ContractInvariantMethod]
public void ObjectInvariant () {
Contract.Invariant ( this.balance >= - this.overdraft );
}
}
// -----------------------------------------------------------------------------
class ForeignCurrencyAccount: ProperBankAccount {
// currency
public string currency { get ; set ; }
// exchange rate
public double rate { get ; set ; }
public ForeignCurrencyAccount(string name, string cur, double rate): base(name) { this.currency = cur; this.rate = rate; }
public override double ConvertToGBP() { return (double)this.balance*this.rate; }
public override void ShowAccount() {
base.ShowAccount();
Console.WriteLine("\tthe currency on this account is {0} with an exchange rate {0}->GBP of 1:{1}", this.currency, this.rate);
Console.WriteLine("\tthe balance in GBP is {0}", ConvertToGBP());
}
// Class invariants (using Code Contracts):
// invariant: this.balance >= - this.overdraft
[ContractInvariantMethod]
public void ObjectInvariant () {
Contract.Invariant ( this.balance >= - this.overdraft );
}
}
// -----------------------------------------------------------------------------
class Tester {
// a class for running tests from the Main method
class RunTester {
// RunTransactions works on BankAccount and ProperBankAccount
public void RunTransactions(BankAccount acct) {
// if it has an overdraft facility, initialise its value
ProperBankAccount pacct = acct as ProperBankAccount;
if (pacct != null) {
pacct.overdraft = 200M;
}
/* or:
if (acct is ProperBankAccount) {
((ProperBankAccount)acct).overdraft = 200;
}
*/
acct.ShowAccount();
acct.ShowBalance();
// first, deposit something
decimal x = 600M;
Console.WriteLine("Depositing " + x);
acct.Deposit(x);
acct.ShowBalance();
// then, try to withdraw something
decimal y = 400M;
Console.WriteLine("Withdrawing " + y);
try {
acct.Withdraw(y);
} catch (InsufficientBalance e) {
Console.WriteLine("InsufficientBalance {0} for withdrawl of {1}", acct.GetBalance(), y);
}
acct.ShowBalance();
// then, try to withdraw the same amount again
Console.WriteLine("Withdrawing " + y);
try {
acct.Withdraw(y);
} catch (InsufficientBalance e) {
Console.WriteLine("InsufficientBalance {0} for withdrawl of {1}", acct.GetBalance(), y);
}
acct.ShowBalance();
acct.ShowAccount();
}
}
public static void Main(){
RunTester t = new RunTester();
// create a basic account
BankAccount mine = new BankAccount("MyAccount");
// create a proper account
ProperBankAccount mineOvdft = new ProperBankAccount("MyProperAccount");
// create a proper account
ForeignCurrencyAccount foreign = new ForeignCurrencyAccount("ProperBankAccount", "EUR", 0.80);
// collect them in an array
BankAccount[] accts = new BankAccount[3] { mine, mineOvdft, foreign };
for (int i=0; i