Reading — step 1 of 6
Learn
Generic Classes and Methods
You already know List<int> and Dictionary<string, int>. Generics are the machinery behind those angle brackets — and you can build your own.
The problem generics solve
Before generics, reusable containers stored object:
ArrayList list = new ArrayList();
list.Add(42);
int n = (int)list[0]; // cast required — a runtime gamble
string s = (string)list[0]; // compiles fine, EXPLODES at runtime
Two costs: every read needs a cast the compiler cannot check, and value types get boxed (wrapped in a heap object) on the way in. Generics fix both — the type parameter makes the element type part of the compile-time contract.
class Box<T> {
public T Value { get; set; }
public Box(T v) { Value = v; }
}
var b = new Box<string>("hi");
string s = b.Value; // no cast — the compiler KNOWS Value is a string
T is a type parameter: a placeholder filled in at the use site. Box<int> and Box<string> are two distinct types generated from one definition.
Generic methods
Methods can have their own type parameters, usually inferred from the arguments:
static T FirstOr<T>(List<T> list, T fallback) {
return list.Count > 0 ? list[0] : fallback;
}
int x = FirstOr(new List<int> { 3, 4 }, -1); // T inferred as int
Constraints — the trap and the fix
Inside a generic method the compiler only lets you do things that work for EVERY possible T. So this does not compile:
static T Largest<T>(List<T> list) {
T m = list[0];
foreach (var x in list)
if (x.CompareTo(m) > 0) m = x; // error: T has no CompareTo
return m;
}
The fix is a where constraint — it narrows what T can be, and in exchange unlocks that type's members:
static T Largest<T>(List<T> list) where T : IComparable<T> {
T m = list[0];
foreach (var x in list)
if (x.CompareTo(m) > 0) m = x; // OK now
return m;
}
Common constraints:
// where T : class — reference type
// where T : struct — value type
// where T : new() — has a parameterless ctor (enables new T())
// where T : SomeBase — inherits from SomeBase
// where T : IComparable<T> — implements the interface
Same story with operators: x == m on an unconstrained T is a compile error, and default(T) is how you spell "the zero value of T" (null for reference types, 0 for numbers).
Multiple type parameters
class Pair<A, B> {
public A First;
public B Second;
public Pair(A a, B b) { First = a; Second = b; }
public override string ToString() => $"({First}, {Second})";
}
One footnote worth knowing: C# generics are reified — List<int> really exists at runtime, unlike Java, where erasure turns every List<T> into a raw List.
Your exercise
Build exactly that Pair<A, B>: public fields First and Second, a two-argument constructor, and ToString() returning the pair in parentheses. In Main, construct new Pair<string, int>("Ada", 36) and pass it to Console.WriteLine. The grader expects exactly:
(Ada, 36)
The two mistakes the grader catches every time:
- Writing
public string ToString()withoutoverride. It compiles (with a warning), butConsole.WriteLinestill calls the originalobject.ToString(), so instead of your text it prints the type name:
Pair`2[System.String,System.Int32]
- Format drift: it is
(Ada, 36)— comma, then ONE space.$"({First}, {Second})"gets it right;(Ada,36)fails the test.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…