Skip to content

Latest commit

Β 

History

357 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

C# Mistakes Explained

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.

πŸ—‚ Collections

  • 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 ToDictionary by a key that can repeat - a duplicate throws; use GroupBy / ToLookup
  • 0087 never trust readonly to freeze an array - it guards the reference, not the elements

πŸ”’ Numbers

  • 0002 never use double for money
  • 0025 never assume Math.Round rounds half up
  • 0029 never divide by a value that can be zero
  • 0050 never trust a wide target type to fix int math - cast one operand first (@palkotnyk)
  • 0055 never key or compare a decimal by its text - equal values keep different scales (1.5m vs 1.50m)
  • 0072 never assume int math throws on overflow - it wraps; use checked

⚑ Async & Threading

  • 0003 never mutate shared state without synchronization
  • 0007 never write async void outside 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.WhenAll to report more than one failure
  • 0031 never hand an async lambda to Parallel.ForEach (@palkotnyk)
  • 0035 never block on async code with .Result or .Wait()
  • 0036 never elide await inside a using or try/finally block
  • 0037 never launch an async lambda with Task.Factory.StartNew - use Task.Run
  • 0086 never pass an async lambda where an Action is expected - it becomes async void, fire-and-forget

πŸ”— LINQ & Lambdas

  • 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 Except to filter a list - set operators return distinct results, dropping duplicates

πŸ”” Events

  • 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; use E?.Invoke(args)

πŸ“¦ Value Types

  • 0011 never write a mutable struct
  • 0054 never call new Guid() - it is empty; use Guid.NewGuid()
  • 0075 never rely on a struct's ctor for default or arrays - reference fields come back null

πŸ’₯ Exceptions

  • 0005 never rethrow with throw ex - use bare throw
  • 0015 never let a catch-all eat OperationCanceledException
  • 0017 never let a finally block 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.Exit and FailFast skip it

πŸ—„ ORM

  • 0008 never query the database inside a loop

πŸ“„ Serialization

  • 0012 never deserialize without pinning the naming contract
  • 0024 never serialize a polymorphic value without declaring the hierarchy
  • 0088 never expect System.Text.Json to read a quoted number - \"3\" won't bind to an int

πŸ’‰ DI Lifetimes

  • 0014 never resolve transient disposables from the root container
  • 0022 never inject a scoped service into a singleton
  • 0084 never resolve a transient IDisposable from the root provider - it lives until shutdown; use a scope

πŸ“… Datetime

  • 0020 never compute the next date from the previous one - keep the anchor

πŸ—‘οΈ Disposal

  • 0026 never dispose what you didn't create
  • 0053 never let a wrapper own a stream you still need - pass leaveOpen: true
  • 0078 always Commit before the using block closes - a transaction's Dispose rolls back

βš–οΈ Equality

  • 0027 never assume > and <= together cover a nullable value

πŸ“‡ Records

  • 0028 never put a mutable collection in a record
  • 0049 never put a secret in a record - its ToString prints every member (@palkotnyk)

🧩 Pattern Matching

  • 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, not HasFlag (@palkotnyk)
  • 0085 never rely on a type pattern to catch null - case T skips null; add a null arm

πŸͺ΅ Logging

  • 0032 never log a bare string - pass a template so every value becomes a named field (@helga-pawlowska)

πŸͺ† Inheritance

  • 0034 never call a virtual method from a constructor - the override runs before its own fields are set (@alejandro-capel)
  • 0058 new hides, override replaces - a base-typed reference runs the hidden base member, not yours

πŸ’Ύ Memory

  • 0038 never let a long-lived closure share a scope with a large object
  • 0039 never stackalloc inside a loop
  • 0040 never keep a Span over a List that can still grow

πŸ₯Š Boxing

  • 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

🧬 Generics

  • 0044 never expect IEnumerable<object> to match a value-type list
  • 0045 never OrderBy a type with no ordering defined

πŸ•³οΈ Nullability

  • 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 ?? 0 or .Sum()
  • 0082 never reduce a bool? to two branches - null is a third state; gate on == true

πŸ§ͺ Testing

  • 0048 never assert collection equality with Assert.Equal when order is incidental

πŸ“ IO & Files

  • 0059 never leave .Position at the end of a stream you'll read back

πŸͺž Reflection

  • 0060 never Convert.ChangeType into a nullable type - unwrap it with Nullable.GetUnderlyingType first
  • 0061 never catch exception around MethodInfo.Invoke - it is wrapped in TargetInvocationException

🌐 HTTP

  • 0062 never post JSON with new StringContent(json) - it's text/plain, set application/json
  • 0063 never read Content after the HttpResponseMessage is disposed - using disposes the body with it
  • 0064 never expect JSON without an Accept header - the server sends its default format instead

πŸ”’ Security

  • 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 .Message leaks 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

βš™οΈ Configuration

  • 0070 never assume appsettings.json is final - env vars override it
  • 0071 never write a config bool as 1 or yes - only true/false parse
  • 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

About

C# legshots and how to avoid them

Topics

Resources

Contributing

Stars

47 stars

Watchers

0 watching

Forks

Sponsor this project

Contributors

Languages