Type parameters
A box that holds one value is useful for any type. Writing IntBox and StringBox repeats the same code. One box of object fits everything but loses type checks and needs casts. Generics give both: one definition, fully typed.
class Box<T> declares T, a type parameter: a placeholder for a type that whoever uses Box picks later. Inside the class, T works like any real type, so the field Value has type T.
Build: one definition, many types
Line 1 writes Box<int>, so int is the type argument. The compiler puts int wherever the definition says T, making a constructed type, Box<int>, whose Value is an int. new(41) passes 41 to its constructor.
Line 2 uses the same definition with the type argument string. That makes a second constructed type, Box<string>, whose Value is a string. Box<T> was written once and now serves two types; it could serve any type.
The compiler knows a.Value is an int and b.Value is a string, so lines 3 and 4 need no casts. With a box of object you would write (int)box.Value, and a wrong cast would only fail when the program runs.
Add the line a.Value = "oops"; and the build stops with error CS0029: cannot convert string to int. A Box<int> only holds ints, and the compiler checks that before the program ever runs.
A constraint limits which types may replace T. With class Box<T>(T v) where T : struct, only value types such as int fit, so Box<string> fails with error CS0453. In exchange, the class may use whatever the constraint promises.
Run: objects and code
dotnet run builds, then runs. Lines 1 and 2 each make an object on the heap: a refers to a Box<int> whose Value is 41, and b to a Box<string> whose Value refers to the string "hi".
Line 3 reads a.Value, 41, adds 1 and stores 42 in n, an int. Line 4 copies b.Value into s, a string. Nothing is cast: the compiler already proved that the types match.
Line 5 builds the text from n and s with string interpolation, $"{n} {s}", and Console.WriteLine prints it with a new line after it: 42 hi.
The assembly stores Box<T> once, as IL with T still open. At run time the CLR makes machine code for Box<int> with int filled in. All reference types, such as string, share one copy of the code.
What happens
// step 1/11 · why
class IntBox { public int Value; } copy 1
class StringBox { public string Value; } copy 2, same code
class ObjBox { public object Value; } one class, but every read
needs a cast: (int)box.Value
generic class Box<T> ... one class, every type, typed
class Box<T>(T v) uses a primary constructor (C# 12). The older form is a constructor written inside the class: public Box(T v) { Value = v; }. The CLR shares one copy of machine code among reference types because every reference is the same size. Each value type gets its own copy, so Box<int> stores and reads an int directly, without boxing.