InlineCollections are highly specialized. Use standard BCL collections in these scenarios.
Problem: Fixed capacity (8, 16, or 32) is too limiting.
// ❌ Wrong
var ids = new InlineList32<int>();
for (int i = 0; i < 1000; i++) {
ids.Add(i); // Throws after 32 elements
}
// ✅ Correct
var ids = new List<int>();
for (int i = 0; i < 1000; i++) {
ids.Add(i);
}Recommendation: Use List<T> for unbounded growth. Choose InlineList8, InlineList16, or InlineList32 based on your fixed capacity needs.
Problem: InlineCollections are not thread-safe.
// ❌ Wrong
var queue = new InlineQueue32<Message>();
Task.Run(() => queue.Enqueue(msg1));
Task.Run(() => queue.Enqueue(msg2)); // Race condition
// ✅ Correct
var queue = new ConcurrentQueue<Message>();
Task.Run(() => queue.Enqueue(msg1));
Task.Run(() => queue.Enqueue(msg2)); // Thread-safeRecommendation: Use ConcurrentQueue<T>, ConcurrentBag<T>, or manual synchronization.
Problem: Ref struct cannot be used in interfaces or reference-type fields.
// ❌ Wrong
interface IProcessor {
void Process(InlineList8<int> items); // Compiler error: ref struct in signature
}
class Container {
public InlineList16<int> Items; // Compiler error: ref struct as field
}
// ✅ Correct
interface IProcessor {
void Process(List<int> items);
}
class Container {
public List<int> Items;
}Recommendation: Use List<T> when API contracts require reference types.
Problem: Ref structs cannot cross await boundaries.
// ❌ Wrong
async Task ProcessAsync() {
var list = new InlineList8<int>();
list.Add(1);
await SomeAsync(); // ❌ Compiler error
}
// ✅ Correct (if conversion needed)
async Task ProcessAsync() {
var array = new[] { 1, 2, 3 };
await SomeAsync();
ProcessArray(array);
}Recommendation: Use List<T> or arrays for async scenarios.
Problem: Cannot nest ref structs.
// ❌ Wrong
var list = new List<InlineList32<int>>(); // Compiler error
var queues = new InlineList32<InlineQueue32<int>>(); // Compiler error
// ✅ Correct
var lists = new List<List<int>>();Recommendation: If you need to store collections, use reference types.
Problem: LINQ, serializers, and frameworks assume reference types.
// ❌ Wrong (mostly; some methods work)
var list = new InlineList32<int>();
list.Add(1);
list.Add(2);
var json = JsonConvert.SerializeObject(list); // May not work as expected
var ienumerable = (IEnumerable<int>)list; // Won't work (ref struct)
// ✅ Correct
var list = new List<int> { 1, 2 };
var json = JsonConvert.SerializeObject(list); // Works
var ienumerable = (IEnumerable<int>)list; // WorksRecommendation: Use List<T> for interop with frameworks.
Problem: Must be unmanaged types.
// ❌ Wrong
var names = new InlineList32<string>(); // Won't compile
var objects = new InlineList32<MyClass>(); // Won't compile
var items = new InlineList32<IEnumerable>(); // Won't compile
// ✅ Correct
var names = new List<string>();
var objects = new List<MyClass>();
var items = new List<IEnumerable>();Recommendation: Use List<T> for managed types.
Problem: Not designed for persistence across method boundaries.
// ❌ Anti-pattern
class Repository {
private InlineList32<Item> items; // Won't compile anyway
public void Add(Item item) {
items.Add(item); // If it did compile, would be bad
}
}
// ✅ Correct
class Repository {
private List<Item> items = new();
public void Add(Item item) {
items.Add(item);
}
}Recommendation: Use List<T> for persistent collections.
Problem: Stack pressure with multiple collections per frame.
// ❌ Anti-pattern (risky)
void DeepRecursion(int depth) {
var list1 = new InlineList32<int>(); // 132 bytes
var list2 = new InlineList32<int>(); // 132 bytes
var list3 = new InlineList32<int>(); // 132 bytes
if (depth < 5000)
DeepRecursion(depth + 1); // Stack overflow risk
}
// ✅ Better
void DeepRecursion(int depth) {
var list = new List<int>(); // Heap allocated
if (depth < 5000)
DeepRecursion(depth + 1);
}Recommendation: Profile stack usage. Use List<T> in deep recursion.
Problem: Cannot know if 32 is enough.
// ❌ Wrong (risky)
void ProcessUserInput(string[] commands) {
var results = new InlineList32<string>();
foreach (var cmd in commands) {
results.Add(ExecuteCommand(cmd)); // Might exceed 32
}
}
// ✅ Correct
void ProcessUserInput(string[] commands) {
var results = new List<string>();
foreach (var cmd in commands) {
results.Add(ExecuteCommand(cmd)); // Unbounded
}
}Recommendation: Use List<T> for unpredictable growth.
| Requirement | InlineCollections | Standard Collection |
|---|---|---|
| Bounded capacity ≤ 32 | ✅ | ❌ (overkill) |
| Unbounded growth | ❌ | ✅ |
| Zero allocation critical | ✅ | ❌ |
| Thread-safe | ❌ | ✅ (with Concurrent*) |
| Managed types | ❌ | ✅ |
| Framework interop | ❌ | ✅ |
| Hot-path only | ✅ | ❌ (unnecessary) |
| General purpose | ❌ | ✅ |
- Collection size known to be ≤ 32? → Yes? Continue
- All elements unmanaged types? → Yes? Continue
- Allocation a measured bottleneck? → Yes? InlineCollections might fit
- Hot-path code only? → Yes? Consider InlineCollections
- Any uncertainty? → Use
List<T>(safer default)
Default to List<T>, Stack<T>, Queue<T>. Only adopt InlineCollections after:
- Profiling shows allocation is a bottleneck
- Working set is naturally bounded ≤ 32
- Real workload benchmarks show improvement
- Team understands ref struct constraints