// 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