Virtual Method Renaming Enterprise
You frequently override system virtual methods like ToString, Equals, and GetHashCode. Demeanor renames the overrides. The runtime continues to dispatch correctly to the renamed methods.
Usage
| CLI | MSBuild | Default |
|---|---|---|
| (enabled by default at Enterprise) | (enabled by default at Enterprise) | On |
--no-virtual-rename | <DemeanorNoVirtualRename>true</DemeanorNoVirtualRename> | Disable |
Before & After
public class User
{
public string Name { get; }
public string Email { get; }
public override string ToString()
=> $"{Name} <{Email}>";
public override bool Equals(object? obj)
=> obj is User other &&
string.Equals(Email, other.Email, ...);
public override int GetHashCode()
=> StringComparer.OrdinalIgnoreCase
.GetHashCode(Email);
}public class j
{
private string m_a;
private string b;
public virtual string u()
{
return a() + l.a("\u0091\u001c") + a()
+ l.a("<");
}
public virtual bool v(object a)
{
return a is j j2 &&
string.Equals(this.a(), j2.a(), ...);
}
public virtual int w()
{
return StringComparer.OrdinalIgnoreCase
.GetHashCode(a());
}
}Real ILSpy output. ToString → u(), Equals → v(), GetHashCode → w(). The runtime continues to dispatch correctly to the renamed methods. Attempting to recompile produces 11+ C# compiler errors.
What It Covers
Virtual override renaming handles every common shape of override and interface implementation, including:
- Overrides of standard .NET methods —
ToString,Equals,GetHashCode, and other base-method overrides. - Overrides of framework virtuals — lifecycle and event-handler methods you override from a framework base class.
- Interface implementations from external libraries — standard runtime interfaces and custom interfaces from third-party packages.
- Public interfaces with internal implementations — the public contract stays intact; the internal implementation is opaque. Useful when you ship a library API to consumers but obfuscate the internal classes that satisfy it.
- Hierarchies preserved by
[Obfuscation]— if the base of an override chain must keep its name (for reflection or external binding), Demeanor preserves the contract while renaming derived overrides. - Diamond shapes — a single method satisfying both an interface slot and an abstract base slot.
- Generic interfaces — e.g.
IProcessor<T>with a closed implementationImpl : IProcessor<int>. - Reflection-anchored methods — when
typeof(SpecificClass).GetMethod("Foo")appears in your code, only that specific method is preserved; other methods namedFooon unrelated classes still rename.
Framework Notes
Overrides of framework base classes and implementations of framework interfaces in the following frameworks rename automatically — you do not need annotations on the override itself. DTOs your code serializes through these frameworks are a separate concern (serializers bind to property names) and may still need attribute annotations.
- WinForms — control lifecycle and event-handler overrides rename automatically.
- WPF — element lifecycle and notification overrides rename automatically. Dependency-property change callbacks are delegates and rename freely.
- ASP.NET Core — controller actions, custom middleware, action filters, and authorization handlers all rename automatically.
- JSON converters (System.Text.Json or Newtonsoft) — the converter overrides themselves rename. The DTOs you serialize are a separate concern: annotate them with
[Obfuscation(Exclude=true, ApplyToMembers=true)]or, for System.Text.Json, register them with a source-generatedJsonSerializerContext(which Demeanor auto-detects). - MessagePack — formatter implementations rename. DTOs using numeric keys obfuscate fully; DTOs using named keys need annotations on the DTO.
- XmlSerializer — serialization-callback implementations rename. Annotate the serialized DTO types.
Obfuscation Report Visibility
Two entries surface in the automatic_decisions section of --report output:
VirtualOverrideBridged— the count of renamed virtual overrides successfully wired up. A sanity check that virtual renaming is firing on your code.VirtualOverrideChainFrozen— override chains Demeanor had to keep at original names because their shape made renaming unsafe (typically unusual reflection patterns). Each entry lists the specific group and the reason, so you can see which methods didn’t rename and decide whether to restructure or add annotations.
When to Disable
- Reflection on method names without a
typeof(T).GetMethod(...)pattern — when your reflection code stores theSystem.Typein a variable or receives it fromGetType(), Demeanor cannot resolve the target precisely and falls back to preserving every method with that name across the assembly set. Correct at runtime, but over-conservative; disable virtual rename only if this is pervasive. - Expression trees —
Expression.Call(typeof(T), "ToString", ...)uses string-based method lookup against the renamed method’s current name. Use theMethodInfooverload ofExpression.Callinstead, or exclude the affected methods via[Obfuscation]. - Interop with unobfuscated assemblies that look up your methods by name — if external code binds to a specific method name on your type beyond standard virtual dispatch (e.g., late binding from PowerShell, VBA, COM without a type library), the renamed name won’t match.
Ready to protect your .NET code?