diff --git a/src/libraries/Common/src/SourceGenerators/UnsafeAccessorEmitter.cs b/src/libraries/Common/src/SourceGenerators/UnsafeAccessorEmitter.cs
new file mode 100644
index 00000000000000..3d4f6ce9170b2b
--- /dev/null
+++ b/src/libraries/Common/src/SourceGenerators/UnsafeAccessorEmitter.cs
@@ -0,0 +1,528 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace SourceGenerators
+{
+ ///
+ /// Emits member and constructor accessors for a source generator: [UnsafeAccessor] externs on frameworks that
+ /// support them, and a reflection-based fallback (cached delegates / /
+ /// ) downlevel. Callers describe the members to emit with the neutral,
+ /// primitive-only spec types in this file; the emitter owns accessor naming and the generic wrapper-class machinery.
+ ///
+ ///
+ /// The reflection fallback emitted for members references an InstanceMemberBindingFlags constant (of type
+ /// ) that the consuming generator must emit into the same scope by
+ /// writing , and a ValueTypeSetter<TDeclaringType, TValue>
+ /// delegate (written via ) when
+ /// returns for a value-type setter.
+ ///
+ internal static class UnsafeAccessorEmitter
+ {
+ private const string UnsafeAccessorAttributeTypeRef = "global::System.Runtime.CompilerServices.UnsafeAccessorAttribute";
+ private const string UnsafeAccessorKindTypeRef = "global::System.Runtime.CompilerServices.UnsafeAccessorKind";
+ private const string EmptyTypeArray = "global::System.Array.Empty()";
+
+ // RefKind.RefReadOnlyParameter (value 4) is unavailable in the Roslyn version the netstandard build compiles
+ // against; reference it by its underlying value so both Roslyn targets build.
+ private const RefKind RefKindRefReadOnlyParameter = (RefKind)4;
+
+ ///
+ /// The declaration of the InstanceMemberBindingFlags constant that the reflection fallback (emitted by
+ /// and ) references. A consuming generator
+ /// must write this once into the scope containing the emitted accessors.
+ ///
+ public const string InstanceMemberBindingFlagsDeclaration = """
+ private const global::System.Reflection.BindingFlags InstanceMemberBindingFlags =
+ global::System.Reflection.BindingFlags.Instance |
+ global::System.Reflection.BindingFlags.Public |
+ global::System.Reflection.BindingFlags.NonPublic;
+ """;
+
+ ///
+ /// The declaration of the ValueTypeSetter<TDeclaringType, TValue> delegate that the value-type
+ /// setter reflection fallback references. A consuming generator must write this once when
+ /// returns .
+ ///
+ public const string ValueTypeSetterDelegateDeclaration = "private delegate void ValueTypeSetter(ref TDeclaringType obj, TValue value);";
+
+ internal enum AccessorMemberKind
+ {
+ Property,
+ Field,
+ }
+
+ ///
+ /// Describes a single member (property or field) an accessor may be emitted for. All type names are neutral
+ /// fully-qualified strings so the spec carries no Roslyn symbols and remains incremental-pipeline safe.
+ ///
+ internal sealed record UnsafeAccessorMemberSpec
+ {
+ public required AccessorMemberKind Kind { get; init; }
+ public required string MemberName { get; init; }
+ public required bool NeedsGetter { get; init; }
+ public required bool NeedsSetter { get; init; }
+ public required bool CanUseUnsafeAccessors { get; init; }
+ public required string DeclaringTypeFQN { get; init; }
+ public required string MemberTypeFQN { get; init; }
+
+ ///
+ /// The zero-based position of the declaring type in the containing type's inheritance hierarchy, used to
+ /// disambiguate the generic wrapper class between members inherited from different generic base types.
+ ///
+ public int DeclaringTypeIndex { get; init; }
+
+ /// Type-parameter names of the declaring type when it is generic (.NET 9+ wrapper class), otherwise .
+ public ImmutableEquatableArray? DeclaringTypeParameterNames { get; init; }
+ public string? OpenDeclaringTypeFQN { get; init; }
+ public string? OpenMemberTypeFQN { get; init; }
+ public string? DeclaringTypeParameterConstraintClauses { get; init; }
+
+ public bool IsProperty => Kind is AccessorMemberKind.Property;
+ }
+
+ /// Describes a constructor accessor to emit.
+ internal sealed record UnsafeAccessorConstructorSpec
+ {
+ public required string TypeFriendlyName { get; init; }
+ public required string TypeFQN { get; init; }
+ public required bool CanUseUnsafeAccessor { get; init; }
+ public required ImmutableEquatableArray Parameters { get; init; }
+
+ /// Type-parameter names of the type when it is generic (.NET 9+ wrapper class), otherwise .
+ public ImmutableEquatableArray? DeclaringTypeParameterNames { get; init; }
+ public string? OpenDeclaringTypeFQN { get; init; }
+ public string? DeclaringTypeParameterConstraintClauses { get; init; }
+ }
+
+ /// A single constructor parameter of a .
+ internal sealed record UnsafeAccessorParameterSpec
+ {
+ public required string TypeFQN { get; init; }
+ public required int Index { get; init; }
+ public RefKind RefKind { get; init; }
+
+ /// The open (type-parameter-referencing) form of the parameter type, used inside the generic wrapper class; when the declaring type is non-generic or the parameter type contains no type parameters.
+ public string? OpenTypeFQN { get; init; }
+ }
+
+ ///
+ /// Gets the accessor name for a property or field. For UnsafeAccessor this is the extern method name;
+ /// for reflection fallback this is the strongly typed wrapper method name.
+ /// Use kind "get"/"set" for property getters/setters, or "field" for field UnsafeAccessor externs.
+ /// The property index suffix is only appended when needed to disambiguate shadowed members.
+ ///
+ public static string GetAccessorName(string typeFriendlyName, string accessorKind, string memberName, int propertyIndex, bool needsDisambiguation)
+ => needsDisambiguation
+ ? $"__{accessorKind}_{typeFriendlyName}_{memberName}_{propertyIndex}"
+ : $"__{accessorKind}_{typeFriendlyName}_{memberName}";
+
+ ///
+ /// For properties on generic types using wrapper-class UnsafeAccessors (.NET 9+), returns the
+ /// fully qualified accessor reference including the generic wrapper class prefix, e.g.
+ /// __GenericAccessors_MyType_0<int>.__get_MyType_Name.
+ /// For non-generic types, returns the plain accessor name.
+ ///
+ public static string GetQualifiedAccessorName(
+ ImmutableEquatableArray? declaringTypeParameterNames,
+ int declaringTypeIndex,
+ string declaringTypeFQN,
+ string typeFriendlyName,
+ string accessorKind,
+ string memberName,
+ int propertyIndex,
+ bool needsDisambiguation)
+ {
+ string accessorName = GetAccessorName(typeFriendlyName, accessorKind, memberName, propertyIndex, needsDisambiguation);
+ if (declaringTypeParameterNames is null)
+ {
+ return accessorName;
+ }
+
+ int openAngle = declaringTypeFQN.IndexOf('<');
+ string typeArgsList = declaringTypeFQN.Substring(openAngle);
+ return $"__GenericAccessors_{typeFriendlyName}_{declaringTypeIndex}{typeArgsList}.{accessorName}";
+ }
+
+ public static string GetReflectionCacheName(string typeFriendlyName, string accessorKind, string memberName, int propertyIndex, bool needsDisambiguation)
+ => needsDisambiguation
+ ? $"s_{accessorKind}_{typeFriendlyName}_{memberName}_{propertyIndex}"
+ : $"s_{accessorKind}_{typeFriendlyName}_{memberName}";
+
+ ///
+ /// Gets the unified constructor accessor name. The wrapper has the same
+ /// signature for both UnsafeAccessor and reflection fallback:
+ /// static TypeName __ctor_TypeName(params)
+ ///
+ public static string GetConstructorAccessorName(string typeFriendlyName)
+ => $"__ctor_{typeFriendlyName}";
+
+ ///
+ /// For an inaccessible constructor on a generic type using a wrapper-class UnsafeAccessor (.NET 9+), returns the
+ /// fully qualified accessor reference including the generic wrapper class prefix, e.g.
+ /// __GenericAccessors_MyType_0<int>.__ctor_MyType. Otherwise returns the plain accessor name. The
+ /// wrapper is only used for the UnsafeAccessor path; the reflection fallback for a generic type is a flat method.
+ ///
+ public static string GetQualifiedConstructorAccessorName(
+ bool canUseUnsafeAccessor,
+ ImmutableEquatableArray? declaringTypeParameterNames,
+ string typeFQN,
+ string typeFriendlyName)
+ {
+ string accessorName = GetConstructorAccessorName(typeFriendlyName);
+ if (canUseUnsafeAccessor && declaringTypeParameterNames is not null)
+ {
+ string typeArgsList = typeFQN.Substring(typeFQN.IndexOf('<'));
+ return $"__GenericAccessors_{typeFriendlyName}_0{typeArgsList}.{accessorName}";
+ }
+
+ return accessorName;
+ }
+
+ public static string GetConstructorReflectionCacheName(string typeFriendlyName)
+ => $"s_ctor_{typeFriendlyName}";
+
+ ///
+ /// Returns the set of member names that appear more than once in the property list.
+ /// This occurs when derived types shadow base members via the new keyword.
+ ///
+ public static HashSet GetDuplicateMemberNames(IEnumerable memberNames)
+ {
+ HashSet seen = new();
+ HashSet duplicates = new();
+ foreach (string memberName in memberNames)
+ {
+ if (!seen.Add(memberName))
+ {
+ duplicates.Add(memberName);
+ }
+ }
+
+ return duplicates;
+ }
+
+ ///
+ /// Emits the member accessors for the given ordered member list. Members not requiring a getter or setter
+ /// accessor are skipped. Returns if a value-type setter reflection fallback was emitted,
+ /// requiring the consuming generator to emit the ValueTypeSetter delegate type.
+ ///
+ public static bool EmitMemberAccessors(
+ SourceWriter writer,
+ string typeFriendlyName,
+ bool declaringTypeIsValueType,
+ bool useUpdatedMemorySafetyRules,
+ IReadOnlyList members)
+ {
+ string safetyModifier = useUpdatedMemorySafetyRules ? "safe " : "";
+ HashSet duplicateMemberNames = GetDuplicateMemberNames(members.Select(static m => m.MemberName));
+ bool needsAccessors = false;
+ bool needsValueTypeSetterDelegate = false;
+ Dictionary>? genericAccessorEntries = null;
+
+ for (int i = 0; i < members.Count; i++)
+ {
+ UnsafeAccessorMemberSpec member = members[i];
+ bool needsGetterAccessor = member.NeedsGetter;
+ bool needsSetterAccessor = member.NeedsSetter;
+
+ if (!needsGetterAccessor && !needsSetterAccessor)
+ {
+ continue;
+ }
+
+ if (!needsAccessors)
+ {
+ writer.WriteLine();
+ needsAccessors = true;
+ }
+
+ string declaringTypeFQN = member.DeclaringTypeFQN;
+ string propertyTypeFQN = member.MemberTypeFQN;
+ bool disambiguate = duplicateMemberNames.Contains(member.MemberName);
+
+ if (member.CanUseUnsafeAccessors)
+ {
+ if (member.DeclaringTypeParameterNames is not null)
+ {
+ // Generic types need a wrapper class for UnsafeAccessor (.NET 9+).
+ // Collect the accessor and emit the wrapper class after the loop.
+ string key = member.DeclaringTypeFQN;
+ genericAccessorEntries ??= new();
+ if (!genericAccessorEntries.TryGetValue(key, out List<(UnsafeAccessorMemberSpec Member, int Index, bool Disambiguate)>? entries))
+ {
+ entries = new();
+ genericAccessorEntries[key] = entries;
+ }
+
+ entries.Add((member, i, disambiguate));
+ }
+ else
+ {
+ string refPrefix = declaringTypeIsValueType ? "ref " : "";
+
+ if (member.IsProperty)
+ {
+ if (needsGetterAccessor)
+ {
+ string accessorName = GetAccessorName(typeFriendlyName, "get", member.MemberName, i, disambiguate);
+ writer.WriteLine($"""[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Method, Name = "get_{member.MemberName}")]""");
+ writer.WriteLine($"private static {safetyModifier}extern {propertyTypeFQN} {accessorName}({refPrefix}{declaringTypeFQN} obj);");
+ }
+
+ if (needsSetterAccessor)
+ {
+ string accessorName = GetAccessorName(typeFriendlyName, "set", member.MemberName, i, disambiguate);
+ writer.WriteLine($"""[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Method, Name = "set_{member.MemberName}")]""");
+ writer.WriteLine($"private static {safetyModifier}extern void {accessorName}({refPrefix}{declaringTypeFQN} obj, {propertyTypeFQN} value);");
+ }
+ }
+ else
+ {
+ // Field: single UnsafeAccessor that returns ref T, used for both get and set.
+ string fieldAccessorName = GetAccessorName(typeFriendlyName, "field", member.MemberName, i, disambiguate);
+ writer.WriteLine($"""[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Field, Name = "{member.MemberName}")]""");
+ writer.WriteLine($"private static {safetyModifier}extern ref {propertyTypeFQN} {fieldAccessorName}({refPrefix}{declaringTypeFQN} obj);");
+ }
+ }
+ }
+ else if (member.IsProperty)
+ {
+ // Reflection fallback for properties: use Delegate.CreateDelegate on the MethodInfo for efficient invocation.
+ // Wrapper methods are strongly typed to match UnsafeAccessor signatures.
+ string propertyExpr = $"typeof({declaringTypeFQN}).GetProperty({FormatStringLiteral(member.MemberName)}, InstanceMemberBindingFlags, null, typeof({propertyTypeFQN}), {EmptyTypeArray}, null)!";
+
+ if (needsGetterAccessor)
+ {
+ string cacheName = GetReflectionCacheName(typeFriendlyName, "get", member.MemberName, i, disambiguate);
+ string wrapperName = GetAccessorName(typeFriendlyName, "get", member.MemberName, i, disambiguate);
+
+ if (declaringTypeIsValueType)
+ {
+ // For value types, Delegate.CreateDelegate doesn't work with struct instance getters
+ // on .NET Framework (the this parameter is passed by-ref internally).
+ // Cache the MethodInfo and use Invoke instead.
+ string methodCacheType = "global::System.Reflection.MethodInfo";
+ writer.WriteLine($"private static {methodCacheType}? {cacheName};");
+ writer.WriteLine($"private static {propertyTypeFQN} {wrapperName}({declaringTypeFQN} obj) => ({propertyTypeFQN})({cacheName} ??= {propertyExpr}.GetGetMethod(true)!).Invoke(obj, null)!;");
+ }
+ else
+ {
+ string delegateType = $"global::System.Func<{declaringTypeFQN}, {propertyTypeFQN}>";
+ writer.WriteLine($"private static {delegateType}? {cacheName};");
+ writer.WriteLine($"private static {propertyTypeFQN} {wrapperName}({declaringTypeFQN} obj) => ({cacheName} ??= ({delegateType})global::System.Delegate.CreateDelegate(typeof({delegateType}), {propertyExpr}.GetGetMethod(true)!))(obj);");
+ }
+ }
+
+ if (needsSetterAccessor)
+ {
+ string cacheName = GetReflectionCacheName(typeFriendlyName, "set", member.MemberName, i, disambiguate);
+ string wrapperName = GetAccessorName(typeFriendlyName, "set", member.MemberName, i, disambiguate);
+
+ if (declaringTypeIsValueType)
+ {
+ // For value types, use a ref-parameter delegate to mutate the unboxed value in-place.
+ needsValueTypeSetterDelegate = true;
+ string delegateType = $"ValueTypeSetter<{declaringTypeFQN}, {propertyTypeFQN}>";
+ writer.WriteLine($"private static {delegateType}? {cacheName};");
+ writer.WriteLine($"private static void {wrapperName}(ref {declaringTypeFQN} obj, {propertyTypeFQN} value) => ({cacheName} ??= ({delegateType})global::System.Delegate.CreateDelegate(typeof({delegateType}), {propertyExpr}.GetSetMethod(true)!))(ref obj, value);");
+ }
+ else
+ {
+ string delegateType = $"global::System.Action<{declaringTypeFQN}, {propertyTypeFQN}>";
+ writer.WriteLine($"private static {delegateType}? {cacheName};");
+ writer.WriteLine($"private static void {wrapperName}({declaringTypeFQN} obj, {propertyTypeFQN} value) => ({cacheName} ??= ({delegateType})global::System.Delegate.CreateDelegate(typeof({delegateType}), {propertyExpr}.GetSetMethod(true)!))(obj, value);");
+ }
+ }
+ }
+ else
+ {
+ // Reflection fallback for fields: cache the FieldInfo and use GetValue/SetValue.
+ // Fields don't have MethodInfo, so Delegate.CreateDelegate can't be used.
+ string fieldExpr = $"typeof({declaringTypeFQN}).GetField({FormatStringLiteral(member.MemberName)}, InstanceMemberBindingFlags)!";
+ string fieldCacheName = GetReflectionCacheName(typeFriendlyName, "field", member.MemberName, i, disambiguate);
+ writer.WriteLine($"private static global::System.Reflection.FieldInfo? {fieldCacheName};");
+
+ if (needsGetterAccessor)
+ {
+ string wrapperName = GetAccessorName(typeFriendlyName, "get", member.MemberName, i, disambiguate);
+ writer.WriteLine($"private static {propertyTypeFQN} {wrapperName}(object obj) => ({propertyTypeFQN})({fieldCacheName} ??= {fieldExpr}).GetValue(obj)!;");
+ }
+
+ if (needsSetterAccessor)
+ {
+ string wrapperName = GetAccessorName(typeFriendlyName, "set", member.MemberName, i, disambiguate);
+ writer.WriteLine($"private static void {wrapperName}(object obj, {propertyTypeFQN} value) => ({fieldCacheName} ??= {fieldExpr}).SetValue(obj, value);");
+ }
+ }
+ }
+
+ // Emit generic wrapper classes for UnsafeAccessors on generic types (.NET 9+).
+ if (genericAccessorEntries is not null)
+ {
+ foreach (KeyValuePair> kvp in genericAccessorEntries)
+ {
+ List<(UnsafeAccessorMemberSpec Member, int Index, bool Disambiguate)> entries = kvp.Value;
+ UnsafeAccessorMemberSpec firstMember = entries[0].Member;
+ ImmutableEquatableArray typeParams = firstMember.DeclaringTypeParameterNames!;
+ string openDeclaringTypeFQN = firstMember.OpenDeclaringTypeFQN!;
+ string refPrefix = declaringTypeIsValueType ? "ref " : "";
+ string typeParamList = string.Join(", ", typeParams);
+ string constraintClauses = firstMember.DeclaringTypeParameterConstraintClauses is { } c ? $" {c}" : "";
+
+ writer.WriteLine();
+ writer.WriteLine($"private static partial class __GenericAccessors_{typeFriendlyName}_{firstMember.DeclaringTypeIndex}<{typeParamList}>{constraintClauses}");
+ writer.WriteLine('{');
+ writer.Indentation++;
+
+ foreach ((UnsafeAccessorMemberSpec member, int index, bool disambiguate) in entries)
+ {
+ bool needsGetter = member.NeedsGetter;
+ bool needsSetter = member.NeedsSetter;
+ string openPropertyTypeFQN = member.OpenMemberTypeFQN ?? member.MemberTypeFQN;
+
+ if (member.IsProperty)
+ {
+ if (needsGetter)
+ {
+ string accessorName = GetAccessorName(typeFriendlyName, "get", member.MemberName, index, disambiguate);
+ writer.WriteLine($"""[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Method, Name = "get_{member.MemberName}")]""");
+ writer.WriteLine($"public static {safetyModifier}extern {openPropertyTypeFQN} {accessorName}({refPrefix}{openDeclaringTypeFQN} obj);");
+ }
+
+ if (needsSetter)
+ {
+ string accessorName = GetAccessorName(typeFriendlyName, "set", member.MemberName, index, disambiguate);
+ writer.WriteLine($"""[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Method, Name = "set_{member.MemberName}")]""");
+ writer.WriteLine($"public static {safetyModifier}extern void {accessorName}({refPrefix}{openDeclaringTypeFQN} obj, {openPropertyTypeFQN} value);");
+ }
+ }
+ else
+ {
+ string fieldAccessorName = GetAccessorName(typeFriendlyName, "field", member.MemberName, index, disambiguate);
+ writer.WriteLine($"""[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Field, Name = "{member.MemberName}")]""");
+ writer.WriteLine($"public static {safetyModifier}extern ref {openPropertyTypeFQN} {fieldAccessorName}({refPrefix}{openDeclaringTypeFQN} obj);");
+ }
+ }
+
+ writer.Indentation--;
+ writer.WriteLine('}');
+ }
+ }
+
+ return needsValueTypeSetterDelegate;
+ }
+
+ ///
+ /// Emits a constructor accessor: a [UnsafeAccessor(Constructor)] extern where supported, otherwise a
+ /// cached wrapper. On a generic type the UnsafeAccessor extern is
+ /// emitted inside a partial __GenericAccessors_{TypeFriendlyName}_0 wrapper class (.NET 9+), shared with the
+ /// member accessors; the reflection fallback for a generic type is a flat method.
+ ///
+ public static void EmitConstructorAccessor(SourceWriter writer, bool useUpdatedMemorySafetyRules, UnsafeAccessorConstructorSpec spec)
+ {
+ bool useGenericWrapper = spec.CanUseUnsafeAccessor && spec.OpenDeclaringTypeFQN is not null;
+ string typeFQN = useGenericWrapper ? spec.OpenDeclaringTypeFQN! : spec.TypeFQN;
+ string wrapperName = GetConstructorAccessorName(spec.TypeFriendlyName);
+ ImmutableEquatableArray parameters = spec.Parameters;
+
+ if (useGenericWrapper)
+ {
+ // The constructor and member accessors share the declaring type's helper.
+ string typeParamList = string.Join(", ", spec.DeclaringTypeParameterNames!);
+ string constraintClauses = spec.DeclaringTypeParameterConstraintClauses is { } c ? $" {c}" : "";
+ writer.WriteLine($"private static partial class __GenericAccessors_{spec.TypeFriendlyName}_0<{typeParamList}>{constraintClauses}");
+ writer.WriteLine('{');
+ writer.Indentation++;
+ }
+
+ // Build the parameter list for the wrapper method.
+ var wrapperParams = new StringBuilder();
+ var callArgs = new StringBuilder();
+
+ foreach (UnsafeAccessorParameterSpec param in parameters)
+ {
+ if (wrapperParams.Length > 0)
+ {
+ wrapperParams.Append(", ");
+ callArgs.Append(", ");
+ }
+
+ string parameterTypeFQN = (useGenericWrapper ? param.OpenTypeFQN : null) ?? param.TypeFQN;
+ string refModifier = param.RefKind switch
+ {
+ RefKind.Ref => "ref ",
+ RefKind.Out => "out ",
+ // 'in' preserves the readonly by-ref signature without requiring C# 12.
+ RefKind.In or RefKindRefReadOnlyParameter => "in ",
+ _ => "",
+ };
+ wrapperParams.Append($"{refModifier}{parameterTypeFQN} p{param.Index}");
+ callArgs.Append(param.RefKind is RefKind.Out ? "null" : $"p{param.Index}");
+ }
+
+ if (spec.CanUseUnsafeAccessor)
+ {
+ string safetyModifier = useUpdatedMemorySafetyRules ? "safe " : "";
+ writer.WriteLine($"[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Constructor)]");
+ writer.WriteLine($"{(useGenericWrapper ? "public" : "private")} static {safetyModifier}extern {typeFQN} {wrapperName}({wrapperParams});");
+ }
+ else
+ {
+ // Reflection fallback: cached ConstructorInfo + Invoke.
+ // Note: ConstructorInfo cannot be wrapped in a delegate, so we cache the ConstructorInfo directly.
+ string cacheName = GetConstructorReflectionCacheName(spec.TypeFriendlyName);
+
+ string argTypes = parameters.Count == 0
+ ? EmptyTypeArray
+ : $"new global::System.Type[] {{{string.Join(", ", parameters.Select(p => $"typeof({p.TypeFQN}){(p.RefKind is RefKind.None ? "" : ".MakeByRefType()")}"))}}}";
+
+ writer.WriteLine($"private static global::System.Reflection.ConstructorInfo? {cacheName};");
+
+ string invokeArgs = parameters.Count == 0
+ ? "null"
+ : $"new object?[] {{{callArgs}}}";
+ string constructorInfo = $"{cacheName} ??= typeof({typeFQN}).GetConstructor(InstanceMemberBindingFlags, binder: null, {argTypes}, modifiers: null)!";
+
+ if (parameters.Any(p => p.RefKind is RefKind.Ref or RefKind.Out))
+ {
+ writer.WriteLine($$"""
+ private static {{typeFQN}} {{wrapperName}}({{wrapperParams}})
+ {
+ object?[] args = {{invokeArgs}};
+ {{typeFQN}} result = ({{typeFQN}})({{constructorInfo}}).Invoke(args);
+ """);
+ writer.Indentation++;
+
+ foreach (UnsafeAccessorParameterSpec param in parameters)
+ {
+ if (param.RefKind is RefKind.Ref or RefKind.Out)
+ {
+ writer.WriteLine($"p{param.Index} = ({param.TypeFQN})args[{param.Index}]!;");
+ }
+ }
+
+ writer.WriteLine("return result;");
+ writer.Indentation--;
+ writer.WriteLine('}');
+ }
+ else
+ {
+ writer.WriteLine($"private static {typeFQN} {wrapperName}({wrapperParams}) => ({typeFQN})({constructorInfo}).Invoke({invokeArgs});");
+ }
+ }
+
+ if (useGenericWrapper)
+ {
+ writer.Indentation--;
+ writer.WriteLine('}');
+ }
+ }
+
+ private static string FormatStringLiteral(string? value) => value is null ? "null" : SymbolDisplay.FormatLiteral(value, quote: true);
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
index 8ec68abadc4318..a508b9c76f5ba4 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
@@ -11,7 +11,6 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using SourceGenerators;
-using GenericAccessorEntry = (System.Text.Json.SourceGeneration.PropertyGenerationSpec Property, int Index, bool Disambiguate, bool NeedsGetter, bool NeedsSetter);
namespace System.Text.Json.SourceGeneration
{
@@ -28,8 +27,6 @@ private sealed partial class Emitter
private const string UnsafeTypeRef = "global::System.Runtime.CompilerServices.Unsafe";
private const string EqualityComparerTypeRef = "global::System.Collections.Generic.EqualityComparer";
private const string KeyValuePairTypeRef = "global::System.Collections.Generic.KeyValuePair";
- private const string UnsafeAccessorAttributeTypeRef = "global::System.Runtime.CompilerServices.UnsafeAccessorAttribute";
- private const string UnsafeAccessorKindTypeRef = "global::System.Runtime.CompilerServices.UnsafeAccessorKind";
private const string JsonEncodedTextTypeRef = "global::System.Text.Json.JsonEncodedText";
private const string JsonNamingPolicyTypeRef = "global::System.Text.Json.JsonNamingPolicy";
private const string JsonSerializerTypeRef = "global::System.Text.Json.JsonSerializer";
@@ -659,7 +656,7 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene
}
// Generate UnsafeAccessor methods or reflection cache fields for property accessors.
- _emitValueTypeSetterDelegate |= GeneratePropertyAccessors(writer, contextSpec, typeMetadata);
+ _emitValueTypeSetterDelegate |= GenerateMemberAccessors(writer, contextSpec, typeMetadata);
// Generate constructor accessor for inaccessible [JsonConstructor] constructors.
GenerateConstructorAccessor(writer, contextSpec, typeMetadata);
@@ -829,7 +826,7 @@ private static string FormatNullCast(UnionCaseSpec caseSpec)
private void GeneratePropMetadataInitFunc(SourceWriter writer, ContextGenerationSpec contextSpec, string propInitMethodName, TypeGenerationSpec typeGenerationSpec)
{
ImmutableEquatableArray properties = typeGenerationSpec.PropertyGenSpecs;
- HashSet duplicateMemberNames = GetDuplicateMemberNames(properties);
+ HashSet duplicateMemberNames = UnsafeAccessorEmitter.GetDuplicateMemberNames(properties.Select(static p => p.MemberName));
writer.WriteLine($"private static {JsonPropertyInfoTypeRef}[] {propInitMethodName}({JsonSerializerOptionsTypeRef} options)");
writer.WriteLine('{');
@@ -1006,13 +1003,13 @@ private static string GetPropertyGetterValue(
: $"({declaringTypeFQN})obj";
string accessorName = property.IsProperty
- ? GetQualifiedAccessorName(property, typeFriendlyName, "get", property.MemberName, propertyIndex, needsDisambiguation)
- : GetQualifiedAccessorName(property, typeFriendlyName, "field", property.MemberName, propertyIndex, needsDisambiguation);
+ ? UnsafeAccessorEmitter.GetQualifiedAccessorName(property.DeclaringTypeParameterNames, property.DeclaringTypeIndex, declaringTypeFQN, typeFriendlyName, "get", property.MemberName, propertyIndex, needsDisambiguation)
+ : UnsafeAccessorEmitter.GetQualifiedAccessorName(property.DeclaringTypeParameterNames, property.DeclaringTypeIndex, declaringTypeFQN, typeFriendlyName, "field", property.MemberName, propertyIndex, needsDisambiguation);
return $"static obj => {accessorName}({castExpr})";
}
- string getterName = GetAccessorName(typeFriendlyName, "get", property.MemberName, propertyIndex, needsDisambiguation);
+ string getterName = UnsafeAccessorEmitter.GetAccessorName(typeFriendlyName, "get", property.MemberName, propertyIndex, needsDisambiguation);
if (!property.IsProperty)
{
return $"static obj => {getterName}(obj)";
@@ -1081,15 +1078,15 @@ private static string GetAccessorBasedSetterDelegate(
if (property.IsProperty)
{
- string accessorName = GetQualifiedAccessorName(property, typeFriendlyName, "set", property.MemberName, propertyIndex, needsDisambiguation);
+ string accessorName = UnsafeAccessorEmitter.GetQualifiedAccessorName(property.DeclaringTypeParameterNames, property.DeclaringTypeIndex, declaringTypeFQN, typeFriendlyName, "set", property.MemberName, propertyIndex, needsDisambiguation);
return $"static (obj, value) => {accessorName}({castExpr}, value!)";
}
- string fieldName = GetQualifiedAccessorName(property, typeFriendlyName, "field", property.MemberName, propertyIndex, needsDisambiguation);
+ string fieldName = UnsafeAccessorEmitter.GetQualifiedAccessorName(property.DeclaringTypeParameterNames, property.DeclaringTypeIndex, declaringTypeFQN, typeFriendlyName, "field", property.MemberName, propertyIndex, needsDisambiguation);
return $"static (obj, value) => {fieldName}({castExpr}) = value!";
}
- string setterName = GetAccessorName(typeFriendlyName, "set", property.MemberName, propertyIndex, needsDisambiguation);
+ string setterName = UnsafeAccessorEmitter.GetAccessorName(typeFriendlyName, "set", property.MemberName, propertyIndex, needsDisambiguation);
if (!property.IsProperty)
{
return $"static (obj, value) => {setterName}(obj, value!)";
@@ -1103,290 +1100,33 @@ private static string GetAccessorBasedSetterDelegate(
return $"static (obj, value) => {setterName}({setterCastExpr}, value!)";
}
- private static bool GeneratePropertyAccessors(SourceWriter writer, ContextGenerationSpec contextSpec, TypeGenerationSpec typeGenerationSpec)
+ private static bool GenerateMemberAccessors(SourceWriter writer, ContextGenerationSpec contextSpec, TypeGenerationSpec typeGenerationSpec)
{
- string safetyModifier = contextSpec.UseUpdatedMemorySafetyRules ? "safe " : "";
ImmutableEquatableArray properties = typeGenerationSpec.PropertyGenSpecs;
- HashSet duplicateMemberNames = GetDuplicateMemberNames(properties);
- bool needsAccessors = false;
- bool needsValueTypeSetterDelegate = false;
- Dictionary>? genericAccessorEntries = null;
+ var members = new List(properties.Count);
- for (int i = 0; i < properties.Count; i++)
- {
- PropertyGenerationSpec property = properties[i];
- bool needsGetterAccessor = NeedsAccessorForGetter(property);
- bool needsSetterAccessor = NeedsAccessorForSetter(property);
-
- if (!needsGetterAccessor && !needsSetterAccessor)
- {
- continue;
- }
-
- if (!needsAccessors)
- {
- writer.WriteLine();
- needsAccessors = true;
- }
-
- string typeFriendlyName = typeGenerationSpec.TypeInfoPropertyName;
- string declaringTypeFQN = property.DeclaringType.FullyQualifiedName;
- string propertyTypeFQN = property.PropertyType.FullyQualifiedName;
- bool disambiguate = duplicateMemberNames.Contains(property.MemberName);
-
- if (property.CanUseUnsafeAccessors)
- {
- if (property.DeclaringTypeParameterNames is not null)
- {
- // Generic types need a wrapper class for UnsafeAccessor (.NET 9+).
- // Collect the accessor and emit the wrapper class after the loop.
- string key = property.DeclaringType.FullyQualifiedName;
- genericAccessorEntries ??= new();
- if (!genericAccessorEntries.TryGetValue(key, out List? entries))
- {
- entries = new();
- genericAccessorEntries[key] = entries;
- }
-
- entries.Add((property, i, disambiguate, needsGetterAccessor, needsSetterAccessor));
- }
- else
- {
- string refPrefix = typeGenerationSpec.TypeRef.IsValueType ? "ref " : "";
-
- if (property.IsProperty)
- {
- if (needsGetterAccessor)
- {
- string accessorName = GetAccessorName(typeFriendlyName, "get", property.MemberName, i, disambiguate);
- writer.WriteLine($"""[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Method, Name = "get_{property.MemberName}")]""");
- writer.WriteLine($"private static {safetyModifier}extern {propertyTypeFQN} {accessorName}({refPrefix}{declaringTypeFQN} obj);");
- }
-
- if (needsSetterAccessor)
- {
- string accessorName = GetAccessorName(typeFriendlyName, "set", property.MemberName, i, disambiguate);
- writer.WriteLine($"""[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Method, Name = "set_{property.MemberName}")]""");
- writer.WriteLine($"private static {safetyModifier}extern void {accessorName}({refPrefix}{declaringTypeFQN} obj, {propertyTypeFQN} value);");
- }
- }
- else
- {
- // Field: single UnsafeAccessor that returns ref T, used for both get and set.
- string fieldAccessorName = GetAccessorName(typeFriendlyName, "field", property.MemberName, i, disambiguate);
- writer.WriteLine($"""[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Field, Name = "{property.MemberName}")]""");
- writer.WriteLine($"private static {safetyModifier}extern ref {propertyTypeFQN} {fieldAccessorName}({refPrefix}{declaringTypeFQN} obj);");
- }
- }
- }
- else if (property.IsProperty)
- {
- // Reflection fallback for properties: use Delegate.CreateDelegate on the MethodInfo for efficient invocation.
- // Wrapper methods are strongly typed to match UnsafeAccessor signatures.
- string propertyExpr = $"typeof({declaringTypeFQN}).GetProperty({FormatStringLiteral(property.MemberName)}, InstanceMemberBindingFlags, null, typeof({propertyTypeFQN}), {EmptyTypeArray}, null)!";
-
- if (needsGetterAccessor)
- {
- string cacheName = GetReflectionCacheName(typeFriendlyName, "get", property.MemberName, i, disambiguate);
- string wrapperName = GetAccessorName(typeFriendlyName, "get", property.MemberName, i, disambiguate);
-
- if (typeGenerationSpec.TypeRef.IsValueType)
- {
- // For value types, Delegate.CreateDelegate doesn't work with struct instance getters
- // on .NET Framework (the this parameter is passed by-ref internally).
- // Cache the MethodInfo and use Invoke instead.
- string methodCacheType = "global::System.Reflection.MethodInfo";
- writer.WriteLine($"private static {methodCacheType}? {cacheName};");
- writer.WriteLine($"private static {propertyTypeFQN} {wrapperName}({declaringTypeFQN} obj) => ({propertyTypeFQN})({cacheName} ??= {propertyExpr}.GetGetMethod(true)!).Invoke(obj, null)!;");
- }
- else
- {
- string delegateType = $"global::System.Func<{declaringTypeFQN}, {propertyTypeFQN}>";
- writer.WriteLine($"private static {delegateType}? {cacheName};");
- writer.WriteLine($"private static {propertyTypeFQN} {wrapperName}({declaringTypeFQN} obj) => ({cacheName} ??= ({delegateType})global::System.Delegate.CreateDelegate(typeof({delegateType}), {propertyExpr}.GetGetMethod(true)!))(obj);");
- }
- }
-
- if (needsSetterAccessor)
- {
- string cacheName = GetReflectionCacheName(typeFriendlyName, "set", property.MemberName, i, disambiguate);
- string wrapperName = GetAccessorName(typeFriendlyName, "set", property.MemberName, i, disambiguate);
-
- if (typeGenerationSpec.TypeRef.IsValueType)
- {
- // For value types, use a ref-parameter delegate to mutate the unboxed value in-place.
- needsValueTypeSetterDelegate = true;
- string delegateType = $"ValueTypeSetter<{declaringTypeFQN}, {propertyTypeFQN}>";
- writer.WriteLine($"private static {delegateType}? {cacheName};");
- writer.WriteLine($"private static void {wrapperName}(ref {declaringTypeFQN} obj, {propertyTypeFQN} value) => ({cacheName} ??= ({delegateType})global::System.Delegate.CreateDelegate(typeof({delegateType}), {propertyExpr}.GetSetMethod(true)!))(ref obj, value);");
- }
- else
- {
- string delegateType = $"global::System.Action<{declaringTypeFQN}, {propertyTypeFQN}>";
- writer.WriteLine($"private static {delegateType}? {cacheName};");
- writer.WriteLine($"private static void {wrapperName}({declaringTypeFQN} obj, {propertyTypeFQN} value) => ({cacheName} ??= ({delegateType})global::System.Delegate.CreateDelegate(typeof({delegateType}), {propertyExpr}.GetSetMethod(true)!))(obj, value);");
- }
- }
- }
- else
- {
- // Reflection fallback for fields: cache the FieldInfo and use GetValue/SetValue.
- // Fields don't have MethodInfo, so Delegate.CreateDelegate can't be used.
- string fieldExpr = $"typeof({declaringTypeFQN}).GetField({FormatStringLiteral(property.MemberName)}, InstanceMemberBindingFlags)!";
- string fieldCacheName = GetReflectionCacheName(typeFriendlyName, "field", property.MemberName, i, disambiguate);
- writer.WriteLine($"private static global::System.Reflection.FieldInfo? {fieldCacheName};");
-
- if (needsGetterAccessor)
- {
- string wrapperName = GetAccessorName(typeFriendlyName, "get", property.MemberName, i, disambiguate);
- writer.WriteLine($"private static {propertyTypeFQN} {wrapperName}(object obj) => ({propertyTypeFQN})({fieldCacheName} ??= {fieldExpr}).GetValue(obj)!;");
- }
-
- if (needsSetterAccessor)
- {
- string wrapperName = GetAccessorName(typeFriendlyName, "set", property.MemberName, i, disambiguate);
- writer.WriteLine($"private static void {wrapperName}(object obj, {propertyTypeFQN} value) => ({fieldCacheName} ??= {fieldExpr}).SetValue(obj, value);");
- }
- }
- }
-
- // Emit generic wrapper classes for UnsafeAccessors on generic types (.NET 9+).
- if (genericAccessorEntries is not null)
- {
- string typeFriendlyName = typeGenerationSpec.TypeInfoPropertyName;
-
- foreach (KeyValuePair> kvp in genericAccessorEntries)
- {
- List entries = kvp.Value;
- PropertyGenerationSpec firstProperty = entries[0].Property;
- ImmutableEquatableArray typeParams = firstProperty.DeclaringTypeParameterNames!;
- string openDeclaringTypeFQN = firstProperty.OpenDeclaringTypeFQN!;
- string refPrefix = typeGenerationSpec.TypeRef.IsValueType ? "ref " : "";
- string typeParamList = string.Join(", ", typeParams);
- string constraintClauses = firstProperty.DeclaringTypeParameterConstraintClauses is { } c ? $" {c}" : "";
-
- writer.WriteLine();
- writer.WriteLine($"private static partial class __GenericAccessors_{typeFriendlyName}_{firstProperty.DeclaringTypeIndex}<{typeParamList}>{constraintClauses}");
- writer.WriteLine('{');
- writer.Indentation++;
-
- foreach (GenericAccessorEntry entry in entries)
- {
- PropertyGenerationSpec property = entry.Property;
- int index = entry.Index;
- bool disambiguate = entry.Disambiguate;
- bool needsGetter = entry.NeedsGetter;
- bool needsSetter = entry.NeedsSetter;
- string openPropertyTypeFQN = property.OpenPropertyTypeFQN ?? property.PropertyType.FullyQualifiedName;
-
- if (property.IsProperty)
- {
- if (needsGetter)
- {
- string accessorName = GetAccessorName(typeFriendlyName, "get", property.MemberName, index, disambiguate);
- writer.WriteLine($"""[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Method, Name = "get_{property.MemberName}")]""");
- writer.WriteLine($"public static {safetyModifier}extern {openPropertyTypeFQN} {accessorName}({refPrefix}{openDeclaringTypeFQN} obj);");
- }
-
- if (needsSetter)
- {
- string accessorName = GetAccessorName(typeFriendlyName, "set", property.MemberName, index, disambiguate);
- writer.WriteLine($"""[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Method, Name = "set_{property.MemberName}")]""");
- writer.WriteLine($"public static {safetyModifier}extern void {accessorName}({refPrefix}{openDeclaringTypeFQN} obj, {openPropertyTypeFQN} value);");
- }
- }
- else
- {
- string fieldAccessorName = GetAccessorName(typeFriendlyName, "field", property.MemberName, index, disambiguate);
- writer.WriteLine($"""[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Field, Name = "{property.MemberName}")]""");
- writer.WriteLine($"public static {safetyModifier}extern ref {openPropertyTypeFQN} {fieldAccessorName}({refPrefix}{openDeclaringTypeFQN} obj);");
- }
- }
-
- writer.Indentation--;
- writer.WriteLine('}');
- }
- }
-
- return needsValueTypeSetterDelegate;
- }
-
- ///
- /// Gets the accessor name for a property or field. For UnsafeAccessor this is the extern method name;
- /// for reflection fallback this is the strongly typed wrapper method name.
- /// Use kind "get"/"set" for property getters/setters, or "field" for field UnsafeAccessor externs.
- /// The property index suffix is only appended when needed to disambiguate shadowed members.
- ///
- private static string GetAccessorName(string typeFriendlyName, string accessorKind, string memberName, int propertyIndex, bool needsDisambiguation)
- => needsDisambiguation
- ? $"__{accessorKind}_{typeFriendlyName}_{memberName}_{propertyIndex}"
- : $"__{accessorKind}_{typeFriendlyName}_{memberName}";
-
- ///
- /// For properties on generic types using wrapper-class UnsafeAccessors (.NET 9+), returns the
- /// fully qualified accessor reference including the generic wrapper class prefix, e.g.
- /// __GenericAccessors_MyType_0<int>.__get_MyType_Name.
- /// For non-generic types, returns the plain accessor name.
- ///
- private static string GetQualifiedAccessorName(PropertyGenerationSpec property, string typeFriendlyName, string accessorKind, string memberName, int propertyIndex, bool needsDisambiguation)
- {
- string accessorName = GetAccessorName(typeFriendlyName, accessorKind, memberName, propertyIndex, needsDisambiguation);
- if (property.DeclaringTypeParameterNames is null)
- {
- return accessorName;
- }
-
- string closedTypeArgs = property.DeclaringType.FullyQualifiedName;
- int openAngle = closedTypeArgs.IndexOf('<');
- string typeArgsList = closedTypeArgs.Substring(openAngle);
- return $"__GenericAccessors_{typeFriendlyName}_{property.DeclaringTypeIndex}{typeArgsList}.{accessorName}";
- }
-
- private static string GetReflectionCacheName(string typeFriendlyName, string accessorKind, string memberName, int propertyIndex, bool needsDisambiguation)
- => needsDisambiguation
- ? $"s_{accessorKind}_{typeFriendlyName}_{memberName}_{propertyIndex}"
- : $"s_{accessorKind}_{typeFriendlyName}_{memberName}";
-
- ///
- /// Returns the set of member names that appear more than once in the property list.
- /// This occurs when derived types shadow base members via the new keyword.
- ///
- private static HashSet GetDuplicateMemberNames(ImmutableEquatableArray properties)
- {
- HashSet seen = new();
- HashSet duplicates = new();
foreach (PropertyGenerationSpec property in properties)
{
- if (!seen.Add(property.MemberName))
+ members.Add(new UnsafeAccessorEmitter.UnsafeAccessorMemberSpec
{
- duplicates.Add(property.MemberName);
- }
+ Kind = property.IsProperty ? UnsafeAccessorEmitter.AccessorMemberKind.Property : UnsafeAccessorEmitter.AccessorMemberKind.Field,
+ MemberName = property.MemberName,
+ NeedsGetter = NeedsAccessorForGetter(property),
+ NeedsSetter = NeedsAccessorForSetter(property),
+ CanUseUnsafeAccessors = property.CanUseUnsafeAccessors,
+ DeclaringTypeFQN = property.DeclaringType.FullyQualifiedName,
+ MemberTypeFQN = property.PropertyType.FullyQualifiedName,
+ DeclaringTypeIndex = property.DeclaringTypeIndex,
+ DeclaringTypeParameterNames = property.DeclaringTypeParameterNames,
+ OpenDeclaringTypeFQN = property.OpenDeclaringTypeFQN,
+ OpenMemberTypeFQN = property.OpenPropertyTypeFQN,
+ DeclaringTypeParameterConstraintClauses = property.DeclaringTypeParameterConstraintClauses,
+ });
}
- return duplicates;
+ return UnsafeAccessorEmitter.EmitMemberAccessors(writer, typeGenerationSpec.TypeInfoPropertyName, typeGenerationSpec.TypeRef.IsValueType, contextSpec.UseUpdatedMemorySafetyRules, members);
}
- ///
- /// Gets the unified constructor accessor name. The wrapper has the same
- /// signature for both UnsafeAccessor and reflection fallback:
- /// static TypeName __ctor_TypeName(params)
- ///
- private static string GetConstructorAccessorName(TypeGenerationSpec typeSpec, bool qualified = true)
- {
- string accessorName = $"__ctor_{typeSpec.TypeInfoPropertyName}";
- if (qualified && typeSpec.CanUseUnsafeAccessorForConstructor && typeSpec.OpenDeclaringTypeFQN is not null)
- {
- string typeFQN = typeSpec.TypeRef.FullyQualifiedName;
- string typeArgsList = typeFQN.Substring(typeFQN.IndexOf('<'));
- return $"__GenericAccessors_{typeSpec.TypeInfoPropertyName}_0{typeArgsList}.{accessorName}";
- }
-
- return accessorName;
- }
-
- private static string GetConstructorReflectionCacheName(TypeGenerationSpec typeSpec)
- => $"s_ctor_{typeSpec.TypeInfoPropertyName}";
-
///
/// Generates the constructor accessor for inaccessible constructors.
/// For UnsafeAccessor: emits a [UnsafeAccessor(Constructor)] extern method.
@@ -1401,102 +1141,28 @@ private static void GenerateConstructorAccessor(SourceWriter writer, ContextGene
writer.WriteLine();
- bool useGenericWrapper = typeSpec.CanUseUnsafeAccessorForConstructor && typeSpec.OpenDeclaringTypeFQN is not null;
- string typeFQN = useGenericWrapper ? typeSpec.OpenDeclaringTypeFQN! : typeSpec.TypeRef.FullyQualifiedName;
- string wrapperName = GetConstructorAccessorName(typeSpec, qualified: false);
- ImmutableEquatableArray parameters = typeSpec.CtorParamGenSpecs;
-
- if (useGenericWrapper)
- {
- // The constructor and member accessors share the declaring type's helper.
- string typeParamList = string.Join(", ", typeSpec.DeclaringTypeParameterNames!);
- string constraintClauses = typeSpec.DeclaringTypeParameterConstraintClauses is { } c ? $" {c}" : "";
- writer.WriteLine($"private static partial class __GenericAccessors_{typeSpec.TypeInfoPropertyName}_0<{typeParamList}>{constraintClauses}");
- writer.WriteLine('{');
- writer.Indentation++;
- }
-
- // Build the parameter list for the wrapper method.
- var wrapperParams = new StringBuilder();
- var callArgs = new StringBuilder();
-
- foreach (ParameterGenerationSpec param in parameters)
+ var parameters = new List(typeSpec.CtorParamGenSpecs.Count);
+ foreach (ParameterGenerationSpec param in typeSpec.CtorParamGenSpecs)
{
- if (wrapperParams.Length > 0)
- {
- wrapperParams.Append(", ");
- callArgs.Append(", ");
- }
-
- string parameterTypeFQN = (useGenericWrapper ? param.OpenParameterTypeFQN : null) ?? param.ParameterType.FullyQualifiedName;
- string refModifier = param.RefKind switch
- {
- RefKind.Ref => "ref ",
- RefKind.Out => "out ",
- // 'in' preserves the readonly by-ref signature without requiring C# 12.
- RefKind.In or RefKindRefReadOnlyParameter => "in ",
- _ => "",
- };
- wrapperParams.Append($"{refModifier}{parameterTypeFQN} p{param.ParameterIndex}");
- callArgs.Append(param.RefKind is RefKind.Out ? "null" : $"p{param.ParameterIndex}");
- }
-
- if (typeSpec.CanUseUnsafeAccessorForConstructor)
- {
- string safetyModifier = contextSpec.UseUpdatedMemorySafetyRules ? "safe " : "";
- writer.WriteLine($"[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Constructor)]");
- writer.WriteLine($"{(useGenericWrapper ? "public" : "private")} static {safetyModifier}extern {typeFQN} {wrapperName}({wrapperParams});");
- }
- else
- {
- // Reflection fallback: cached ConstructorInfo + Invoke.
- // Note: ConstructorInfo cannot be wrapped in a delegate, so we cache the ConstructorInfo directly.
- string cacheName = GetConstructorReflectionCacheName(typeSpec);
-
- string argTypes = parameters.Count == 0
- ? EmptyTypeArray
- : $"new global::System.Type[] {{{string.Join(", ", parameters.Select(p => $"typeof({p.ParameterType.FullyQualifiedName}){(p.RefKind is RefKind.None ? "" : ".MakeByRefType()")}"))}}}";
-
- writer.WriteLine($"private static global::System.Reflection.ConstructorInfo? {cacheName};");
-
- string invokeArgs = parameters.Count == 0
- ? "null"
- : $"new object?[] {{{callArgs}}}";
- string constructorInfo = $"{cacheName} ??= typeof({typeFQN}).GetConstructor(InstanceMemberBindingFlags, binder: null, {argTypes}, modifiers: null)!";
-
- if (parameters.Any(p => p.RefKind is RefKind.Ref or RefKind.Out))
+ parameters.Add(new UnsafeAccessorEmitter.UnsafeAccessorParameterSpec
{
- writer.WriteLine($$"""
- private static {{typeFQN}} {{wrapperName}}({{wrapperParams}})
- {
- object?[] args = {{invokeArgs}};
- {{typeFQN}} result = ({{typeFQN}})({{constructorInfo}}).Invoke(args);
- """);
- writer.Indentation++;
-
- foreach (ParameterGenerationSpec param in parameters)
- {
- if (param.RefKind is RefKind.Ref or RefKind.Out)
- {
- writer.WriteLine($"p{param.ParameterIndex} = ({param.ParameterType.FullyQualifiedName})args[{param.ParameterIndex}]!;");
- }
- }
-
- writer.WriteLine("return result;");
- writer.Indentation--;
- writer.WriteLine('}');
- }
- else
- {
- writer.WriteLine($"private static {typeFQN} {wrapperName}({wrapperParams}) => ({typeFQN})({constructorInfo}).Invoke({invokeArgs});");
- }
+ TypeFQN = param.ParameterType.FullyQualifiedName,
+ Index = param.ParameterIndex,
+ RefKind = param.RefKind,
+ OpenTypeFQN = param.OpenParameterTypeFQN,
+ });
}
- if (useGenericWrapper)
+ UnsafeAccessorEmitter.EmitConstructorAccessor(writer, contextSpec.UseUpdatedMemorySafetyRules, new UnsafeAccessorEmitter.UnsafeAccessorConstructorSpec
{
- writer.Indentation--;
- writer.WriteLine('}');
- }
+ TypeFriendlyName = typeSpec.TypeInfoPropertyName,
+ TypeFQN = typeSpec.TypeRef.FullyQualifiedName,
+ CanUseUnsafeAccessor = typeSpec.CanUseUnsafeAccessorForConstructor,
+ Parameters = parameters.ToImmutableEquatableArray(),
+ DeclaringTypeParameterNames = typeSpec.DeclaringTypeParameterNames,
+ OpenDeclaringTypeFQN = typeSpec.OpenDeclaringTypeFQN,
+ DeclaringTypeParameterConstraintClauses = typeSpec.DeclaringTypeParameterConstraintClauses,
+ });
}
///
@@ -1522,15 +1188,15 @@ private static string GetFastPathPropertyValueExpr(
if (property.CanUseUnsafeAccessors)
{
string accessorName = property.IsProperty
- ? GetQualifiedAccessorName(property, typeFriendlyName, "get", property.MemberName, propertyIndex, needsDisambiguation)
- : GetQualifiedAccessorName(property, typeFriendlyName, "field", property.MemberName, propertyIndex, needsDisambiguation);
+ ? UnsafeAccessorEmitter.GetQualifiedAccessorName(property.DeclaringTypeParameterNames, property.DeclaringTypeIndex, property.DeclaringType.FullyQualifiedName, typeFriendlyName, "get", property.MemberName, propertyIndex, needsDisambiguation)
+ : UnsafeAccessorEmitter.GetQualifiedAccessorName(property.DeclaringTypeParameterNames, property.DeclaringTypeIndex, property.DeclaringType.FullyQualifiedName, typeFriendlyName, "field", property.MemberName, propertyIndex, needsDisambiguation);
return typeGenSpec.TypeRef.IsValueType
? $"{accessorName}(ref value)"
: $"{accessorName}({objectExpr})";
}
- string getterName = GetAccessorName(typeFriendlyName, "get", property.MemberName, propertyIndex, needsDisambiguation);
+ string getterName = UnsafeAccessorEmitter.GetAccessorName(typeFriendlyName, "get", property.MemberName, propertyIndex, needsDisambiguation);
return $"{getterName}({objectExpr})";
}
@@ -1621,7 +1287,7 @@ private void GenerateFastPathFuncForObject(SourceWriter writer, ContextGeneratio
GenerateFastPathFuncHeader(writer, typeGenSpec, serializeMethodName);
- HashSet duplicateMemberNames = GetDuplicateMemberNames(typeGenSpec.PropertyGenSpecs);
+ HashSet duplicateMemberNames = UnsafeAccessorEmitter.GetDuplicateMemberNames(typeGenSpec.PropertyGenSpecs.Select(static p => p.MemberName));
if (typeGenSpec.ImplementsIJsonOnSerializing)
{
@@ -1782,7 +1448,7 @@ private static string GetParameterizedCtorInvocationFunc(TypeGenerationSpec type
if (typeGenerationSpec.ConstructorIsInaccessible)
{
- string accessorName = GetConstructorAccessorName(typeGenerationSpec);
+ string accessorName = UnsafeAccessorEmitter.GetQualifiedConstructorAccessorName(typeGenerationSpec.CanUseUnsafeAccessorForConstructor, typeGenerationSpec.DeclaringTypeParameterNames, typeGenerationSpec.TypeRef.FullyQualifiedName, typeGenerationSpec.TypeInfoPropertyName);
sb.Append($"return {accessorName}(");
}
else
@@ -1793,7 +1459,7 @@ private static string GetParameterizedCtorInvocationFunc(TypeGenerationSpec type
else if (typeGenerationSpec.ConstructorIsInaccessible)
{
// Inaccessible constructor: use the unified constructor accessor wrapper.
- string accessorName = GetConstructorAccessorName(typeGenerationSpec);
+ string accessorName = UnsafeAccessorEmitter.GetQualifiedConstructorAccessorName(typeGenerationSpec.CanUseUnsafeAccessorForConstructor, typeGenerationSpec.DeclaringTypeParameterNames, typeGenerationSpec.TypeRef.FullyQualifiedName, typeGenerationSpec.TypeInfoPropertyName);
sb = new($"static args => {accessorName}(");
}
else
@@ -2496,7 +2162,7 @@ private static string FormatDefaultConstructorExpr(TypeGenerationSpec typeSpec)
{ IsValueTuple: true } => $"() => default({typeSpec.TypeRef.FullyQualifiedName})",
{ ConstructionStrategy: ObjectConstructionStrategy.ParameterlessConstructor, ConstructorIsInaccessible: false } => $"() => new {typeSpec.TypeRef.FullyQualifiedName}()",
{ ConstructionStrategy: ObjectConstructionStrategy.ParameterlessConstructor, ConstructorIsInaccessible: true } =>
- $"static () => {GetConstructorAccessorName(typeSpec)}()",
+ $"static () => {UnsafeAccessorEmitter.GetQualifiedConstructorAccessorName(typeSpec.CanUseUnsafeAccessorForConstructor, typeSpec.DeclaringTypeParameterNames, typeSpec.TypeRef.FullyQualifiedName, typeSpec.TypeInfoPropertyName)}()",
_ => "null",
};
}
diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets
index 830c1c88c2a2b6..ac8db9f9cc6282 100644
--- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets
+++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets
@@ -35,6 +35,7 @@
+