Small, runnable C# mistakes - each exhibit is a bug you can run, its mirror fix, and the mechanic behind it.
88 exhibits in 27 halls, latest - #0088.
- 0001 never modify a collection while iterating it
- 0004 never mutate an object that serves as a dictionary key
- 0030 never write through a covariant array reference (@tygronia)
- 0073 never treat
new List<T>(n)as n slots - n is capacity, Count is 0 - 0074 never
ToDictionaryby a key that can repeat - a duplicate throws; useGroupBy/ToLookup - 0087 never trust
readonlyto freeze an array - it guards the reference, not the elements
- 0002 never use
doublefor money - 0025 never assume
Math.Roundrounds half up - 0029 never divide by a value that can be zero
- 0050 never trust a wide target type to fix
intmath - cast one operand first (@palkotnyk) - 0055 never key or compare a
decimalby its text - equal values keep different scales (1.5mvs1.50m) - 0072 never assume int math throws on overflow - it wraps; use
checked
- 0003 never mutate shared state without synchronization
- 0007 never write
async voidoutside event handlers - 0016 never accept a token you don't pass down or check
- 0018 never mistake a collection of tasks for a collection of results
- 0019 never drop a Task - await it or hand it to someone who will
- 0021 never trust
await Task.WhenAllto report more than one failure - 0031 never hand an async lambda to
Parallel.ForEach(@palkotnyk) - 0035 never block on async code with
.Resultor.Wait() - 0036 never elide
awaitinside a using or try/finally block - 0037 never launch an async lambda with
Task.Factory.StartNew- useTask.Run - 0086 never pass an
asynclambda where anActionis expected - it becomesasync void, fire-and-forget
- 0006 never close over a loop variable - capture a copy
- 0009 never enumerate a LINQ query twice - materialize it once
- 0013 never dedupe objects that don't define equality
- 0057 never use
Exceptto filter a list - set operators return distinct results, dropping duplicates
- 0010 never subscribe to a long-lived event without unsubscribing
- 0023 never unsubscribe with a lambda - name the handler
- 0052 never raise an event unguarded - one throwing handler aborts the whole invocation list
- 0083 never raise an event as
E(args)- with no subscribers it is a null delegate; useE?.Invoke(args)
- 0011 never write a mutable struct
- 0054 never call
new Guid()- it is empty; useGuid.NewGuid() - 0075 never rely on a struct's ctor for
defaultor arrays - reference fields come back null
- 0005 never rethrow with
throw ex- use barethrow - 0015 never let a catch-all eat
OperationCanceledException - 0017 never let a
finallyblock throw - 0076 never throw while you still hold a resource you acquired earlier - release it first
- 0077 never put must-run cleanup only in
finally-Environment.ExitandFailFastskip it
- 0008 never query the database inside a loop
- 0012 never deserialize without pinning the naming contract
- 0024 never serialize a polymorphic value without declaring the hierarchy
- 0088 never expect
System.Text.Jsonto read a quoted number -\"3\"won't bind to an int
- 0014 never resolve transient disposables from the root container
- 0022 never inject a scoped service into a singleton
- 0084 never resolve a transient
IDisposablefrom the root provider - it lives until shutdown; use a scope
- 0020 never compute the next date from the previous one - keep the anchor
- 0026 never dispose what you didn't create
- 0053 never let a wrapper own a stream you still need - pass
leaveOpen: true - 0078 always
Commitbefore theusingblock closes - a transaction'sDisposerolls back
- 0027 never assume
>and<=together cover a nullable value
- 0028 never put a mutable collection in a record
- 0049 never put a secret in a record - its
ToStringprints every member (@palkotnyk)
- 0033 never trust a switch expression to be exhaustive just because it once compiled clean
- 0051 never test a [Flags] enum with
is- a constant pattern is exact equality, notHasFlag(@palkotnyk) - 0085 never rely on a type pattern to catch null -
case Tskips null; add anullarm
- 0032 never log a bare string - pass a template so every value becomes a named field (@helga-pawlowska)
- 0034 never call a virtual method from a constructor - the override runs before its own fields are set (@alejandro-capel)
- 0058
newhides,overridereplaces - a base-typed reference runs the hidden base member, not yours
- 0038 never let a long-lived closure share a scope with a large object
- 0039 never
stackallocinside a loop - 0040 never keep a
Spanover a List that can still grow
- 0041 never unbox to anything but the exact boxed type
- 0042 never compare boxed values with
== - 0043 never expect a boxed nullable to still be nullable
- 0044 never expect
IEnumerable<object>to match a value-type list - 0045 never
OrderBya type with no ordering defined
- 0046 never silence a nullable warning with
!- it checks nothing at runtime - 0047 never assume deserialization respects your non-nullable annotations
- 0056 never sum nullables with
+=- a single null makes all null; use?? 0or.Sum() - 0082 never reduce a
bool?to two branches - null is a third state; gate on== true
- 0048 never assert collection equality with
Assert.Equalwhen order is incidental
- 0059 never leave
.Positionat the end of a stream you'll read back
- 0060 never
Convert.ChangeTypeinto a nullable type - unwrap it withNullable.GetUnderlyingTypefirst - 0061 never
catchexception aroundMethodInfo.Invoke- it is wrapped inTargetInvocationException
- 0062 never post JSON with
new StringContent(json)- it's text/plain, setapplication/json - 0063 never read
Contentafter theHttpResponseMessageis disposed -usingdisposes the body with it - 0064 never expect JSON without an
Acceptheader - the server sends its default format instead
- 0065 never encrypt a password - encryption is reversible; hash it with a salted KDF
- 0066 never trust the client - the server sets price, status, and ownership
- 0067 never return an exception to the client - even
.Messageleaks internals; log it, return a trace id - 0068 never put a secret in the query string - it's logged server-side even over HTTPS; use a header
- 0069 never ship a session cookie without HttpOnly and Secure - both default to off
- 0070 never assume appsettings.json is final - env vars override it
- 0071 never write a config bool as 1 or yes - only
true/falseparse - 0079 never let a present-but-empty env var pass for 'unset'
- 0080 never pack an array into one env var - give each item an indexed key (
KEY__0,KEY__1) - 0081 never leave a duplicate key in appsettings - the JSON provider won't load the file