Implement Generics and Anonymous Types in a Banking Application
Generics and anonymous types are powerful tools in C# that allow you to create reusable, type-safe, and efficient code. In this exercise, you will explore how generics enable you to work with collections and methods in a type-safe manner, while anonymous types allow you to group related data into temporary objects without defining a full class. You will implement these concepts in a banking application by defining an enum for account types, a struct for transactions, and a record for customer details. Additionally, you will implement a class to manage bank accounts and use anonymous types to summarize transaction details efficiently.
This exercise takes approximately 25 minutes to complete.
Before you start
Before you can start this exercise, you need to:
-
Ensure that you have the latest short-term support (STS) version of the .NET SDK installed on your computer. You can download the latest versions of the .NET SDK using the following URL: Download .NET
-
Ensure that you have Visual Studio Code installed on your computer. You can download Visual Studio Code using the following URL: Download Visual Studio Code
-
Ensure that you have the C# Dev Kit configured in Visual Studio Code.
For additional help configuring the Visual Studio Code environment, see Install and configure Visual Studio Code for C# development
Exercise scenario
Suppose you’re a software developer at a tech company working on a new project. Your team needs to implement a banking application that uses generics and anonymous types to manage account types, transactions, and customer details. To ensure consistent behavior, you decide to create and implement these features in a simple console application.
You’ve developed an initial version of the app that includes the following files:
Program.cs
: This file contains the main entry point of the application, demonstrating how to use generics and anonymous types in a banking application.Bank.cs
: This file defines theAccountType
enum,Transaction
struct,Customer
record, andBankAccount
class.
This exercise includes the following tasks:
- Define the
AccountType
enum andTransaction
struct. - Define the
Customer
record. - Implement the
BankAccount
class. - Use anonymous types to summarize transaction details.
- Demonstrate record comparison and the immutability of structs.
- Display basic bank account information.
- Perform transactions.
- Display transaction history.
Review the current version of your project
In this task, you download the existing version of your project and review the code.
Use the following steps to complete this section of the exercise:
-
Download the starter code from the following URL: Implement Collection Types - exercise code projects
-
Extract the contents of the LP4SampleApps.zip file to a folder location on your computer.
-
Expand the LP4SampleApps folder, and then open the
Data_M4
folder.The Data_M4 folder contains the following code project folders:
- Solution
- Starter
The Starter folder contains the starter project files for this exercise.
-
Use Visual Studio Code to open the Starter folder.
-
In the EXPLORER view, collapse the STARTER folder, select SOLUTION EXPLORER, and expand the Data_M4 project.
You should see the following project files:
- Program.cs
- Bank.cs
-
Take a few minutes to open and review the Program.cs and Bank.cs files.
Program.cs
: This file contains the main entry point of the application, demonstrating how to use generics and anonymous types in a banking application.Bank.cs
: This file defines theAccountType
enum,Transaction
struct,Customer
record, andBankAccount
class.
-
Run the app and review the output in the terminal window.
To run your app, right-click the Data_M4 project in the Solution Explorer, select Debug, and then select Start New Instance.
[!TIP] If you encounter any issues while completing this exercise, review the provided code snippets and compare them to your own code. Pay close attention to the syntax and structure of the code. If you’re still having trouble, you can review the solution code in the sample apps that you downloaded at the beginning of this exercise. To view the Data_M4 solution, navigate to the LP4SampleApps/Data_M4/Solution folder and open the Solution project in Visual Studio Code.
Task 1: Define the AccountType
Enum and Transaction
Struct
In this task, you’ll define an enum named AccountType
to represent different types of bank accounts and a struct named Transaction
to represent individual transactions. These are foundational building blocks for creating reusable and type-safe code in C#. The Transaction
struct will later be used in a generic collection to manage transactions efficiently.
Steps
-
Open the
Bank.cs
file in the Starter folder. -
Locate the placeholder comment for Task 1: Step 1.
- Define an enum named
AccountType
with the following values:Checking
Savings
Business
public enum AccountType { Checking, Savings, Business }
-
Locate the placeholder comment for Task 2: Step 1.
- Define a struct named
Transaction
with the following:- Properties:
Amount
(type:double
)Date
(type:DateTime
)Description
(type:string
)
- A constructor to initialize the properties.
- An overridden
ToString
method to format transaction details.
public struct Transaction { public double Amount { get; } public DateTime Date { get; } public string Description { get; } public Transaction(double amount, DateTime date, string description) { Amount = amount; Date = date; Description = description; } public override string ToString() { return $"{Date.ToShortDateString()}: {Description} - {Amount:C}"; } }
- Properties:
- Save your changes.
What you should see
The AccountType
enum defines a set of named constants (Checking
, Savings
, Business
) that represent different types of bank accounts. The Transaction
struct represents individual transactions with properties for the amount, date, and description. It uses a constructor for initialization and overrides the ToString
method to format transaction details for display.
These definitions will later be used in generic collections to manage transactions in a type-safe way and in methods that summarize transaction details using anonymous types.
Task 2: Define the Customer
Record
In this task, you’ll define a record named Customer
to represent customer details such as their name, ID, and address. Records in C# are immutable by default, making them ideal for representing data that shouldn’t change after it’s created. This record will later be used to associate accounts with customers in the BankAccount
class.
Steps
-
In Bank.cs, locate the placeholder comment for Task 3: Step 1.
- Define a record named
Customer
with the following properties:Name
(type:string
)CustomerId
(type:string
)Address
(type:string
)
public record Customer { public string Name { get; init; } public string CustomerId { get; init; } public string Address { get; init; } }
- Save your changes.
What you should see
The Customer
record defines an immutable data structure with properties for the customer’s name, ID, and address. This record will later be used in the BankAccount
class to associate accounts with customers and in methods that demonstrate record comparison. Its immutability ensures that customer data remains consistent throughout the application.
Task 3: Implement the BankAccount
Class
In this task, you’ll implement a class named BankAccount
to manage bank accounts and their associated transactions. The BankAccount
class will include properties for account details, methods for deposits and withdrawals, and functionality to display account information. It will also use a generic collection (List<Transaction>
) to store transactions, ensuring type safety and reusability.
Steps
-
In
Bank.cs
, locate the placeholder comment for Task 3: Step 1. - Define the
BankAccount
class and add the following properties:AccountNumber
(type:int
)Balance
(type:double
)AccountHolder
(type:Customer
)Type
(type:AccountType
)
public class BankAccount { public int AccountNumber { get; } public double Balance { get; private set; } public Customer AccountHolder { get; } public AccountType Type { get; } }
-
Locate the placeholder comment for Task 3: Step 2.
-
Add a private list to track transactions.
private List<Transaction> _transactions = new();
-
Locate the placeholder comment for Task 3: Step 3.
-
Add a constructor to initialize the properties.
public BankAccount(int accountNumber, double initialBalance, Customer accountHolder, AccountType type) { AccountNumber = accountNumber; Balance = initialBalance; AccountHolder = accountHolder; Type = type; }
-
Locate the placeholder comment for Task 3: Step 4.
- Add a
Deposit
method to:- Add the specified amount to the balance.
- Create a new
Transaction
object and add it to the_transactions
list.
public void Deposit(double amount, string description) { if (amount > 0) { Balance += amount; _transactions.Add(new Transaction(amount, DateTime.Now, description)); } }
-
Locate the placeholder comment for Task 3: Step 5.
- Add a
Withdraw
method to:- Subtract the specified amount from the balance if sufficient funds are available.
- Create a new
Transaction
object and add it to the_transactions
list.
public bool Withdraw(double amount, string description) { if (amount > 0 && Balance >= amount) { Balance -= amount; _transactions.Add(new Transaction(-amount, DateTime.Now, description)); return true; } return false; }
-
Locate the placeholder comment for Task 3: Step 6.
-
Add a
DisplayAccountInfo
method to return a formatted string with account details.public string DisplayAccountInfo() { return $"Account Number: {AccountNumber}, Balance: {Balance:C}, Account Holder: {AccountHolder.Name}, Type: {Type}"; }
-
Locate the placeholder comment for Task 3: Step 7.
-
Add a
DisplayTransactions
method to iterate through the_transactions
list and display each transaction using theToString
method.public void DisplayTransactions() { Console.WriteLine("Transaction History:"); foreach (var transaction in _transactions) { Console.WriteLine(transaction); } }
- Save your changes.
What you should see
The BankAccount
class defines a reusable and type-safe structure for managing bank accounts and their transactions. It uses a generic collection (List<Transaction>
) to store transactions. The methods in this class allow you to perform deposits and withdrawals, display account information, and list all transactions.
Task 4: Implement the BankAccount
Class
In this task, you’ll continue implementing the BankAccount
class to manage bank accounts and their transactions. You’ll add properties, a constructor, and methods for deposits, withdrawals, and displaying account information. This task demonstrates the use of generic collections (List<Transaction>
) to store transactions in a type-safe and reusable way, as well as encapsulation to protect the internal state of the class.
Steps
-
Open the
Bank.cs
file in the Starter folder. -
Locate the placeholder comment for Task 4: Step 1.
- Add the following properties to the
BankAccount
class:AccountNumber
(type:int
, read-only)Balance
(type:double
, private set)AccountHolder
(type:Customer
, read-only)Type
(type:AccountType
, read-only)
public int AccountNumber { get; } public double Balance { get; private set; } public Customer AccountHolder { get; } public AccountType Type { get; }
-
Locate the placeholder comment for Task 4: Step 2.
-
Add a constructor to initialize the properties.
public BankAccount(int accountNumber, double initialBalance, Customer accountHolder, AccountType type) { AccountNumber = accountNumber; Balance = initialBalance; AccountHolder = accountHolder; Type = type; }
-
Locate the placeholder comment for Task 4: Step 3.
- Add a
Deposit
method to:- Add the specified amount to the balance.
- Create a new
Transaction
object and add it to the_transactions
list.
public void Deposit(double amount, string description) { if (amount > 0) { Balance += amount; _transactions.Add(new Transaction(amount, DateTime.Now, description)); } }
-
Locate the placeholder comment for Task 4: Step 4.
- Add a
Withdraw
method to:- Subtract the specified amount from the balance if sufficient funds are available.
- Create a new
Transaction
object and add it to the_transactions
list.
public bool Withdraw(double amount, string description) { if (amount > 0 && Balance >= amount) { Balance -= amount; _transactions.Add(new Transaction(-amount, DateTime.Now, description)); return true; } return false; }
-
Locate the placeholder comment for Task 4: Step 5.
-
Add a
DisplayAccountInfo
method to return a formatted string with account details.public string DisplayAccountInfo() { return $"Account Number: {AccountNumber}, Balance: {Balance:C}, Account Holder: {AccountHolder.Name}, Type: {Type}"; }
-
Locate the placeholder comment for Task 4: Step 6.
-
Add a private list to track transactions.
private List<Transaction> _transactions = new();
-
Locate the placeholder comment for Task 4: Step 7.
-
Add a
DisplayTransactions
method to iterate through the_transactions
list and display each transaction using theToString
method.public void DisplayTransactions() { Console.WriteLine("Transaction History:"); foreach (var transaction in _transactions) { Console.WriteLine(transaction); } }
- Save your changes.
What you should see
The BankAccount
class now includes properties for account details, methods for deposits and withdrawals, and functionality to display account information. It also uses a generic collection (List<Transaction>
) to store transactions. These additions make the BankAccount
class a reusable and type-safe structure for managing bank accounts and their transactions.
Task 5: Display Basic Bank Account Information
In this task, you’ll create a BankAccount
object and display its basic information, including the account number, balance, account holder, and account type. This task highlights the use of records (e.g., Customer
) for immutable data structures and demonstrates how to initialize and use the BankAccount
class effectively.
Steps
-
Open the
Program.cs
file in the Starter folder. -
Locate the placeholder comment for Task 5.
- Create a
Customer
object to represent the account holder. Use the following properties:Name
:"John Doe"
CustomerId
:"C12345"
Address
:"123 Main St"
Customer customer = new Customer { Name = "John Doe", CustomerId = "C12345", Address = "123 Main St" };
- Create a
BankAccount
object using the following parameters:AccountNumber
:12345678
InitialBalance
:500
AccountHolder
: TheCustomer
object you just created.Type
:AccountType.Checking
BankAccount account = new BankAccount(12345678, 500, customer, AccountType.Checking);
-
Display the account information by calling the
DisplayAccountInfo
method.Console.WriteLine(account.DisplayAccountInfo());
- Save your changes.
What you should see
When you run the application, the console should display the basic information for the bank account, including the account number, balance, account holder’s name, and account type. For example:
Account Number: 12345678, Balance: $500.00, Account Holder: John Doe, Type: Checking
Task 6: Perform Transactions
In this task, you’ll enhance the functionality of the BankAccount
class by performing transactions. Specifically, you’ll add a deposit and a withdrawal to the account. Each transaction will update the account balance and be recorded in the _transactions
list using the Transaction
struct. This task demonstrates how to use generic collections to manage transaction data and encapsulate logic for updating account balances and recording transaction details.
Steps
-
Open the
Program.cs
file in the Starter folder. -
Locate the placeholder comment for Task 6.
- Add a deposit to the account using the
Deposit
method. Use the following parameters:Amount
:200
Description
:"Paycheck"
account.Deposit(200, "Paycheck");
-
Display the updated account information by calling the
DisplayAccountInfo
method.Console.WriteLine("After deposit:"); Console.WriteLine(account.DisplayAccountInfo());
- Add a withdrawal from the account using the
Withdraw
method. Use the following parameters:Amount
:100
Description
:"Groceries"
bool success = account.Withdraw(100, "Groceries");
-
Display whether the withdrawal was successful or not.
Console.WriteLine(success ? "Withdrawal successful." : "Withdrawal failed.");
-
Display the updated account information by calling the
DisplayAccountInfo
method again.Console.WriteLine("After withdrawal:"); Console.WriteLine(account.DisplayAccountInfo());
- Save your changes.
What you should see
When you run the application, the console should display the updated account information after the deposit and withdrawal. For example:
After deposit:
Account Number: 12345678, Balance: $700.00, Account Holder: John Doe, Type: Checking
Withdrawal successful.
After withdrawal:
Account Number: 12345678, Balance: $600.00, Account Holder: John Doe, Type: Checking
Task 7: Display Transaction History
In this task, you’ll display the transaction history for a bank account. This task demonstrates how to iterate through a generic collection (List<Transaction>
) and format data for display. It also highlights the importance of designing reusable methods, such as DisplayTransactions
, to simplify code and improve readability.
Steps
-
Open the
Program.cs
file in the Starter folder. -
Locate the placeholder comment for Task 7.
-
Call the
DisplayTransactions
method on theBankAccount
object to display the transaction history.account.DisplayTransactions();
-
Save your changes.
What you should see
When you run the application, the console should display the transaction history for the bank account. For example:
Transaction History:
4/2/2025: Paycheck - $200.00
4/2/2025: Groceries - -$100.00
This output shows the date, description, and amount for each transaction in the account.
Check your work
After completing all the tasks, review your code to ensure it matches the provided instructions. Pay close attention to the following:
- The
BankAccount
class includes properties, methods, and a generic collection (List<Transaction>
) to manage transactions. - The
Customer
record andTransaction
struct are implemented correctly and used in theBankAccount
class. - The
Program.cs
file demonstrates the creation of aBankAccount
object, performs transactions, and displays transaction history.
If you encounter any issues, compare your code with the solution files provided in the Solution folder. Debugging and troubleshooting your code is an important part of the learning process.
Run the project
To run your project and view the output:
- Save all your changes.
- Open the terminal in Visual Studio Code.
-
Run the project by pressing F5 or using the following command in the terminal:
dotnet run
When you run the project, you should see output similar to the following:
Welcome to the Bank App!
Account Number: 12345678, Balance: $500.00, Account Holder: John Doe, Type: Checking
After deposit:
Account Number: 12345678, Balance: $700.00, Account Holder: John Doe, Type: Checking
Withdrawal successful.
After withdrawal:
Account Number: 12345678, Balance: $600.00, Account Holder: John Doe, Type: Checking
Transaction History:
4/2/2025: Paycheck - $200.00
4/2/2025: Groceries - ($100.00)
If your output differs, review your code and ensure it matches the instructions provided in the tasks.
Clean up
Now that you’ve finished the exercise, consider the following steps:
- Archive your project files for future reference. This will allow you to revisit your work and track your progress over time.
- Review the solution files provided in the Solution folder to compare your implementation with the intended solution.
- Reflect on what you’ve learned in this exercise, including:
- Using generics and anonymous types to build reusable and type-safe code.
- Designing immutable data structures with records and structs.
- Encapsulating logic in classes to manage state and behavior effectively.
By completing this exercise, you’ve gained valuable experience with generics, anonymous types, and object-oriented programming in C#. Keep practicing these concepts to further strengthen your skills.