Lab: Generics#
Objective#
In this lab, we will apply our understanding of how to use a generic type that someone has defined to reduce redundancy in code while maintaining type-safety. In this case we’ll introduce the generic class Pair<T>
.
Provided Code#
Carefully review the provided code. Notice the redundancy in the DiceRollPair
and CardPair
classes and how DiceRoll
and Card
classes are coupled with them.
class DiceRoll
{
public int Value { get; set; }
}
class Card
{
public string Suit { get; set; }
public string Rank { get; set; }
}
class DiceRollPair
{
public DiceRoll Item1 { get; set; }
public DiceRoll Item2 { get; set; }
static readonly Random random = new Random();
public static DiceRollPair PickRandom(DiceRoll[] diceRolls)
=> new DiceRollPair
{
Item1 = diceRolls[random.Next(diceRolls.Length)],
Item2 = diceRolls[random.Next(diceRolls.Length)]
};
}
class CardPair
{
public Card Item1 { get; set; }
public Card Item2 { get; set; }
static readonly Random random = new Random();
public static CardPair PickRandom(Card[] cards)
=> new CardPair
{
Item1 = cards[random.Next(cards.Length)],
Item2 = cards[random.Next(cards.Length)]
};
}
DiceRoll[] diceRolls = new DiceRoll[]
{
new DiceRoll { Value = 1 },
new DiceRoll { Value = 2 },
new DiceRoll { Value = 3 },
new DiceRoll { Value = 4 },
new DiceRoll { Value = 5 },
new DiceRoll { Value = 6 }
};
Card[] cards = new Card[]
{
new Card { Suit = "Hearts", Rank = "Jack" },
new Card { Suit = "Hearts", Rank = "Queen" },
new Card { Suit = "Hearts", Rank = "King" },
new Card { Suit = "Hearts", Rank = "Ace" },
// ...
};
var randomDiceRollPair = DiceRollPair.PickRandom(diceRolls);
var randomCardPair = CardPair.PickRandom(cards);
Console.WriteLine("Random Dice Roll Pair:");
Console.WriteLine($"{randomDiceRollPair.Item1.Value}");
Console.WriteLine($"{randomDiceRollPair.Item2.Value}");
Console.WriteLine("Random Card Pair:");
Console.WriteLine($"{randomCardPair.Item1.Rank} of {randomCardPair.Item1.Suit}");
Console.WriteLine($"{randomCardPair.Item2.Rank} of {randomCardPair.Item2.Suit}");
Random Dice Roll Pair:
2
4
Random Card Pair:
Ace of Hearts
Ace of Hearts
Instructions#
Step 1: Introduce a non-generic ObjectPair
class#
Start by creating a non-generic class ObjectPair
. This will replace the need for separate pair classes for DiceRoll
and Card
but will not be compile-time type-safe.
class ObjectPair
{
public object Item1 { get; set; }
public object Item2 { get; set; }
static readonly Random random = new Random();
public static ObjectPair PickRandom(object[] items)
=> new ObjectPair
{
Item1 = items[random.Next(items.Length)],
Item2 = items[random.Next(items.Length)]
};
}
🤔 Reflection
Why is it not compile-time type-safe?
Step 2: Refactor Main
#
Delete the old classes DiceRollPair
and CardPair
.
Minimally rewrite the Main
method so that we make use of ObjectPair
instead of the two classes DiceRollPair
and CardPair
.
Warning
This step requires downcasting.
🤔 Reflection
Did we eliminate the duplication? Why did we loose compile-time type-safety?
Step 3: Introduce a Generic Pair
Class#
Add the generic class Pair<T>
. This will replace the need for separate pair classes for DiceRoll
and Card
and will be compile-time type-safe.
class Pair<T>
{
public T Item1 { get; set; }
public T Item2 { get; set; }
static readonly Random random = new Random();
public static Pair<T> PickRandom(T[] items)
=> new Pair<T>
{
Item1 = items[random.Next(items.Length)],
Item2 = items[random.Next(items.Length)]
};
}
Step 4: Refactor Main
#
Delete the class ObjectPair
.
Minimally rewrite the Main
method to make use of Pair<T>
instead of ObjectPair
.
Important
You should no longer need to use downcasting.
🤔 Reflection
How does the use of generics enhance code maintainability and reduce redundancy in this scenario? Why did we regain compile-time type-safety?
Challenge#
Extend Functionality: Override the
ToString()
method inPair<T>
. It should print the details of both the objects in the pair. Tip: All types in C# support theToString()
method so it can be called on any object irrespectively of its type.Nested Generics: Create a
Pair<Pair<T>>
instance in theMain
method and experiment with nesting generics.Replace arrays with List
: Use the generic type List<T>
instead of arrays in theMain
method as well as inPair<T>
.
🤔 Reflection
Consider the advantages of using a Pair<T>
class. Would implementing such generic classes be feasible or beneficial in a more extensive, real-world project setting? Reflect on scenarios where using generics could be disadvantageous.