Limitations¶
Imposter keeps the source generator and runtime focused on common impersonation scenarios. This comes with a few intentional limitations.
Language and tooling¶
- Minimum supported C# version is 9.0.
- Only Roslyn-based C# projects are supported (no Visual Basic or F#).
- Generated imposters compile for projects targeting .NET Standard 2.0 and later, .NET Framework 4.7.2 and later, and .NET Core or .NET 5 and later. Projects targeting .NET Standard or .NET Framework default to an older C# version, so set
<LangVersion>9.0</LangVersion>or later (see IMP003).
Class targets¶
- Only non-sealed classes can be impersonated.
- Only virtual or abstract members can be impersonated on class imposters.
UseBaseImplementation()applies only to non-abstract, virtual class members and is not available for interfaces.- The imposter creates its instance without setting C# 11
requiredmembers. A required field or non-virtual property keeps its default value. A virtual or abstract required property is impersonated like any other. Before .NET 7, the project needs its ownSetsRequiredMembersAttribute(see IMP010).
Virtual calls during construction¶
If a target constructor calls an intercepted member, the imposter's setup state is not yet available. During this phase, concrete virtual methods, properties, indexers, and event accessors call their base implementation. Exceptions from that implementation propagate normally.
Abstract methods and getters return default (including null for reference types and Task),
out parameters receive their default value, and ref parameters remain unchanged. Abstract void
methods, setters, and event accessors do nothing. Constructors must tolerate these default values;
for example, they cannot await an abstract method's default Task result.
This behavior is the same in implicit and explicit modes. Constructor-time calls do not run setup callbacks or appear in invocation verification. After construction, normal setup, verification, and explicit-mode checks apply.
Interface targets¶
- Static members of an interface aren't impersonated. They're called through the interface, never through the imposter's instance, so the imposter has no setup for them.
- An interface with a static abstract member that has no implementation in it, declared or inherited, can't be impersonated: C# doesn't allow it as a type argument, which its imposter needs. Such a target reports IMP012 and gets no imposter.
Ref-like types¶
- Methods with
Span<T>orReadOnlySpan<T>parameters, or that return one by value, properties and indexers with span values or keys, and events whose delegate takes a span can be impersonated (see Span properties, Span keys and values and Span parameters of events). The imposter copies the elements a span argument arrives with, and you match them withSpanArg<T>orReadOnlySpanArg<T>, orOutSpanArg<T>orOutReadOnlySpanArg<T>for anoutspan (see Span parameters).Returnstakes the array a returned span covers (see Methods). - In a generic method, a span whose element type uses one of the method's type parameters passes as a copy. A
Span<T>argument reaches the delegates you pass toReturnsandCallbackas a copy, so their writes don't reach the caller's memory; areforoutspan the delegates leave reaches the caller as a copy; and a returnedSpan<T>is a copy of the array given toReturns, so the caller's writes don't reach that array. - A
scopedparameter, or aparamsspan, which is scoped implicitly, staysscopedin the delegates you pass toReturnsandCallback. C# doesn't inferscopedfor a lambda that leaves out its parameter types, so when the member returns a span or takes one byreforout, a lambda passed toReturnsdeclares its parameters,scopedincluded (see Span parameters). - A method's parameter of a custom
ref structtype isn't matched or recorded: setups and verification leave it out, and only the delegates you pass toReturns,CallbackandThrowssee it (see Ref struct parameters). So a test can't tell calls apart by that argument. - A method's custom
ref structresult comes only from aReturnsdelegate or the base implementation: there's noReturns(value), and the invocation history doesn't record it (see Methods). - A value of a method's type parameter that allows ref structs (
where T : allows ref struct) passes through like a customref structparameter or result, whatever the type argument, and the method's setups apply only to calls with the same type arguments (see Generics). The imposter repeats the anti-constraint, which needs the Roslyn 4.14 compiler or later; an older one reports IMP009 for such a method. The imposter of a generic target takes only type arguments that aren't ref structs, even for a type parameter of the target that allows them. - A property of a custom
ref structtype doesn't keep or match its value: its getter'sReturnstakes only a delegate, itsSetter()takes no criteria, and without a setup the getter returns the base getter's value or the default, even after a set (see Ref struct properties). - An indexer of a custom
ref structtype doesn't keep or match its value either: its getter'sReturnstakes only the delegate that gets the keys, its setter'sCalledcounts the sets by their keys, and without a setup the getter returns the base getter's value or the default (see Ref struct values). - An indexer key of a custom
ref structtype isn't matched or recorded: setups and verification match the other keys only, and an indexer whose keys are all ref structs, or whose other keys are another indexer's, is set up by a method named after it, such asIndexer()(see Ref struct keys). - Members that use a ref-like type (
Span<T>,ReadOnlySpan<T>or anotherref struct) in any other way can't be impersonated, because an imposter can't store or match its values: a span returned by reference, a span property or indexer returned by reference, a span an async event's delegate takes byref,outorref readonly, a customref structanywhere but a method's parameter or by-value result, the type of a property or indexer returned by value, an indexer key passed by value, or a sync event delegate's parameter, a class indexer'sref structvalue next to a key taken byinorref readonlywhen its getter has a base implementation, a method's parameter or result of aref structtype that uses the method's type parameters, or a generic method'sref structresult next to aref,inorref readonlyparameter. The two generic method cases don't apply when one of the method's type parameters allows ref structs. A target with such a member reports IMP009 and gets no imposter.
Ref returns¶
- Methods, properties and indexers that return by
reforref readonlycan't be impersonated, because an imposter returns its results by value. A target with such a member reports IMP011 and gets no imposter. An interface member of this kind with a default body is left out instead: it has no setup, and calls reach its body.
Pointer types¶
- Methods, properties, indexers and events whose signature uses a pointer or function pointer type, such as
int*ordelegate*<int, void>, can't be impersonated, because a pointer can't be a type argument. A target with such a member reports IMP013 and gets no imposter. - A class imposter has no constructor for a target constructor that takes a pointer. A class whose accessible constructors all take one reports IMP013.
Async behavior¶
- Async methods without setup return
default, which forTaskisnull. - Sequenced async outcomes are consumed in order; when a sequence is exhausted, the last outcome is repeated (where applicable).
Generated code¶
- Generated
.g.csfiles are implementation details and should not be edited directly; customize behavior via setups or by changing generator inputs.
Unresolved types and other source generators¶
Imposter preserves methods, properties and indexers whose types have not resolved yet. Types emitted
by another generator may be unavailable when Imposter runs and resolve only in the final compilation.
For example, a method using Gen.Other can compile once another
generator supplies that class. Qualify these references in the target declaration (Gen.Other or
global::Gen.Other); Imposter does not copy the target file's using directives into its generated file.
If the type is genuinely missing, the compiler may report errors in both the target and the generated
code. Fix the missing type in the target; do not edit the .g.cs file.
Events require a known delegate signature to generate their raising API. An event whose delegate type
is unresolved is left out, even if another generator later supplies that delegate. This can leave an
interface event or an abstract class event unimplemented. Declare the delegate in ordinary source or
a referenced assembly so Imposter can inspect it. A known delegate such as System.Action<Gen.Other>
can still work when another generator supplies the argument's class.