|
| 1 | +--- |
| 2 | +uid: extension-blocks-multicast |
| 3 | +title: "C# 14 extension blocks and multicasting" |
| 4 | +product: "postsharp" |
| 5 | +categories: "PostSharp;AOP;Metaprogramming" |
| 6 | +summary: "Explains how C# 14 extension blocks are handled by PostSharp's multicast engine in PostSharp 2026.0, including the AllowExtensionBlockMembers property and ReflectionHelper.IsExtensionBlockMetadata method." |
| 7 | +--- |
| 8 | +# C# 14 extension blocks and multicasting |
| 9 | + |
| 10 | + |
| 11 | +## Overview |
| 12 | + |
| 13 | +Starting with PostSharp 2026.0, the multicast engine provides support for C# 14 extension blocks. By default, aspects and multicast attributes are not applied to extension block members to prevent unexpected behavior. You can explicitly enable this using the <xref:PostSharp.Extensibility.MulticastAttribute.AllowExtensionBlockMembers> property. |
| 14 | + |
| 15 | +## How extension blocks are implemented in IL |
| 16 | + |
| 17 | +The C# compiler implements extension block members as static methods (including properties) and a set of special metadata types. These metadata types are intended for the C# compiler to match extension methods and properties with the receiver type. |
| 18 | + |
| 19 | +At the IL level and when viewed through `System.Reflection`, the compiler generates: |
| 20 | + |
| 21 | +- Static implementation methods (e.g., `ExtensionMethod` and `get_ExtensionProperty`) |
| 22 | +- Nested compiler-generated metadata types that describe the extension block structure |
| 23 | + |
| 24 | +For example, consider this C# 14 extension block: |
| 25 | + |
| 26 | +```csharp |
| 27 | +public static class TestClassExtensions |
| 28 | +{ |
| 29 | + extension<TInstance>(TInstance instance) |
| 30 | + { |
| 31 | + public TInstance ExtensionMethod() => instance; |
| 32 | + |
| 33 | + public TInstance ExtensionProperty => instance; |
| 34 | + } |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +The compiler generates nested types similar to: |
| 39 | + |
| 40 | +```csharp |
| 41 | +public static class TestClassExtensions |
| 42 | +{ |
| 43 | + // Compiler-generated nested metadata types representing the extension block. |
| 44 | + [SpecialName] |
| 45 | + private static class <G>$BA41CFE2B5EDAEB8C1B9062F59ED4D69<TInstance> |
| 46 | + { |
| 47 | + // Pure metadata classes. |
| 48 | + [SpecialName] |
| 49 | + public static class <M>$DE9F57644BDC66EDA3F1FD365749DA9F; |
| 50 | + |
| 51 | + [SpecialName] |
| 52 | + public static class <M>$7A2C8F3E91B4D5A6C0E9F1B2D4A7C8E3; |
| 53 | + |
| 54 | + |
| 55 | + // Original extension members without implementations. |
| 56 | + [ExtensionMarker("<M>$DE9F57644BDC66EDA3F1FD365749DA9F")] |
| 57 | + public TInstance ExtensionMethod() => throw null; |
| 58 | + |
| 59 | + [ExtensionMarker("<M>$DE9F57644BDC66EDA3F1FD365749DA9F")] |
| 60 | + public TInstance ExtensionProperty => throw null; |
| 61 | + } |
| 62 | + |
| 63 | + // Static implementation methods with received parameter. |
| 64 | + public static TInstance ExtensionMethod<TInstance>(TInstance instance) => instance; |
| 65 | + |
| 66 | + public static TInstance get_ExtensionProperty<TInstance>(TInstance instance) => instance; |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +Since both the implementation methods and the metadata types may not be expected by existing aspects, the multicasting algorithm in PostSharp 2026.0 and later behaves as follows: |
| 71 | + |
| 72 | +- **Extension block metadata types are always skipped** - These compiler-generated nested types and their members are never eligible for aspect application |
| 73 | +- **Extension block implementation methods require opt-in** - Static methods that implement extension members are skipped by default but can be targeted by setting `AllowExtensionBlockMembers = true` |
| 74 | + |
| 75 | +## Enabling extension block support |
| 76 | + |
| 77 | +To apply aspects to extension block members, one of the following actions needs to be taken: |
| 78 | + |
| 79 | +- Add the aspect directly to the extension block member, or |
| 80 | +- Set the <xref:PostSharp.Extensibility.MulticastAttribute.AllowExtensionBlockMembers> property to `true` when multicasting the aspect from an outer scope, such as the declaring type. |
| 81 | +- Add <xref:PostSharp.Extensibility.MulticastAttributeUsageAttribute> to the aspect type and set <xref:PostSharp.Extensibility.MulticastAttributeUsageAttribute.AllowExtensionBlockMembers> to `true`. |
| 82 | + |
| 83 | +### Example: Applying aspects directly to extension members |
| 84 | + |
| 85 | +When applied explicitly to extension members, aspects are added without the need to change any of the properties. |
| 86 | + |
| 87 | +```csharp |
| 88 | +[PSerializable] |
| 89 | +class LoggingAspect : OnMethodBoundaryAspect |
| 90 | +{ |
| 91 | + public override void OnEntry(MethodExecutionArgs args) |
| 92 | + { |
| 93 | + Console.WriteLine($"Entering: {args.Method.Name}"); |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +public static class TestClassExtensions |
| 98 | +{ |
| 99 | + extension<TInstance>(TInstance instance) |
| 100 | + { |
| 101 | + [LoggingAspect] |
| 102 | + public TInstance ExtensionMethod() => instance; |
| 103 | + |
| 104 | + [LoggingAspect] |
| 105 | + public TInstance ExtensionProperty |
| 106 | + { |
| 107 | + get => instance; |
| 108 | + set { } |
| 109 | + } |
| 110 | + } |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +### Example: Multicasting with AllowExtensionBlockMembers set to true |
| 115 | + |
| 116 | +After setting <xref:PostSharp.Extensibility.MulticastAttribute.AllowExtensionBlockMembers> to `true` and multicasting on the whole class, members within extension blocks will be eligible for the aspect. |
| 117 | + |
| 118 | +```csharp |
| 119 | +[PSerializable] |
| 120 | +class LoggingAspect : OnMethodBoundaryAspect |
| 121 | +{ |
| 122 | + public override void OnEntry(MethodExecutionArgs args) |
| 123 | + { |
| 124 | + Console.WriteLine($"Entering: {args.Method.Name}"); |
| 125 | + } |
| 126 | +} |
| 127 | + |
| 128 | +[LoggingAspect(AllowExtensionBlockMembers = true)] |
| 129 | +public static class TestClassExtensions |
| 130 | +{ |
| 131 | + extension<TInstance>(TInstance instance) |
| 132 | + { |
| 133 | + public TInstance ExtensionMethod() => instance; |
| 134 | + |
| 135 | + public TInstance ExtensionProperty |
| 136 | + { |
| 137 | + get => instance; |
| 138 | + set { } |
| 139 | + } |
| 140 | + } |
| 141 | +} |
| 142 | +``` |
| 143 | + |
| 144 | +### Example: Changing default behavior for custom aspects |
| 145 | + |
| 146 | +Setting the <xref:PostSharp.Extensibility.MulticastAttributeUsageAttribute.AllowExtensionBlockMembers> property to `true` on the aspect class will make extension members automatically eligible for the aspect: |
| 147 | + |
| 148 | +```csharp |
| 149 | +[MulticastAttributeUsage(MulticastTargets.Method, AllowExtensionBlockMembers = true)] |
| 150 | +[PSerializable] |
| 151 | +public class LoggingAspect : OnMethodBoundaryAspect |
| 152 | +{ |
| 153 | + public override void OnEntry(MethodExecutionArgs args) |
| 154 | + { |
| 155 | + Console.WriteLine($"Entering: {args.Method.Name}"); |
| 156 | + } |
| 157 | +} |
| 158 | + |
| 159 | +[LoggingAspect] |
| 160 | +public static class TestClassExtensions |
| 161 | +{ |
| 162 | + extension<TInstance>(TInstance instance) |
| 163 | + { |
| 164 | + public TInstance ExtensionMethod() => instance; |
| 165 | + |
| 166 | + public TInstance ExtensionProperty |
| 167 | + { |
| 168 | + get => instance; |
| 169 | + set { } |
| 170 | + } |
| 171 | + } |
| 172 | +} |
| 173 | +``` |
| 174 | + |
| 175 | +## IAspectProvider and IAdviceProvider |
| 176 | + |
| 177 | +C# 14 extension blocks present a challenge when using <xref:PostSharp.Aspects.IAspectProvider> or <xref:PostSharp.Aspects.Advices.IAdviceProvider> because the `System.Reflection` API exposes the compiler-generated IL artifacts rather than the source-level C# syntax. |
| 178 | + |
| 179 | +When implementing `IAspectProvider` or `IAdviceProvider`, you receive reflection objects (`Type`, `MethodInfo`, etc.) that represent the IL structure. For extension blocks, this means you'll encounter: |
| 180 | + |
| 181 | +- Static implementation methods for extension members |
| 182 | +- Compiler-generated nested metadata types (e.g., `<Extension>0<TInstance>`) |
| 183 | + |
| 184 | +Attempting to target extension block metadata types or their members will result in error **LA0167**. |
| 185 | + |
| 186 | +To make your `IAspectProvider` or `IAdviceProvider` safe to use with extension blocks, remember that: |
| 187 | + |
| 188 | +- **Extension member implementation methods are safe to target** - These are the static methods that implement the actual extension logic and can have aspects applied to them. |
| 189 | +- **Extension block metadata types must be avoided** - Targeting these compiler-generated types or their members will result in error LA0167. |
| 190 | + |
| 191 | +PostSharp 2026.0 introduces the <xref:PostSharp.Reflection.ReflectionHelper.IsExtensionBlockMetadata> method to help identify and filter extension block metadata artifacts. This method allows you to distinguish between regular types/members and compiler-generated extension block metadata. |
| 192 | + |
| 193 | +### Example: Filtering extension block metadata |
| 194 | + |
| 195 | +When implementing `IAspectProvider`, use `IsExtensionBlockMetadata` to skip compiler-generated metadata types: |
| 196 | + |
| 197 | +```csharp |
| 198 | +using PostSharp.Aspects; |
| 199 | +using PostSharp.Reflection; |
| 200 | +using System; |
| 201 | +using System.Collections.Generic; |
| 202 | +using System.Linq; |
| 203 | +using System.Reflection; |
| 204 | + |
| 205 | +[PSerializable] |
| 206 | +public class MyAspectProvider : IAspectProvider |
| 207 | +{ |
| 208 | + public IEnumerable<AspectInstance> ProvideAspects(object targetElement) |
| 209 | + { |
| 210 | + Type type = (Type)targetElement; |
| 211 | + |
| 212 | + // Get all nested types, but filter out extension block metadata |
| 213 | + var nestedTypes = type.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic) |
| 214 | + .Where(t => !ReflectionHelper.IsExtensionBlockMetadata(t)); |
| 215 | + |
| 216 | + foreach (var nestedType in nestedTypes) |
| 217 | + { |
| 218 | + // Safely process non-metadata nested types |
| 219 | + yield return new AspectInstance(nestedType, new MyAspect()); |
| 220 | + } |
| 221 | + |
| 222 | + // Extension member implementation methods are safe to target |
| 223 | + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Static) |
| 224 | + .Where(m => !ReflectionHelper.IsExtensionBlockMetadata(m.DeclaringType)); |
| 225 | + |
| 226 | + foreach (var method in methods) |
| 227 | + { |
| 228 | + yield return new AspectInstance(method, new MyMethodAspect()); |
| 229 | + } |
| 230 | + } |
| 231 | +} |
| 232 | +``` |
| 233 | + |
| 234 | + |
| 235 | + |
| 236 | +## Backward compatibility |
| 237 | + |
| 238 | +Classic extension methods using the `this` modifier continue to work as before and are not affected by the `AllowExtensionBlockMembers` property. They are always eligible for aspect application according to existing multicast rules. |
| 239 | + |
| 240 | + |
| 241 | +## See Also |
| 242 | + |
| 243 | +<xref:multicast-conceptual> |
| 244 | +<br><xref:attribute-multicasting> |
| 245 | +<br><xref:whats-new-20260> |
| 246 | +<br><xref:PostSharp.Extensibility.MulticastAttribute.AllowExtensionBlockMembers> |
| 247 | +<br><xref:PostSharp.Extensibility.MulticastAttributeUsageAttribute.AllowExtensionBlockMembers> |
| 248 | +<br><xref:PostSharp.Reflection.ReflectionHelper.IsExtensionBlockMetadata> |
| 249 | +<br><xref:iaspectprovider> |
| 250 | + |
| 251 | +**Other Resources** |
| 252 | +<br>[C# 14 Extension Members - Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/extension) |
| 253 | +<br>[Exploring extension members - .NET Blog](https://devblogs.microsoft.com/dotnet/csharp-exploring-extension-members/) |
0 commit comments