C# & .NET (C# 14, .NET 10 LTS) Module 18
Reflection, attributes and source generators interview questions
Interview questions on reflection, attributes and source generators: warm-ups, then what each harder question tests and how a good answer starts.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher
What is reflection in C#, and when would you avoid it?
-
Fresher
What is an attribute in C#?
Answered in the lesson Custom attributes and caller-info attributes
-
Fresher
What does a source generator do, and what can it not do?
-
Fresher
What makes a source generator incremental?
Answered in the lesson Writing an incremental source generator
-
Fresher
What is the difference between an analyzer and a code fix?
Answered in the lesson Roslyn analyzers, code fixes and .editorconfig
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Mid to senior
An audit logger prints every request object with GetType().GetProperties() and PropertyInfo.GetValue. At 20,000 requests a second it has become the service's largest source of allocations. How do you make it fast while it still works for any type?
- What it tests
- Knowing where reflection's cost lies, caching per type, compiled getters, thread safety of the cache, and when a source generator replaces run-time reflection.
- First step
- Separate the work that is the same for every object of a type from the work each object needs. Finding a type's properties, its attributes and how to read them is per type; reading the values is per object. Ask what can be done once per type and kept.
- Follow-ups
-
- How do you keep the cache safe when many threads log at once?
- What changes if the service is later published with Native AOT?
- How do you show that the change helped?
From the lesson Reflection, dynamic and their costs
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Senior to staff
Publishing a service with Native AOT reports IL2075 in a mapper that calls GetType().GetProperties(), IL2057 in a plug-in loader that calls Type.GetType(name), and IL2026 with IL3050 on a dynamic call. How do you deal with each, and what must be redesigned?
- What it tests
- Reading trim and AOT warnings, DynamicallyAccessedMembers and RequiresUnreferencedCode, and redesigning what Native AOT cannot support at all.
- First step
- Sort the warnings by what the code really needs: a set of types that annotations can describe, types known only by a name at run time, or code generated while the program runs, which Native AOT cannot do at all.
- Follow-ups
-
- Is suppressing a warning with UnconditionalSuppressMessage ever right?
- What happens to the plug-in loader under Native AOT?
- How do you stop new warnings from creeping back in?
From the lesson Reflection, dynamic and their costs
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
A validation library reads [Required] and [Range] attributes from properties. After a team overrides a property in a derived class, its rules stop being enforced, although every test written against the base class passes. What is going on, and how do you fix and test it?
- What it tests
- How attribute inheritance works on classes and on properties, which reflection APIs honour it, and how to test a rule engine against derived types.
- First step
- Ask where the attribute really is: on the base class's property, not on the override. Then check which method the library calls to find it, and whether that method searches the inheritance chain of a property at all.
- Follow-ups
-
- Does AttributeUsage Inherited = false change the answer?
- How would you test the library so that this cannot come back?
- What does the fix cost on a hot path, and how do you keep it cheap?
From the lesson Custom attributes and caller-info attributes
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Fresher to mid
In production a counter of processed orders stays at 0, while the same code counts correctly on a developer's machine. The only line that increments it is inside the arguments of a call to Debug.WriteLine. Explain what happens and how you fix it.
- What it tests
- Knowing what [Conditional] does to a call and its arguments, the difference between Debug and Release builds, and keeping side effects out of diagnostic calls.
- First step
- Ask which build runs in production and which runs on the developer's machine, then look at how Debug.WriteLine is declared: whether the compiler keeps a call to it in both builds.
- Follow-ups
-
- Which other common methods behave the same way?
- How would you make such a mistake fail a build before it ships?
From the lesson Custom attributes and caller-info attributes
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
A security audit finds that your NuGet package shows the build server's folder layout and a developer's user name in its log and exception messages. The logging helper uses [CallerFilePath]. What causes it, and what do you change?
- What it tests
- Understanding that caller-info values are compile-time constants, how paths reach a binary, and the build settings that make paths stable and safe to publish.
- First step
- Ask when the path in the message is decided. If it is fixed when the package is compiled, the fix belongs in the build, not in the logging code.
- Follow-ups
-
- Do stack traces and PDB files leak the same paths, and how are they fixed?
- How do you keep file names useful in logs after mapping?
- How do you check a published package for leaked paths?
From the lesson Custom attributes and caller-info attributes
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
A profiler shows 64 bytes allocated by every logger.LogInformation call in a hot loop, although Information is switched off in production. Why does a disabled log call allocate, and how do you remove the cost without losing the log line?
- What it tests
- Understanding params arrays and boxing at the call site, the level check, LoggerMessage source generation and structured logging.
- First step
- Look at what the call site builds before the logger is even asked whether the level is on: the arguments of the extension method, and what happens to an int passed where an object is expected.
- Follow-ups
-
- Would wrapping each call in if (logger.IsEnabled(...)) be enough?
- What changes in the structured values a log store receives?
- How do you find the hot log calls in a large code base?
From the lesson Using source generators in .NET
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Fresher to mid
After adding a JsonSerializerContext and a [GeneratedRegex] property, a build fails with CS0534 on the context and CS9248 on the property, among other errors. A teammate wants to go back to reflection and new Regex(...). What do you check first, and how do you fix it?
- What it tests
- Reading generator diagnostics, the partial requirement, and telling a cause from its consequences in a build log.
- First step
- Look past the compiler errors for the generators' own SYSLIB diagnostics in the same build: they say what the generator refused to do, and the compiler errors usually follow from that.
- Follow-ups
-
- Why does a non-partial context produce errors about abstract members?
- How do you make a warning such as SYSLIB1015 fail the build?
From the lesson Using source generators in .NET
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Senior to staff
Your team must publish a console service with Native AOT. It serialises JSON by reflection, binds configuration with Get<T>, builds regexes at run time, logs with LogInformation and calls one native library with DllImport. Plan the move to generated code, and say how you will know it is finished.
- What it tests
- Knowing which built-in generator replaces which reflection or run-time code path, and making trim and AOT analysis part of the build.
- First step
- Make the problems visible before fixing any: switch the trim and AOT analyzers on in the build and list the warnings by call, then map each kind of call to the generator that replaces it.
- Follow-ups
-
- Which of these changes affects behaviour, not only warnings?
- How do you stop the warnings from coming back?
- What would still block a Native AOT publish after all five are generated?
From the lesson Using source generators in .NET
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
Since your team added its own source generator, typing in a large solution has become slow, and a trace shows the generator's output being rebuilt on every keystroke for hundreds of types. What do you look at first, and how do you make the generator really incremental?
- What it tests
- Knowing how the pipeline caches, what breaks value equality in a model, how to find types efficiently, and how to prove caching with tracked steps.
- First step
- Ask what the pipeline compares after each edit. Look at the model each step returns: if it holds symbols, syntax nodes or plain arrays, it is never equal to the previous one, and every later step runs again.
- Follow-ups
-
- How do you prove the fix in a test rather than by feel?
- Which way of finding the marked types is cheapest?
- What is still recomputed after the fix, and is that a problem?
From the lesson Writing an incremental source generator
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
A build on the CI server shows warning CS8785 from your generator, followed by errors that a generated method does not exist. On your machine everything builds. What happened, and how do you make the generator fail well?
- What it tests
- Understanding what the compiler does with a generator's exception, reporting diagnostics instead, and testing generators on the inputs that break them.
- First step
- Read the CS8785 message: it names the exception and its message. Then find the input in the CI build that your machine does not have, such as a type the generator was not written for.
- Follow-ups
-
- Why do the errors appear in user code and not in the generator?
- Should the generator report the problem itself or leave it to an analyzer?
From the lesson Writing an incremental source generator
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Senior to staff
You must ship a source generator to 40 teams that use different SDK and IDE versions. How do you package and version it, which Roslyn version do you build against, and what do you test before every release?
- What it tests
- Package layout for analyzers, private Roslyn dependencies, choosing the oldest supported Roslyn, versioning and the tests that protect consumers.
- First step
- Start from the oldest tool any team uses: that compiler is the one that must load the generator, so it fixes the Roslyn version you may reference. The package layout and the tests follow from it.
- Follow-ups
-
- What goes wrong if two releases share a version number?
- How do you test the oldest supported compiler?
- How do you tell teams about a new diagnostic?
From the lesson Writing an incremental source generator
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
Your team raises AnalysisLevel to latest-recommended on a solution of 120 projects, and the next build shows 3,400 new warnings. Some colleagues want to switch code analysis off again. How do you bring the rules in so that the build stays useful and new code meets them?
- What it tests
- Knowing what AnalysisLevel and .editorconfig each control, how severity entries take precedence, and how to adopt rules in steps without hiding new problems.
- First step
- Count the warnings by rule id before deciding anything. A few rules usually make most of the noise, and each one is a decision of its own: fix it and raise it, keep it as a warning for now, or switch it off with a reason.
- Follow-ups
-
- How do you stop the set of rules from changing when the build machine gets a newer SDK?
- How do you keep test projects on looser rules than production code?
- How do you make new code meet a rule that old code still breaks?
From the lesson Roslyn analyzers, code fixes and .editorconfig
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
Billing code keeps shipping Math.Round calls on decimals that use the default midpoint rounding, and two invoices disagreed with a payment partner by a paisa. Design a check that stops this in every repository: what it inspects, how it reports, how developers fix it, and how you test and ship it.
- What it tests
- Choosing an analyzer over checklists or wrappers, analysing bound calls, a code fix that leaves the rounding policy to the team, tests that run the analyzer and the fix, and a package with a severity teams can raise.
- First step
- Write the rule down as cases before any code: which calls must be reported (a decimal rounded without a MidpointRounding), which must not (a double, a call that names a mode), and what a correct call looks like. Each case becomes a test, and the cases decide what the analyzer has to inspect.
- Follow-ups
-
- Why an analyzer, and not a checklist or a wrapper method?
- How do you keep the rule from slowing every build?
- What should the code fix insert, when not every amount rounds the same way?
From the lesson Roslyn analyzers, code fixes and .editorconfig
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
After a platform team adds its analyzer package to every project, CI builds of a large solution take noticeably longer and the editor lags while you type. How do you find out whether the analyzers are to blame, and which habits in an analyzer's code keep it cheap?
- What it tests
- Measuring analyzer cost with the compiler's own report, reading it correctly, and the habits that keep analyzer callbacks fast in builds and in the editor.
- First step
- Measure before changing any code: build one slow project at detailed verbosity with the ReportAnalyzer property set to true. The log then lists the time spent in analyzers and the share of each analyzer, so you know which package and which rule cost the time.
- Follow-ups
-
- The report shows one rule taking most of the analyzer time. What do you look for in its code?
- Why can the analyzer time be larger than the time the build took?
- How do you keep an expensive rule out of the editor without losing it?
From the lesson Roslyn analyzers, code fixes and .editorconfig
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
All C# & .NET (C# 14, .NET 10 LTS) interview questions · The C# & .NET (C# 14, .NET 10 LTS) track