Skip to content

Commit 727e9ca

Browse files
m-nashchristothes
andauthored
Mrw reflection collection (Azure#52791)
* Add non aot compatible support for collection reading and writing * updates after cherry-pick * Fix override mis match * pr fb * update api * swap to aot friendly APIs in AOAI * Update sdk/core/System.ClientModel/src/ModelReaderWriter/Reflection/ReflectionModelBuilder.cs Co-authored-by: Christopher Scott <[email protected]> * Update sdk/core/System.ClientModel/src/ModelReaderWriter/Reflection/ReflectionModelBuilder.cs Co-authored-by: Christopher Scott <[email protected]> * update to use AOT safe APIs --------- Co-authored-by: Christopher Scott <[email protected]>
1 parent 730dca0 commit 727e9ca

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+454
-208
lines changed

sdk/core/System.ClientModel/api/System.ClientModel.net8.0.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -387,13 +387,17 @@ public sealed override void Process(System.ClientModel.Primitives.PipelineMessag
387387
}
388388
public static partial class ModelReaderWriter
389389
{
390-
public static object? Read(System.BinaryData data, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
390+
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
391+
public static object? Read(System.BinaryData data, System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
391392
public static object? Read(System.BinaryData data, System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions options, System.ClientModel.Primitives.ModelReaderWriterContext context) { throw null; }
392-
public static T? Read<T>(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel<T> { throw null; }
393+
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
394+
public static T? Read<T>(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
393395
public static T? Read<T>(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions options, System.ClientModel.Primitives.ModelReaderWriterContext context) { throw null; }
396+
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
394397
public static System.BinaryData Write(object model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
395398
public static System.BinaryData Write(object model, System.ClientModel.Primitives.ModelReaderWriterOptions options, System.ClientModel.Primitives.ModelReaderWriterContext context) { throw null; }
396-
public static System.BinaryData Write<T>(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel<T> { throw null; }
399+
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
400+
public static System.BinaryData Write<T>(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
397401
public static System.BinaryData Write<T>(T model, System.ClientModel.Primitives.ModelReaderWriterOptions options, System.ClientModel.Primitives.ModelReaderWriterContext context) { throw null; }
398402
}
399403
[System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true)]

sdk/core/System.ClientModel/api/System.ClientModel.net9.0.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -387,13 +387,17 @@ public sealed override void Process(System.ClientModel.Primitives.PipelineMessag
387387
}
388388
public static partial class ModelReaderWriter
389389
{
390-
public static object? Read(System.BinaryData data, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
390+
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
391+
public static object? Read(System.BinaryData data, System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
391392
public static object? Read(System.BinaryData data, System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions options, System.ClientModel.Primitives.ModelReaderWriterContext context) { throw null; }
392-
public static T? Read<T>(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel<T> { throw null; }
393+
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
394+
public static T? Read<T>(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
393395
public static T? Read<T>(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions options, System.ClientModel.Primitives.ModelReaderWriterContext context) { throw null; }
396+
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
394397
public static System.BinaryData Write(object model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
395398
public static System.BinaryData Write(object model, System.ClientModel.Primitives.ModelReaderWriterOptions options, System.ClientModel.Primitives.ModelReaderWriterContext context) { throw null; }
396-
public static System.BinaryData Write<T>(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel<T> { throw null; }
399+
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
400+
public static System.BinaryData Write<T>(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
397401
public static System.BinaryData Write<T>(T model, System.ClientModel.Primitives.ModelReaderWriterOptions options, System.ClientModel.Primitives.ModelReaderWriterContext context) { throw null; }
398402
}
399403
[System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true)]

sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,11 +387,11 @@ public static partial class ModelReaderWriter
387387
{
388388
public static object? Read(System.BinaryData data, System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
389389
public static object? Read(System.BinaryData data, System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions options, System.ClientModel.Primitives.ModelReaderWriterContext context) { throw null; }
390-
public static T? Read<T>(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel<T> { throw null; }
390+
public static T? Read<T>(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
391391
public static T? Read<T>(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions options, System.ClientModel.Primitives.ModelReaderWriterContext context) { throw null; }
392392
public static System.BinaryData Write(object model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
393393
public static System.BinaryData Write(object model, System.ClientModel.Primitives.ModelReaderWriterOptions options, System.ClientModel.Primitives.ModelReaderWriterContext context) { throw null; }
394-
public static System.BinaryData Write<T>(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel<T> { throw null; }
394+
public static System.BinaryData Write<T>(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
395395
public static System.BinaryData Write<T>(T model, System.ClientModel.Primitives.ModelReaderWriterOptions options, System.ClientModel.Primitives.ModelReaderWriterContext context) { throw null; }
396396
}
397397
[System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true)]

sdk/core/System.ClientModel/src/ModelReaderWriter/JsonModelConverter.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,12 @@ public override bool CanConvert(Type typeToConvert)
7878
return _context.GetTypeBuilder(typeToConvert).CreateObject() as IJsonModel<object>;
7979
}
8080

81-
[UnconditionalSuppressMessage("Trimming", "IL2067",
81+
[UnconditionalSuppressMessage("Trimming", "IL2026",
8282
Justification = "We will only call this when we went through a constructor that is marked with RequiresUnreferencedCode.")]
8383
IJsonModel<object>? NonAotCompatActivate()
8484
{
8585
Debug.Assert(_context is null, "This should only be called when _context is null.");
86-
var context = new ReflectionContext();
87-
return context.GetTypeBuilder(typeToConvert).CreateObject() as IJsonModel<object>;
86+
return new ReflectionModelBuilder(typeToConvert).CreateObject() as IJsonModel<object>;
8887
}
8988

9089
IJsonModel<object>? iJsonModel = _context is null ? NonAotCompatActivate() : AotCompatActivate();

sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ namespace System.ClientModel.Primitives;
1313
/// </summary>
1414
public static class ModelReaderWriter
1515
{
16-
private static readonly Lazy<ReflectionContext> s_reflectionContext = new(() => new());
17-
1816
/// <summary>
1917
/// Converts the value of a model into a <see cref="BinaryData"/>.
2018
/// </summary>
@@ -24,8 +22,9 @@ public static class ModelReaderWriter
2422
/// <returns>A <see cref="BinaryData"/> representation of the model in the <see cref="ModelReaderWriterOptions.Format"/> specified by the <paramref name="options"/>.</returns>
2523
/// <exception cref="FormatException">If the model does not support the requested <see cref="ModelReaderWriterOptions.Format"/>.</exception>
2624
/// <exception cref="ArgumentNullException">If <paramref name="model"/> is null.</exception>
25+
[RequiresDynamicCode("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
26+
[RequiresUnreferencedCode("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
2727
public static BinaryData Write<T>(T model, ModelReaderWriterOptions? options = default)
28-
where T : IPersistableModel<T>
2928
{
3029
if (model is null)
3130
{
@@ -34,7 +33,7 @@ public static BinaryData Write<T>(T model, ModelReaderWriterOptions? options = d
3433

3534
options ??= ModelReaderWriterOptions.Json;
3635

37-
return WritePersistable(model, options);
36+
return WritePersistableOrEnumerable(model, options, ModelReaderWriterReflectionContext.Default);
3837
}
3938

4039
/// <summary>
@@ -46,6 +45,8 @@ public static BinaryData Write<T>(T model, ModelReaderWriterOptions? options = d
4645
/// <exception cref="InvalidOperationException">Throws if <paramref name="model"/> does not implement <see cref="IPersistableModel{T}"/>.</exception>
4746
/// <exception cref="FormatException">If the model does not support the requested <see cref="ModelReaderWriterOptions.Format"/>.</exception>
4847
/// <exception cref="ArgumentNullException">If <paramref name="model"/> is null.</exception>
48+
[RequiresDynamicCode("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
49+
[RequiresUnreferencedCode("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
4950
public static BinaryData Write(object model, ModelReaderWriterOptions? options = default)
5051
{
5152
if (model is null)
@@ -55,16 +56,7 @@ public static BinaryData Write(object model, ModelReaderWriterOptions? options =
5556

5657
options ??= ModelReaderWriterOptions.Json;
5758

58-
//temp blocking this for symetry of functionality on read/write with no context.
59-
//will be allowed after https://github.com/Azure/azure-sdk-for-net/issues/48294
60-
if (model is IPersistableModel<object> iModel)
61-
{
62-
return WritePersistable(iModel, options);
63-
}
64-
else
65-
{
66-
throw new InvalidOperationException($"{model.GetType().ToFriendlyName()} does not implement IPersistableModel");
67-
}
59+
return WritePersistableOrEnumerable(model, options, ModelReaderWriterReflectionContext.Default);
6860
}
6961

7062
/// <summary>
@@ -168,12 +160,11 @@ private static BinaryData WritePersistable<T>(IPersistableModel<T> model, ModelR
168160
/// <exception cref="FormatException">If the model does not support the requested <see cref="ModelReaderWriterOptions.Format"/>.</exception>
169161
/// <exception cref="ArgumentNullException">If <paramref name="data"/> is null.</exception>
170162
/// <exception cref="MissingMethodException">If <typeparamref name="T"/> does not have a public or non public empty constructor.</exception>
171-
public static T? Read<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>(
172-
BinaryData data,
173-
ModelReaderWriterOptions? options = default)
174-
where T : IPersistableModel<T>
163+
[RequiresDynamicCode("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
164+
[RequiresUnreferencedCode("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
165+
public static T? Read<T>(BinaryData data, ModelReaderWriterOptions? options = default)
175166
{
176-
return ReadInternal<T>(data, options ??= ModelReaderWriterOptions.Json, s_reflectionContext.Value);
167+
return ReadInternal<T>(data, options ??= ModelReaderWriterOptions.Json, ModelReaderWriterReflectionContext.Default);
177168
}
178169

179170
/// <summary>
@@ -214,12 +205,11 @@ private static BinaryData WritePersistable<T>(IPersistableModel<T> model, ModelR
214205
/// <exception cref="FormatException">If the model does not support the requested <see cref="ModelReaderWriterOptions.Format"/>.</exception>
215206
/// <exception cref="ArgumentNullException">If <paramref name="data"/> or <paramref name="returnType"/> are null.</exception>
216207
/// <exception cref="MissingMethodException">If <paramref name="returnType"/> does not have a public or non public empty constructor.</exception>
217-
public static object? Read(
218-
BinaryData data,
219-
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type returnType,
220-
ModelReaderWriterOptions? options = default)
208+
[RequiresDynamicCode("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
209+
[RequiresUnreferencedCode("This method uses reflection. Use the overload that takes a ModelReaderWriterContext to be AOT compatible.")]
210+
public static object? Read(BinaryData data, Type returnType, ModelReaderWriterOptions? options = default)
221211
{
222-
return ReadInternal(data, returnType, options ??= ModelReaderWriterOptions.Json, s_reflectionContext.Value);
212+
return ReadInternal(data, returnType, options ??= ModelReaderWriterOptions.Json, ModelReaderWriterReflectionContext.Default);
223213
}
224214

225215
/// <summary>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.ClientModel.Internal;
5+
using System.Collections.Concurrent;
6+
using System.Collections.ObjectModel;
7+
using System.Diagnostics.CodeAnalysis;
8+
9+
namespace System.ClientModel.Primitives;
10+
11+
[RequiresDynamicCode("This class uses reflection. Pass in a generated ModelReaderWriterContext to be AOT compatible.")]
12+
[RequiresUnreferencedCode("This class uses reflection. Pass in a generated ModelReaderWriterContext to be AOT compatible.")]
13+
internal class ModelReaderWriterReflectionContext : ModelReaderWriterContext
14+
{
15+
private ConcurrentDictionary<Type, ModelReaderWriterTypeBuilder>? _typeBuilders;
16+
private ConcurrentDictionary<Type, ModelReaderWriterTypeBuilder> TypeBuilders =>
17+
LazyInitializer.EnsureInitialized(ref _typeBuilders, static () => [])!;
18+
19+
private ConcurrentDictionary<Type, Func<Type, ModelReaderWriterTypeBuilder>>? _typeBuilderFactories;
20+
private ConcurrentDictionary<Type, Func<Type, ModelReaderWriterTypeBuilder>> TypeBuilderFactories =>
21+
LazyInitializer.EnsureInitialized(ref _typeBuilderFactories, static () => [])!;
22+
23+
private static ModelReaderWriterReflectionContext? _instance;
24+
public static ModelReaderWriterReflectionContext Default => _instance ??= new ModelReaderWriterReflectionContext();
25+
26+
private ModelReaderWriterReflectionContext()
27+
{
28+
TypeBuilderFactories.TryAdd(typeof(Collection<>), (type) => new ReflectionCollectionBuilder(type));
29+
TypeBuilderFactories.TryAdd(typeof(List<>), (type) => new ReflectionCollectionBuilder(type));
30+
TypeBuilderFactories.TryAdd(typeof(HashSet<>), (type) => new ReflectionCollectionBuilder(type));
31+
TypeBuilderFactories.TryAdd(typeof(ObservableCollection<>), (type) => new ReflectionCollectionBuilder(type));
32+
TypeBuilderFactories.TryAdd(typeof(LinkedList<>), (type) => new ReflectionCollectionBuilder(type, "AddLast"));
33+
TypeBuilderFactories.TryAdd(typeof(Queue<>), (type) => new ReflectionCollectionBuilder(type, "Enqueue"));
34+
TypeBuilderFactories.TryAdd(typeof(Stack<>), (type) => new ReflectionCollectionBuilder(type, "Push"));
35+
TypeBuilderFactories.TryAdd(typeof(Dictionary<,>), (type) => new ReflectionDictionaryBuilder(type));
36+
TypeBuilderFactories.TryAdd(typeof(ReadOnlyCollection<>), (type) => new ReflectionReadOnlyCollectionBuilder(type));
37+
TypeBuilderFactories.TryAdd(typeof(ReadOnlyDictionary<,>), (type) => new ReflectionReadOnlyDictionaryBuilder(type));
38+
TypeBuilderFactories.TryAdd(typeof(ReadOnlyMemory<>), (type) =>
39+
(ModelReaderWriterTypeBuilder)Activator.CreateInstance(typeof(ReflectionReadOnlyMemoryBuilder<>).MakeGenericType(type.GenericTypeArguments), type)!);
40+
}
41+
42+
protected override bool TryGetTypeBuilderCore(Type type, out ModelReaderWriterTypeBuilder? builder)
43+
{
44+
if (TypeBuilders.TryGetValue(type, out builder))
45+
{
46+
return true;
47+
}
48+
49+
if (type.IsArray)
50+
{
51+
builder = new ReflectionArrayBuilder(type);
52+
}
53+
else if (type.IsGenericType)
54+
{
55+
var genericType = type.GetGenericTypeDefinition();
56+
if (TypeBuilderFactories.TryGetValue(genericType, out var factory))
57+
{
58+
builder = factory(type);
59+
}
60+
else
61+
{
62+
throw new NotSupportedException($"Unsupported type: {type.ToFriendlyName()}");
63+
}
64+
}
65+
else
66+
{
67+
builder = new ReflectionModelBuilder(type);
68+
}
69+
70+
TypeBuilders.TryAdd(type, builder);
71+
return true;
72+
}
73+
}

0 commit comments

Comments
 (0)