Create, Copy or View? A C# Performance Code Review Habit
A quick C# code-review checkpoint for spotting allocations, value copies, reference copies, boxing and opportunities to view existing memory with Span<T>.
When I review performance-sensitive C# code, one small question often exposes more than a long checklist: Am I creating data, copying data, or merely viewing or referencing data that already exists?
The question forces us to think about what the program is doing in memory rather than judging only by friendly-looking syntax. It connects value types, reference types, structs, classes, strings, boxing, garbage collection and Span<T> through one practical habit.
1. Creating data
var customer = new Customer();Customer is a class, so this creates a new managed object. The allocation is normal and the garbage collector will eventually manage its lifetime. The useful review question is not whether allocation is bad; it is whether this particular allocation is necessary.
for (int i = 0; i < 1_000_000; i++)
{
var customer = new Customer();
}Code-review questionDo I genuinely need one million new objects, or could an existing object, a smaller representation or a different algorithm do the job?
Creation becomes especially important inside hot loops, frequently called request paths, parsers and background processing. Measure before redesigning, but learn to notice the cost.
2. Copying values
int first = 10;
int second = first;
second = 20;The value 10 is copied into second. Changing second does not affect first. The same rule applies to a struct: assigning the struct normally copies its value.
Point first = new Point(10, 20);
Point second = first;A small struct is usually inexpensive to copy. A large struct repeatedly passed through a hot method deserves attention because the friendly assignment can hide repeated movement of data. This is a reason to inspect and measure, not a rule that all structs must be replaced with classes.
3. Copying a reference is not copying the object
var customer1 = new Customer();
var customer2 = customer1;This does not normally create a second Customer. It copies the reference, so both variables point to the same managed object. A later mutation through either reference can therefore be visible through the other.
customer1 ----\
---> Customer object
customer2 ----/Code-review questionDid I copy the data itself, or did I copy a reference to the same data?
4. Creating a result versus viewing existing memory
string name = "Afzal Ahmed";
string firstName = name.Substring(0, 5);Substring returns a new string for the selected characters on modern .NET. That may be exactly what the caller needs. But when code only needs to inspect or parse a temporary slice, creating another string can be unnecessary.
ReadOnlySpan<char> name = "Afzal Ahmed";
ReadOnlySpan<char> firstName = name[..5];Here firstName is a view over a region of existing characters. The intent is: do not give me another collection of characters; let me work with this section of the existing memory. Span<T> and ReadOnlySpan<T> are valuable in parsers and other measured hot paths, but their lifetime rules and API trade-offs mean they should be introduced deliberately.
A F Z A L A H M E D |-------| view
5. Watch for hidden creation through boxing
int number = 100;
object value = number;An int is a value type and object is a reference type. Assigning the integer to object boxes the value by creating an object representation. The syntax looks like a simple assignment, which is why the create-copy-view question is useful: did that innocent-looking line allocate something?
A compact code-review checklist
- ✓Creating: Am I allocating a new object, string, array, list or closure?
- ✓Copying: Am I duplicating a value, large struct, buffer or collection?
- ✓Reference copying: Do two variables now point to the same object?
- ✓Viewing: Could I inspect a slice of existing memory instead of creating a new result?
- ✓Hidden creation: Could boxing, LINQ, interpolation or an implicit conversion allocate?
- ✓Evidence: Is this path hot enough for the difference to matter, and have I measured it?
The lesson to keep
When you see new Customer(), think creation. When you see structB = structA, think value copy. When you see customer2 = customer1, think reference copy and the same object. When you see existingData.AsSpan(), think view over existing memory. That habit makes garbage collection, collections, LINQ, Span<T> and profiling easier to reason about.