C# interview bank
Questions interviewers actually ask, each answered at the length you would say it aloud, with the topic to reread if you were not sure.
Language core
14 questions · 0 Seen01 How do value types and reference types differ in C#? reveal ▾ hide ▴
A value-type variable contains its value, so ordinary assignment and by-value argument passing copy that value. A reference-type variable contains a reference; copying it can leave two variables pointing to one mutable object. This is a statement about observable semantics, not a rule that structs always live on the stack and classes always live on the heap. Value-type fields can be inline inside heap objects, and boxing creates an object for a value. A struct can also contain reference fields, so copying it is only a field-by-field copy, not a recursive copy of referenced objects.
02 How does T? differ for value types and reference types? reveal ▾ hide ▴
For a non-nullable value type T, T? is shorthand for Nullable
05 How does a C# event differ from exposing a public delegate field? reveal ▾ hide ▴
An event has a delegate type but restricts how outside code can use the member. Subscribers can add and remove handlers with += and -=; they cannot replace the whole invocation list, clear it, or raise the event. The declaring type retains those operations and therefore controls when notification occurs. A public delegate field exposes all delegate operations, so any caller can overwrite other registrations or invoke the callbacks with arbitrary arguments. Field-like events use compiler-provided storage and add/remove accessors, while explicit event accessors let a type control or forward subscription storage.
09 How does C# choose a catch clause, and when should you use an exception filter? reveal ▾ hide ▴
The runtime searches outward from the throw site and tests catch clauses in source order. A clause is eligible when the thrown object is assignment-compatible with its declared exception type. Its when filter, if present, must also return true; false continues the search. Put derived types before base types, because a general handler would otherwise make a later specific handler unreachable. Use filters for stable properties that decide recoverability, such as a parameter name or status code. Keep them fast and side-effect free because they run before the corresponding stack unwind; a filter that throws is treated as false.
13 How do expressions and statements relate to C#’s static type system? reveal ▾ hide ▴
An expression is evaluated, has a compile-time type, and usually produces a value. A statement performs an action such as declaring a variable, assigning a value, calling a method, choosing a branch, or returning. Statements often contain nested expressions. The compiler types each inner expression before checking how the surrounding statement uses its result. That is why a double destination cannot retroactively change integer division on its right-hand side: the int division result already exists before assignment conversion. During review, annotate intermediate expression types rather than looking only at the final variable declaration.
14 Does var make a C# local variable dynamically typed? reveal ▾ hide ▴
No. var asks the compiler to infer one static type from the initializer, and that type is fixed for the variable. For example, var amount = 12.5m infers decimal because of the m suffix; assigning a string later is a compile error. var changes what the source spells, not the runtime representation or available operations. It works best when the type is obvious from construction or awkward to repeat. Write the explicit type when it communicates domain meaning or prevents a surprising inference, and inspect the initializer whenever generated code uses var around numeric literals.
17 How does a C# property differ from a field, and when do you need a backing field? reveal ▾ hide ▴
A field is storage, while a property is a member whose get, set, or init accessors define read and assignment behavior. Callers use field-like syntax, but access still invokes those accessors. An auto-property is the default for simple storage because the compiler supplies its backing field. Declare a field explicitly when an accessor needs coordinated state, validation that is clearer without C# 14’s field keyword, or storage shared with other behavior. Keep compiler-generated field names out of contracts because reflection-based dependencies on them are brittle.
21 What is a C# attribute, and why does applying one not necessarily change program behavior? reveal ▾ hide ▴
An attribute is structured declarative information attached to an assembly, type, member, parameter, return value, or another supported entity. The compiler encodes its constructor and arguments in assembly metadata. That record does nothing on its own. A consumer must interpret it: the C# compiler recognizes attributes such as Obsolete, while a test runner, serializer, web framework, analyzer, or application scanner recognizes its own types. When reviewing an unfamiliar attribute, identify its fully qualified type, exact target, consuming API, and execution phase. If no consumer reads it, the code has metadata but no implemented behavior.
29 What is a C# delegate, and when would you define a custom delegate instead of using Func or Action? reveal ▾ hide ▴
A delegate is a reference type whose signature defines which methods it can invoke. A value can hold a static method, an instance method plus its target, a lambda, or an ordered invocation list. I use Func for value-returning callbacks and Action for void callbacks when the signature itself explains the contract. I define a custom delegate when the name carries domain meaning, when parameters need ref, in, out, or params modifiers, or when the public API needs its own documented contract. Two custom delegate types remain distinct even if their signatures match.
33 How does an expression tree differ from a delegate, and when do you choose each? reveal ▾ hide ▴
A delegate is typed executable behavior: callers can invoke it, but they cannot recover a supported, portable representation of its body. An Expression
37 What does a generic API preserve that an object-based API loses? reveal ▾ hide ▴
A generic signature preserves relationships between argument, storage, and result types. For example, T Find
41 What does deferred execution mean in LINQ, and when would you materialize a query? reveal ▾ hide ▴
Deferred execution means a sequence-returning query usually records operations and does not read its source until enumeration. Each new enumeration can rerun filters, selectors, I/O, or a provider query and can observe changed source data. I materialize with ToList or ToArray when the contract needs a stable membership snapshot, repeated reads of one result, or data that must outlive a resource scope. Materialization copies the outer container, not the referenced objects. I keep a query deferred when composition or short-circuiting matters and the source remains valid through consumption.
42 How do you choose among First, FirstOrDefault, Single, and SingleOrDefault? reveal ▾ hide ▴
I choose from the domain cardinality, not from which operator avoids an exception. First requires at least one result but permits more. Single asserts exactly one and exposes duplicates as a failure. Their OrDefault variants allow no result, but SingleOrDefault still rejects multiple results. A default value can itself be valid, such as zero for int, so a bare return value may not represent presence clearly. I test zero, one, and multiple matches separately and use a nullable or explicit result type when missing must remain distinguishable.
45 How does C# choose an arm in a pattern-based switch expression? reveal ▾ hide ▴
C# evaluates switch-expression arms in text order and selects the first arm whose pattern matches and whose optional when guard is true. That makes ordering part of the contract: narrow constants, types, and ranges normally precede broader patterns. The compiler rejects a later arm when preceding unguarded patterns provably subsume it, but it cannot infer arbitrary relationships between guard methods. I keep guards side-effect free, put a deliberate catch-all last when the boundary accepts unknown values, and test an overlapping input that could satisfy more than one arm to prove the intended priority.
Numbers and conversions
1 question · 0 Seen03 How do you make numeric conversions and arithmetic fail safely? reveal ▾ hide ▴
Start by identifying the type of every intermediate expression. A wider destination doesn’t prevent earlier overflow: two int operands still multiply as int before assignment to long. Widen an operand before the operation and put the complete expression in a checked context when overflow must be rejected. Explicit casts can narrow range, while implicit integer-to-floating conversions can still lose precision. For external text, use TryParse with an explicit culture and format policy rather than a cast or exception-driven parsing. Boundary tests should cover malformed input, extrema, combined maxima, signs, and any required decimal scale or rounding rule.
Runtime behavior
5 questions · 0 Seen04 What are boxing and unboxing, and why must unboxing use the exact type? reveal ▾ hide ▴
Boxing converts a value type to object or to an implemented interface and usually creates an object containing a copy of the value. Unboxing checks the boxed runtime value type and extracts that value. The check requires an exact match: an int boxed as object can’t be unboxed directly as long even though an unboxed int can widen to long. First unbox as int, then perform the numeric conversion. Repeated boxing adds allocations and type checks, so generic collections such as List
06 What happens when one handler of a multicast C# event throws? reveal ▾ hide ▴
Handlers run synchronously in invocation-list order by default. If one throws, that exception returns to the code that raised the event, and handlers later in the list do not run. Catching around the event invocation receives the failure but does not resume the skipped handlers. If the API contract requires every handler to be attempted, the publisher must snapshot the invocation list, invoke entries individually, and define how it records or aggregates failures. Required invariant updates and cleanup should stay in publisher code, with cleanup protected by finally, rather than relying on a final subscriber.
30 How does a multicast delegate handle order, return values, and exceptions? reveal ▾ hide ▴
Direct invocation calls entries synchronously in invocation-list order. For a void delegate, each normally completing entry simply hands control to the next. For a returning delegate, all reached entries run, but the call expression receives only the last normally completed entry’s result; earlier results are discarded. If an entry throws, invocation stops immediately and later entries do not run. There is no automatic error aggregation. When a contract requires every result or every attempt, I take a GetInvocationList snapshot, invoke entries individually, and define ordering, partial side effects, cancellation, and failure reporting explicitly.
40 What generic type information does the .NET runtime retain? reveal ▾ hide ▴
Closed constructed types retain their type arguments, so reflection distinguishes List
43 Why is multiple enumeration of IEnumerable<T> risky, and how do you review it? reveal ▾ hide ▴
IEnumerable
Object lifetime
2 questions · 0 Seen07 When can an event subscription keep a subscriber alive, and how do you prevent it? reveal ▾ hide ▴
A delegate for an instance method holds a strong reference to its target. While a reachable publisher keeps that delegate in its event invocation list, the subscriber remains reachable too. This matters when a static event, singleton, or other long-lived publisher outlives a page, request object, or temporary service. Put removal at the same ownership boundary that created the subscription, often in Dispose or a framework unload hook, and remove the exact named method or stored delegate instance. A newly written equivalent lambda does not identify the original registration. Tests should raise after cleanup and can use a weak reference to check collection.
31 Why can an identical-looking lambda fail to remove an event handler? reveal ▾ hide ▴
Removal is based on delegate equality, not source-text similarity. A delegate entry includes a method and, for an instance or capturing lambda, a target object. Writing another lambda can create another compiler-generated method or another captured target, so it does not reliably equal the registered delegate. I store the original delegate in a field or local, or subscribe and unsubscribe the same named method. I also place removal at the ownership boundary that created the subscription. A test should raise after cleanup; merely seeing that the -= expression compiles proves nothing about removal.
Concurrency
3 questions · 0 Seen08 Why is an async lambda attached to EventHandler risky, and what should an awaitable design specify? reveal ▾ hide ▴
EventHandler returns void, so an async lambda assigned to it becomes async void. The publisher cannot await its completion, observe cancellation through a returned task, or catch exceptions thrown after an incomplete await through the raising call. If the publisher needs those guarantees, use an explicit delegate returning Task or expose an async method. The contract must state whether handlers run sequentially or concurrently, which CancellationToken applies, whether one failure stops or cancels others, and how multiple errors are reported. Merely naming the raising method RaiseAsync or wrapping handlers in Task.Run does not create these semantics.
12 How do exceptions and cancellation propagate through async methods and Task.WhenAll? reveal ▾ hide ▴
An async method returning Task normally stores an unhandled exception in that task, and await rethrows it in the waiting control flow. OperationCanceledException associated with the operation’s token produces cancellation semantics, which should remain distinct from a fault. Task.WhenAll completes after every input task completes; it faults if any input faults, and its AggregateException retains all input failures. Awaiting the combined task throws one exception, so retain the Task.WhenAll result and inspect its Exception when every failure matters. When no task faults but at least one is canceled, the combined task is canceled. Avoid async void outside event-handler contracts because callers cannot await or inspect it.
28 What does ConcurrentDictionary make atomic, and why can GetOrAdd still repeat work? reveal ▾ hide ▴
ConcurrentDictionary keeps its documented member operations safe under concurrent calls and offers conditional single-key operations such as TryAdd and TryUpdate. It does not combine arbitrary sequences of calls into one transaction or protect invariants spanning several keys. GetOrAdd and AddOrUpdate invoke user delegates outside internal locks, so competing callers may execute a factory more than once even though only one value wins for a key. Factories should therefore be pure or safely repeatable. Irreversible effects and cross-key rules need a separate lock, transaction, idempotency mechanism, or coordinator whose boundary matches the business operation.
Diagnostics
1 question · 0 Seen10 What is the difference between throw;, throw ex;, and wrapping an exception? reveal ▾ hide ▴
Inside a catch, throw; continues the current exception and preserves its existing stack information. throw ex; performs a new throw of the caught object, resetting the stack’s starting point and obscuring the original propagation path. Wrapping creates a new exception to express the current abstraction; pass the caught exception as InnerException so diagnostics retain the lower-level type, message, and stack. Wrap only when you add stable context or change the error contract, not at every method. If propagation must resume later outside the catch, ExceptionDispatchInfo captures the exception with its propagation information and can rethrow it from another execution point.
API design
7 questions · 0 Seen11 When should a C# API return a result instead of throwing an exception? reveal ▾ hide ▴
Return a value when the outcome is expected and callers routinely branch on it. TryParse and TryGetValue are good examples: malformed user input and a missing dictionary key can be normal states, so exceptions would make the ordinary path harder to read and diagnose. Throw when the method cannot fulfill its stated contract and the caller needs failure propagation, such as an invalid public argument or unavailable required storage. The choice belongs to the API contract, not a blanket performance rule. Preserve every state the caller must distinguish, but avoid a large result wrapper that merely recreates an exception hierarchy with strings.
16 What parts of a method contract can a C# signature express? reveal ▾ hide ▴
A signature expresses the method name, parameter types and passing modes, return type, generic parameters, and accessibility. Those choices let the compiler reject many invalid calls and tell callers which values must be supplied. A narrow type is preferable to object when the method needs a specific value. The signature cannot usually express every range, format, cross-parameter relationship, side effect, or failure policy, so the method validates remaining preconditions at its boundary and documents observable behavior. Avoid Boolean mode flags when named methods or an enum make valid choices clearer, and preserve every result state callers must distinguish.
20 What should you review when a property exposes a mutable object or collection? reveal ▾ hide ▴
First separate assignment mutability from object mutability. A get-only, private-set, or init property can still return a List, array, or mutable child that callers modify directly. Decide whether callers need a live view, an immutable value, or a snapshot. Keep mutable storage private and expose an immutable collection or read-only wrapper, then inspect whether elements themselves remain mutable. Also check equality and hash code: changing a property used for identity after insertion into a Dictionary or HashSet can make the object unreachable through its original key or bucket.
22 How should you design the contract of a custom C# attribute? reveal ▾ hide ▴
Derive a usually sealed class from System.Attribute and use AttributeUsage to state the smallest valid target set, whether multiple instances are meaningful, and whether inheritance is intended. Put required, ordered data in public constructor parameters. Put genuinely optional data in public writable fields or properties for named arguments, with explicit defaults. Attribute arguments must use metadata-compatible compile-time values such as strings, numbers, enums, Type values, or permitted one-dimensional arrays. Keep constructors and setters deterministic and free of I/O. Document how the consumer validates duplicates, conflicts, unknown values, and version changes, because the attribute class is a metadata protocol.
27 How does a read-only collection view differ from an immutable snapshot? reveal ▾ hide ▴
A read-only interface or wrapper prevents mutation through that particular reference, but it can still expose a mutable backing collection. The owner or another alias may change the data, and a caller can observe those changes. A snapshot copies the container state at one point, so later structural changes to the source are not visible. An immutable collection additionally exposes updates as operations that return new collection values. None of these choices automatically makes mutable elements deeply immutable. An API should say whether it returns a live view, shallow snapshot, or immutable value and define cross-thread visibility separately.
36 What do you review in a dynamic expression builder fed by client input? reveal ▾ hide ▴
I treat the builder as a small input language. External names map to an explicit property and operator allowlist rather than every member reflection can find. Values are parsed with a documented culture, enum, and nullable policy, then represented with the exact operand type. I bound tree depth and collection sizes, reject unsupported nodes before provider execution, and pass values as provider parameters instead of target-language text. Tests cover forbidden and missing members, invalid and null values, provider translation, authorization, and in-memory parity. Any cache also needs structural keys, capacity, invalidation, and capture-lifetime checks.
38 How do you choose constraints for a public generic method? reveal ▾ hide ▴
I start from operations in the implementation and map each one to the narrowest protocol. Equality usually needs an IEqualityComparer
Control flow
1 question · 0 Seen15 How do you review a loop or branch for control-flow bugs? reveal ▾ hide ▴
Enumerate paths instead of following only the sample input. For a loop, trace zero, one, and several iterations; identify the first and last accessed element, the termination condition, and whether every continuing path advances state. For branches, include every else, switch fallback, early return, continue, break, and thrown exception. Record where each variable becomes definitely assigned and which invariants hold afterward. Then build boundary tests for empty input, one item, malformed values, limits, and unknown cases. This catches off-by-one errors, skipped updates, unreachable handling, and values used after failed parsing.
Initialization
2 questions · 0 Seen18 How do get-only, private set, init, and required properties differ? reveal ▾ hide ▴
A get-only property can be initialized by its declaration or by a constructor but has no setter for later assignment. A private setter permits the declaring type to assign the property even after construction while rejecting external assignment. An init accessor lets callers assign during object construction and rejects ordinary later assignment. Required answers a different question: creation code must initialize the member unless the selected constructor declares that it already did. It can accompany set or init, performs no domain validation, and doesn’t make referenced objects deeply immutable.
19 Why are required members not a runtime validation boundary? reveal ▾ hide ▴
Required members are primarily a compile-time protocol for C# object-creation expressions. An explicit assignment of null still counts as assigning the member, though nullable analysis can issue a separate warning. Reflection, some serializers, and code built with older compilers don’t necessarily follow the same creation check. In addition, SetsRequiredMembers tells the compiler to trust a constructor without verifying its body. Validate nullness, formats, ranges, and cross-field rules inside the trusted model boundary, and test the actual serializer or framework construction path end to end.
Reflection
2 questions · 0 Seen23 When should a consumer use GetCustomAttributes, and when should it use CustomAttributeData? reveal ▾ hide ▴
GetCustomAttribute and GetCustomAttributes create attribute instances, so they are convenient when trusted application code wants strongly typed properties. Construction invokes the attribute constructor and applies named field or property assignments; those operations can throw or produce side effects. CustomAttributeData instead exposes the constructor, positional arguments, and named arguments as metadata without creating the attribute object. It suits analyzers, documentation tools, plugin catalogs, and other discovery code that should not execute inspected attribute code. Use the plural instance API for multi-use attributes, define inheritance explicitly, validate every value, and cache a normal immutable descriptor when discovery feeds repeated runtime work.
24 What makes attribute targets and inheritance tricky in reflection-based frameworks? reveal ▾ hide ▴
One source declaration can correspond to several metadata entities. An auto-property has property metadata, accessor methods, and a generated backing field, so property:, method:, field:, param:, and return: can lead scanners to different places. Inheritance is a query policy, not copied metadata: the reflection entry point, its inherit argument, the attribute’s Inherited setting, and the member kind all matter. Interface implementation is not class inheritance and needs explicit interface mapping. A framework should document exactly which entities it scans, use plural retrieval for multi-use attributes, and test direct, overridden, interface, missing, and duplicate declarations separately.
Collections
1 question · 0 Seen25 How do you choose among List<T>, Dictionary<TKey,TValue>, and HashSet<T>? reveal ▾ hide ▴
Start from the dominant access pattern, not the item type. Use List
Equality and hashing
2 questions · 0 Seen26 What equality contract must a Dictionary key satisfy? reveal ▾ hide ▴
A dictionary uses its IEqualityComparer
51 How do collection members affect generated record equality and hash keys? reveal ▾ hide ▴
Generated record equality composes the equality contracts of its members; it does not recursively compare arbitrary collection contents. Arrays and common mutable list classes use reference-based equality, so independently constructed lists with equal elements can make two records unequal. Changing a settable member after inserting a record into Dictionary or HashSet can also change its generated hash and break lookup. A sequence-value wrapper may hash elements, which makes element mutation equally dangerous. I define whether order, duplicates, casing, and normalization matter, implement matching equality and hash behavior, and keep every hash input stable for the key’s full residence time.
Async contracts
1 question · 0 Seen32 How should you design a callback contract when the caller must await every asynchronous handler? reveal ▾ hide ▴
I avoid Action and EventHandler because an async lambda converted to either becomes async void. A Task-returning delegate such as Func<CancellationToken, Task> provides a completion signal, but direct multicast invocation still returns only the last handler’s Task. The publisher must snapshot and cast the invocation list, then choose sequential awaits or collect all tasks for Task.WhenAll. Its contract states where cancellation comes from, whether one failure stops or cancels other handlers, how synchronous throws are handled, and how multiple failures are reported. If one result drives the request, a normal async method is often clearer.
Tree rewriting
1 question · 0 Seen34 How do you safely combine two `Expression<Func<T, bool>>` predicates? reveal ▾ hide ▴
Parameter binding uses ParameterExpression object identity, not the displayed name. Two independently created predicates therefore have distinct parameter objects even when both are named x. I create one canonical parameter, visit each body, and replace its original parameter with that instance before joining the bodies with AndAlso or OrElse. The resulting lambda binds only the canonical parameter. I avoid Expression.Invoke unless the target provider explicitly supports InvocationExpression. A reusable visitor must also respect nested lambda scopes so it does not replace a parameter declaration that shadows the outer one.
LINQ providers
2 questions · 0 Seen35 Where is the translation boundary in an `IQueryable<T>` pipeline? reveal ▾ hide ▴
Queryable operators accept expression trees and extend the query description held by the source’s IQueryProvider. Translation and remote execution usually happen only when enumeration or a terminal operator demands a result. Compile converts a predicate to a delegate, while AsEnumerable changes subsequent operator binding to Enumerable; either step moves later work to ordinary in-process execution. AsQueryable on an in-memory sequence does not recreate a database provider. I inspect every materialization and type-boundary call, then test supported nodes, null semantics, parameterization, results, and query count against the actual provider rather than List
44 What changes when a LINQ pipeline crosses from IQueryable<T> to IEnumerable<T>? reveal ▾ hide ▴
Queryable operators build expression-tree descriptions for the source provider, while Enumerable operators accept delegates and execute ordinary .NET logic. AsEnumerable does not fetch data by itself, but it makes later extension calls bind to Enumerable; ToList both executes and materializes. I keep translatable filters, projections, ordering, and limits before that boundary so remote work stays remote. Then I inspect the real provider’s generated query, parameters, null and collation behavior, result size, and round trips. Testing only List
Type relationships
1 question · 0 Seen39 Why is IEnumerable<Dog> assignable to IEnumerable<Animal>, while List<Dog> is not assignable to List<Animal>? reveal ▾ hide ▴
IEnumerable
Nullability and flow
1 question · 0 Seen46 How do type and property patterns behave with null? reveal ▾ hide ▴
A declaration or type pattern never matches null, so value is string text proves both runtime compatibility and a non-null text variable. A property pattern also requires a non-null input. If an intermediate receiver in an extended property path is null, the pattern fails instead of throwing NullReferenceException. I use the null constant pattern for null, is not null for a direct non-null test, and remember that var other still matches null. These checks narrow only the facts expressed by the pattern; omitted fields and external business invariants still need validation.
Exhaustiveness
1 question · 0 Seen47 What does exhaustiveness mean for a switch expression, and what can still go wrong? reveal ▾ hide ▴
A switch expression is exhaustive when some arm applies to every possible input. A final discard supplies that syntactic catch-all, but its business meaning may be wrong if it silently treats new input as success. Without a matching arm, modern .NET throws SwitchExpressionException, and the compiler usually warns about incomplete coverage. I still test null, unknown derived types, and enum values created by casting undefined underlying numbers. List patterns need extra care because the compiler does not warn that sequence lengths or shapes are incompletely covered. The failure policy should be explicit rather than warning-driven.
Sequence patterns
1 question · 0 Seen48 What input can a C# list pattern match, and how does a slice change the requirement? reveal ▾ hide ▴
List-pattern compatibility is structural. The static input type must be countable through an accessible Length or Count and indexable through an Index or int indexer; IEnumerable
Type design
1 question · 0 Seen49 How do you choose among a class, record class, and record struct? reveal ▾ hide ▴
I start with identity and copying semantics. A plain class fits an entity whose identity survives changing attributes or whose mutable lifecycle is encapsulated. A record class fits stable data when all selected member values define equality, while ordinary assignment should copy a reference; it also supports record inheritance. A record struct fits a small, self-contained value when field-by-field copying and a zero-initialized default are valid. I use readonly record struct to prevent ordinary top-level mutation, then still inspect nested references. Performance does not decide the form until a representative benchmark measures allocation, copying, boxing, and equality in the real workload.
Copy semantics
1 question · 0 Seen50 What exactly does a with expression copy, and which invariants can it miss? reveal ▾ hide ▴
A with expression creates a new outer value and applies the named member initializers. Its default copy is shallow, so reference-valued members such as lists still point to the same nested objects unless the initializer replaces them. For record classes, copy behavior creates a new object; record structs are copied as values. The operation is not equivalent to calling a public constructor with all final values, so constructor-only cross-member validation may not rerun. Stored properties initialized from other members can also retain stale calculations after an input changes. I test reference sharing and every derived property after each permitted copy update.
Inheritance
1 question · 0 Seen52 How do inheritance, equality, and with interact for record classes? reveal ▾ hide ▴
Record classes can inherit only within a record-class hierarchy. Generated equality includes runtime record type through the equality contract, so a base record object and a derived record object are not equal merely because their shared members match. This prevents derived state from making equality asymmetric. A with expression on a base-typed variable preserves the operand’s derived runtime type because record-class copy behavior is virtual, although its initializer can name only members visible on the receiver’s compile-time type. I test both equality directions, unknown derived types at consumers, and derived member preservation whenever a polymorphic record crosses a copy boundary.
Runtime type system
1 question · 0 Seen53 How do `typeof(T)`, `obj.GetType()`, and name-based type lookup differ? reveal ▾ hide ▴
typeof(T) uses a type known to the compiled code and needs no instance; it can also represent interfaces, arrays, and open generic definitions. obj.GetType() requires a non-null object and returns its actual runtime type, which may be more derived than the variable’s declared type. Name-based lookup adds assembly-resolution and failure policy: Type.GetType does not search every loaded assembly, and a missing type can return null or throw through a selected overload. I prefer typeof when the type is statically known and make assembly identity explicit when it is not.
Member resolution
1 question · 0 Seen54 How do you make reflection member lookup deterministic? reveal ▾ hide ▴
I start with the exact Type the contract applies to, then choose the member category rather than scanning all MemberInfo values. For methods I include the name, parameter types, generic arity or construction state, and any ref or out shape. BindingFlags explicitly cover visibility, instance versus static, and whether inherited members belong. Zero matches and multiple matches are both configuration errors unless the protocol says otherwise. I never use enumeration order as selection policy. For an external name, I resolve through an application allowlist and cache only the validated result under a key containing the full selection criteria.
Dynamic invocation
1 question · 0 Seen55 How should exceptions from `MethodInfo.Invoke` be diagnosed? reveal ▾ hide ▴
I separate resolution, argument binding, and target execution. A missing or ambiguous member fails during resolution. A wrong target, count, or argument type can fail before the target method begins. If the target itself throws, reflection wraps that exception in TargetInvocationException and exposes it through InnerException. Application policy should inspect or rethrow the original exception without resetting its stack; throw inner is the wrong shape. Diagnostics record the phase, declaring type, and stable signature, but avoid sensitive argument values. Tests cover both a binding failure and a known exception thrown inside the target.
Deployment
1 question · 0 Seen56 Why can reflection code fail after trimming or Native AOT publication? reveal ▾ hide ▴
Trimming follows statically visible use and can remove code or metadata that is reached only through an unanalyzable string. A development build working therefore proves little about the published artifact. I enable trim or AOT analyzers in the real project, treat warnings as design evidence, and run the target RID output. DynamicallyAccessedMembers can propagate a precise member-preservation requirement when Type values are traceable. Truly dynamic protocols may need RequiresUnreferencedCode, but suppressing a warning is not preservation. For broad scanning or mapping, explicit registration or a source generator often gives the linker a clearer, smaller contract.
Memory APIs
1 question · 0 Seen57 How do you choose between Span<T> and Memory<T> in an API? reveal ▾ hide ▴
I choose from the required lifetime and access, not from a blanket performance rule. A method that consumes data only during a synchronous call accepts ReadOnlySpan
Aliasing and mutability
1 question · 0 Seen58 Does ReadOnlySpan<T> or ReadOnlyMemory<T> make the underlying data immutable? reveal ▾ hide ▴
No. ReadOnly prevents writes through that particular view; it says nothing about another alias or the owner. A read-only span over a string has an immutable source because strings are immutable, not because of Span. A read-only view over an array can observe changes made through the array or another writable span. Pooled storage may even be overwritten by the next renter after return. If a consumer requires a stable snapshot, I copy into owned storage or establish synchronization and an exclusive lease. I document which behavior the API promises instead of treating ReadOnly as an ownership guarantee.
Ownership and pooling
1 question · 0 Seen59 How do you safely return data backed by ArrayPool<T> or IMemoryOwner<T>? reveal ▾ hide ▴
A bare Memory
Ref safety
1 question · 0 Seen60 What are the current rules for using Span<T> in async code? reveal ▾ hide ▴
Span
Compiler tooling
2 questions · 0 Seen61 What can a C# source generator read and change during compilation? reveal ▾ hide ▴
A source generator can inspect the input compilation, syntax and semantic information, parse options, references, analyzer configuration, and declared additional files. It can add new source texts and report diagnostics. It cannot edit or delete user syntax trees, so augmentation normally relies on partial declarations or separate generated types. Ordinary generators also have no dependable order and do not consume one another’s ordinary output. I treat generated members and diagnostics as a versioned API: tests inspect the emitted text, compile the updated compilation, and check failures at user-source locations.
62 What makes an `IIncrementalGenerator` actually incremental? reveal ▾ hide ▴
The interface alone does not make generation incremental. The pipeline needs fine-grained providers and transformations whose outputs have stable value equality. I use cheap syntax predicates, perform semantic work only for candidates, then project symbols and syntax into small records before emission. I avoid combining every item with the entire Compilation and avoid Collect unless output genuinely depends on the full set. Collections need explicit element equality and deterministic ordering. An invalidation test runs the driver twice and verifies that an unrelated edit does not change or recompute unaffected outputs.
Roslyn semantics
1 question · 0 Seen63 Why is syntax-text matching unsafe for an attribute-driven source generator? reveal ▾ hide ▴
Source text does not establish symbol identity. The same attribute can appear through a short name, the Attribute suffix, an alias, or a qualified name, while another namespace can define the same short name. A string comparison therefore misses valid targets and can accept unrelated ones. ForAttributeWithMetadataName performs semantic matching against the full metadata name and gives the transform the target symbol and matching attribute data. I still keep its syntax predicate cheap, then extract only the names, options, and locations required by the supported generation contract.
Testing
1 question · 0 Seen64 How do you test a source generator beyond snapshotting its output? reveal ▾ hide ▴
I build an input matrix for supported and rejected declaration shapes, run the generator with GeneratorDriver, and inspect generated hint names, trees, and diagnostics. Then I compile the updated compilation and fail on every unexpected error, because a plausible snapshot can still contain unresolved or incompatible code. Negative tests assert stable diagnostic ids, severities, messages, and source locations. I also run twice with unchanged input and with one local edit to check determinism and invalidation. Package tests load the analyzer under every supported compiler host and exercise the real publish configuration.
No questions match this filter.