Skip to content

Commit 26188cd

Browse files
committed
Target net8, add trimming support
1 parent 09f91df commit 26188cd

9 files changed

Lines changed: 114 additions & 41 deletions

File tree

ValveKeyValue/ValveKeyValue/DefaultObjectReflector.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1+
using System.Diagnostics.CodeAnalysis;
12
using System.Reflection;
23

34
namespace ValveKeyValue
45
{
56
sealed class DefaultObjectReflector : IObjectReflector
67
{
7-
IEnumerable<IObjectMember> IObjectReflector.GetMembers(object @object)
8+
IEnumerable<IObjectMember> IObjectReflector.GetMembers([DynamicallyAccessedMembers(Trimming.Properties)] Type objectType, object @object)
89
{
10+
Require.NotNull(objectType, nameof(objectType));
911
Require.NotNull(@object, nameof(@object));
1012

11-
var objectType = @object.GetType();
12-
1313
if (IsValueTupleType(objectType))
1414
{
1515
var fields = objectType.GetFields(BindingFlags.Instance | BindingFlags.Public);

ValveKeyValue/ValveKeyValue/FieldMember.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Diagnostics.CodeAnalysis;
12
using System.Reflection;
23

34
namespace ValveKeyValue
@@ -20,6 +21,8 @@ public FieldMember(FieldInfo fieldInfo, object @object)
2021

2122
public string Name => fieldInfo.Name;
2223

24+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2073", Justification = "FieldType")]
25+
[DynamicallyAccessedMembers(Trimming.Properties)]
2326
public Type MemberType => fieldInfo.FieldType;
2427

2528
public object Value

ValveKeyValue/ValveKeyValue/IObjectMember.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
13
namespace ValveKeyValue
24
{
35
interface IObjectMember
@@ -6,6 +8,7 @@ interface IObjectMember
68

79
string Name { get; }
810

11+
[DynamicallyAccessedMembers(Trimming.Properties)]
912
Type MemberType { get; }
1013

1114
object Value { get; set; }
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
13
namespace ValveKeyValue
24
{
35
interface IObjectReflector
46
{
5-
IEnumerable<IObjectMember> GetMembers(object @object);
7+
IEnumerable<IObjectMember> GetMembers([DynamicallyAccessedMembers(Trimming.Properties)] Type objectType, object @object);
68
}
79
}

ValveKeyValue/ValveKeyValue/KVSerializer.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Diagnostics.CodeAnalysis;
12
using ValveKeyValue.Abstraction;
23
using ValveKeyValue.Deserialization;
34
using ValveKeyValue.Deserialization.KeyValues1;
@@ -52,7 +53,7 @@ public KVDocument Deserialize(Stream stream, KVSerializerOptions options = null)
5253
/// <param name="options">Options to use that can influence the deserialization process.</param>
5354
/// <returns>A <typeparamref name="TObject" /> instance representing the KeyValues structure in the stream.</returns>
5455
/// <typeparam name="TObject">The type of object to deserialize.</typeparam>;
55-
public TObject Deserialize<TObject>(Stream stream, KVSerializerOptions options = null)
56+
public TObject Deserialize<[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TObject>(Stream stream, KVSerializerOptions options = null)
5657
{
5758
Require.NotNull(stream, nameof(stream));
5859

@@ -91,7 +92,7 @@ public void Serialize(Stream stream, KVDocument data, KVSerializerOptions option
9192
/// <param name="name">The top-level object name</param>
9293
/// <param name="options">Options to use that can influence the serialization process.</param>
9394
/// <typeparam name="TData">The type of object to serialize.</typeparam>
94-
public void Serialize<TData>(Stream stream, TData data, string name, KVSerializerOptions options = null)
95+
public void Serialize<[DynamicallyAccessedMembers(Trimming.Properties)] TData>(Stream stream, TData data, string name, KVSerializerOptions options = null)
9596
{
9697
Require.NotNull(stream, nameof(stream));
9798

ValveKeyValue/ValveKeyValue/ObjectCopier.cs

Lines changed: 77 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
using System.Collections;
22
using System.Collections.ObjectModel;
3+
using System.Diagnostics.CodeAnalysis;
34
using System.Globalization;
45
using System.Linq;
56
using System.Reflection;
7+
using System.Runtime.CompilerServices;
68
using System.Runtime.ExceptionServices;
7-
using System.Runtime.Serialization;
89

910
namespace ValveKeyValue
1011
{
1112
// TODO: Migrate to IVisitationListener
1213
static class ObjectCopier
1314
{
14-
public static TObject MakeObject<TObject>(KVObject keyValueObject)
15+
public static TObject MakeObject<[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TObject>(KVObject keyValueObject)
1516
=> MakeObject<TObject>(keyValueObject, new DefaultObjectReflector());
1617

17-
public static object MakeObject(Type objectType, KVObject keyValueObject, IObjectReflector reflector)
18+
public static object MakeObject(
19+
[DynamicallyAccessedMembers(Trimming.Properties)] Type objectType, KVObject keyValueObject, IObjectReflector reflector)
1820
=> InvokeGeneric(nameof(MakeObject), objectType, new object[] { keyValueObject, reflector });
1921

20-
public static TObject MakeObject<TObject>(KVObject keyValueObject, IObjectReflector reflector)
22+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2062", Justification = "If the lookup value type exists at runtime then it should have enough for us to introspect.")]
23+
public static TObject MakeObject<[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TObject>(KVObject keyValueObject, IObjectReflector reflector)
2124
{
2225
Require.NotNull(keyValueObject, nameof(keyValueObject));
2326
Require.NotNull(reflector, nameof(reflector));
@@ -43,8 +46,8 @@ public static TObject MakeObject<TObject>(KVObject keyValueObject, IObjectReflec
4346

4447
// The object must remain boxed until it is fully initiallized, as this is the only way
4548
// that we can build a struct due to the nature of struct copying.
46-
var typedObject = FormatterServices.GetUninitializedObject(typeof(TObject));
47-
CopyObject(keyValueObject, typedObject, reflector);
49+
var typedObject = RuntimeHelpers.GetUninitializedObject(typeof(TObject));
50+
CopyObject(keyValueObject, typeof(TObject), typedObject, reflector);
4851
return (TObject)typedObject;
4952
}
5053
else if (TryConvertValueTo<TObject>(keyValueObject.Name, keyValueObject.Value, out var converted))
@@ -58,10 +61,18 @@ public static TObject MakeObject<TObject>(KVObject keyValueObject, IObjectReflec
5861
}
5962
}
6063

61-
public static KVObject FromObject(Type objectType, object managedObject, string topLevelName)
64+
public static KVObject FromObject(
65+
[DynamicallyAccessedMembers(Trimming.Properties)] Type objectType,
66+
object managedObject,
67+
string topLevelName)
6268
=> FromObjectCore(objectType, managedObject, topLevelName, new DefaultObjectReflector(), new HashSet<object>());
6369

64-
static KVObject FromObjectCore(Type objectType, object managedObject, string topLevelName, IObjectReflector reflector, HashSet<object> visitedObjects)
70+
static KVObject FromObjectCore(
71+
[DynamicallyAccessedMembers(Trimming.Properties)] Type objectType,
72+
object managedObject,
73+
string topLevelName,
74+
IObjectReflector reflector,
75+
HashSet<object> visitedObjects)
6576
{
6677
if (managedObject == null)
6778
{
@@ -76,7 +87,12 @@ static KVObject FromObjectCore(Type objectType, object managedObject, string top
7687
return new KVObject(topLevelName, transformedValue);
7788
}
7889

79-
static KVValue ConvertObjectToValue(Type objectType, object managedObject, IObjectReflector reflector, HashSet<object> visitedObjects)
90+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072", Justification = "If the IDictionary's value object already exists at runtime then its properties will too.")]
91+
static KVValue ConvertObjectToValue(
92+
[DynamicallyAccessedMembers(Trimming.Properties)] Type objectType,
93+
object managedObject,
94+
IObjectReflector reflector,
95+
HashSet<object> visitedObjects)
8096
{
8197
if (!objectType.IsValueType && objectType != typeof(string) && !visitedObjects.Add(managedObject))
8298
{
@@ -108,38 +124,35 @@ static KVValue ConvertObjectToValue(Type objectType, object managedObject, IObje
108124
var counter = 0;
109125
foreach (var child in (IEnumerable)managedObject)
110126
{
111-
var childKVObject = CopyObject(child, counter.ToString(), reflector, visitedObjects);
127+
var childKVObject = FromObjectCore(child.GetType(), child, counter.ToString(), reflector, visitedObjects);
112128
childObjects.Add(childKVObject);
113129

114130
counter++;
115131
}
116132
}
117133
else
118134
{
119-
foreach (var member in reflector.GetMembers(managedObject).OrderBy(p => p.Name, StringComparer.InvariantCulture))
135+
foreach (var member in reflector.GetMembers(objectType, managedObject).OrderBy(p => p.Name, StringComparer.InvariantCulture))
120136
{
121137
if (!member.MemberType.IsValueType && member.Value is null)
122138
{
123139
continue;
124140
}
125141

126-
childObjects.Add(CopyObject(member.Value, member.Name, reflector, visitedObjects));
142+
childObjects.Add(FromObjectCore(member.Value.GetType(), member.Value, member.Name, reflector, visitedObjects));
127143
}
128144
}
129145

130146
return childObjects;
131147
}
132148

133-
static KVObject CopyObject(object @object, string name, IObjectReflector reflector, HashSet<object> visitedObjects)
134-
=> FromObjectCore(@object.GetType(), @object, name, reflector, visitedObjects);
135-
136-
static void CopyObject(KVObject kv, object obj, IObjectReflector reflector)
149+
static void CopyObject(KVObject kv, [DynamicallyAccessedMembers(Trimming.Properties)] Type objectType, object obj, IObjectReflector reflector)
137150
{
138151
Require.NotNull(kv, nameof(kv));
139152
Require.NotNull(obj, nameof(obj));
140153
Require.NotNull(reflector, nameof(reflector));
141154

142-
var members = reflector.GetMembers(obj).ToDictionary(m => m.Name, m => m, StringComparer.OrdinalIgnoreCase);
155+
var members = reflector.GetMembers(objectType, obj).ToDictionary(m => m.Name, m => m, StringComparer.OrdinalIgnoreCase);
143156

144157
foreach (var item in kv.Children)
145158
{
@@ -148,7 +161,8 @@ static void CopyObject(KVObject kv, object obj, IObjectReflector reflector)
148161
continue;
149162
}
150163

151-
member.Value = MakeObject(member.MemberType, item, reflector);
164+
var convertedValue = MakeObject(member.MemberType, item, reflector);
165+
member.Value = convertedValue;
152166
}
153167
}
154168

@@ -208,11 +222,18 @@ static bool IsLookupWithStringKey(Type type, out Type valueType)
208222
return true;
209223
}
210224

211-
static object MakeLookup(Type valueType, IEnumerable<KVObject> items, IObjectReflector reflector)
225+
static object MakeLookup(
226+
[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] Type valueType,
227+
IEnumerable<KVObject> items,
228+
IObjectReflector reflector)
212229
=> InvokeGeneric(nameof(MakeLookupCore), valueType, new object[] { items, reflector });
213230

214-
static ILookup<string, TValue> MakeLookupCore<TValue>(IEnumerable<KVObject> items, IObjectReflector reflector)
215-
=> items.ToLookup(kv => kv.Name, kv => ConvertValue<TValue>(kv.Value, reflector));
231+
static ILookup<string, TValue> MakeLookupCore<[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TValue>(IEnumerable<KVObject> items, IObjectReflector reflector)
232+
{
233+
TValue valueConversionFunc(KVObject kv) => ConvertValue<TValue>(kv.Value, reflector);
234+
235+
return items.ToLookup(kv => kv.Name, valueConversionFunc);
236+
}
216237

217238
static readonly Dictionary<Type, Func<Type, object[], IObjectReflector, object>> EnumerableBuilders = new()
218239
{
@@ -223,7 +244,13 @@ static ILookup<string, TValue> MakeLookupCore<TValue>(IEnumerable<KVObject> item
223244
[typeof(ObservableCollection<>)] = (type, values, reflector) => InvokeGeneric(nameof(MakeObservableCollection), type.GetGenericArguments()[0], new object[] { values, reflector }),
224245
};
225246

226-
static bool ConstructTypedEnumerable(Type type, object[] values, IObjectReflector reflector, out object typedEnumerable)
247+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072", Justification = "If our T[] array exists then so much the element T.")]
248+
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "If our T[] array exists then so much the element T.")]
249+
static bool ConstructTypedEnumerable(
250+
Type type,
251+
object[] values,
252+
IObjectReflector reflector,
253+
out object typedEnumerable)
227254
{
228255
object listObject = null;
229256

@@ -274,6 +301,9 @@ static bool IsConstructibleEnumerableType(Type type)
274301
return false;
275302
}
276303

304+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060", Justification = "Analysis cannot follow MakeGenericMethod. All callers validated manually.")]
305+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111", Justification = "Analysis cannot follow MakeGenericMethod. All callers validated manually.")]
306+
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Analysis cannot follow MakeGenericMethod. All callers validated manually.")]
277307
static object InvokeGeneric(string methodName, Type genericType, params object[] parameters)
278308
{
279309
var method = typeof(ObjectCopier)
@@ -292,18 +322,22 @@ static object InvokeGeneric(string methodName, Type genericType, params object[]
292322
}
293323
}
294324

295-
static List<TElement> MakeList<TElement>(object[] items, IObjectReflector reflector)
325+
static List<TElement> MakeList<[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TElement>(object[] items, IObjectReflector reflector)
296326
{
297-
return items.Select(i => ConvertValue<TElement>(i, reflector))
298-
.ToList();
327+
var list = new List<TElement>(capacity: items.Length);
328+
foreach (var item in items)
329+
{
330+
list.Add(ConvertValue<TElement>(item, reflector));
331+
}
332+
return list;
299333
}
300334

301-
static Collection<TElement> MakeCollection<TElement>(object[] items, IObjectReflector reflector)
335+
static Collection<TElement> MakeCollection<[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TElement>(object[] items, IObjectReflector reflector)
302336
{
303337
return new Collection<TElement>(MakeList<TElement>(items, reflector));
304338
}
305339

306-
static ObservableCollection<TElement> MakeObservableCollection<TElement>(object[] items, IObjectReflector reflector)
340+
static ObservableCollection<TElement> MakeObservableCollection<[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TElement>(object[] items, IObjectReflector reflector)
307341
{
308342
return new ObservableCollection<TElement>(MakeList<TElement>(items, reflector));
309343
}
@@ -334,20 +368,25 @@ static bool IsDictionary(Type type)
334368
return true;
335369
}
336370

337-
static object MakeDictionary(Type type, KVObject kv, IObjectReflector reflector)
371+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060", Justification = "Analysis cannot follow MakeGenericMethod but we should be clear by here anyway.")]
372+
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Analysis cannot follow MakeGenericMethod but we should be clear by here anyway.")]
373+
static object MakeDictionary(
374+
[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] Type type,
375+
KVObject kv,
376+
IObjectReflector reflector)
338377
{
339378
var dictionary = Activator.CreateInstance(type);
340379
var genericArguments = type.GetGenericArguments();
341380

342-
typeof(ObjectCopier)
343-
.GetMethod(nameof(FillDictionary), BindingFlags.Static | BindingFlags.NonPublic)
344-
.MakeGenericMethod(genericArguments)
381+
var method = typeof(ObjectCopier)
382+
.GetMethod(nameof(FillDictionary), BindingFlags.Static | BindingFlags.NonPublic);
383+
method.MakeGenericMethod(genericArguments)
345384
.Invoke(null, new[] { dictionary, kv, reflector });
346385

347386
return dictionary;
348387
}
349388

350-
static void FillDictionary<TKey, TValue>(Dictionary<TKey, TValue> dictionary, KVObject kv, IObjectReflector reflector)
389+
static void FillDictionary<[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TKey, [DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TValue>(Dictionary<TKey, TValue> dictionary, KVObject kv, IObjectReflector reflector)
351390
{
352391
foreach (var item in kv.Children)
353392
{
@@ -363,9 +402,13 @@ static void FillDictionary<TKey, TValue>(Dictionary<TKey, TValue> dictionary, KV
363402
}
364403
}
365404

366-
static TValue ConvertValue<TValue>(object value, IObjectReflector reflector) => (TValue)ConvertValue(value, typeof(TValue), reflector);
405+
static TValue ConvertValue<[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TValue>(object value, IObjectReflector reflector)
406+
=> (TValue)ConvertValue(value, typeof(TValue), reflector);
367407

368-
static object ConvertValue(object value, Type valueType, IObjectReflector reflector)
408+
static object ConvertValue(
409+
object value,
410+
[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] Type valueType,
411+
IObjectReflector reflector)
369412
{
370413
if (value is KVCollectionValue collectionValue)
371414
{

ValveKeyValue/ValveKeyValue/PropertyMember.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Diagnostics.CodeAnalysis;
12
using System.Reflection;
23

34
namespace ValveKeyValue
@@ -21,6 +22,8 @@ public PropertyMember(PropertyInfo propertyInfo, object @object)
2122
string IObjectMember.Name
2223
=> PropertyAttribute?.PropertyName ?? propertyInfo.Name;
2324

25+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2073", Justification = "PropertyType")]
26+
[DynamicallyAccessedMembers(Trimming.Properties)]
2427
Type IObjectMember.MemberType => propertyInfo.PropertyType;
2528

2629
object IObjectMember.Value
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace ValveKeyValue
4+
{
5+
internal static class Trimming
6+
{
7+
public const DynamicallyAccessedMemberTypes Constructors =
8+
DynamicallyAccessedMemberTypes.PublicParameterlessConstructor |
9+
DynamicallyAccessedMemberTypes.PublicConstructors |
10+
DynamicallyAccessedMemberTypes.NonPublicConstructors;
11+
12+
public const DynamicallyAccessedMemberTypes Properties =
13+
DynamicallyAccessedMemberTypes.PublicProperties |
14+
DynamicallyAccessedMemberTypes.NonPublicProperties |
15+
DynamicallyAccessedMemberTypes.PublicFields;
16+
}
17+
}

ValveKeyValue/ValveKeyValue/ValveKeyValue.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFramework>netstandard2.1</TargetFramework>
3+
<TargetFramework>net8.0</TargetFramework>
44
<LangVersion>10.0</LangVersion>
55
<Product>Valve KeyValue Library</Product>
66
<Description>Library to parse and write Valve KeyValue formats</Description>
@@ -10,6 +10,7 @@
1010
<PackageTags>steam valve keyvalues keyvalue kv kv3 csgo dota2 tf2</PackageTags>
1111
<SignAssembly>true</SignAssembly>
1212
<AssemblyOriginatorKeyFile>ValveKeyValue.snk</AssemblyOriginatorKeyFile>
13+
<IsAotCompatible>true</IsAotCompatible>
1314
</PropertyGroup>
1415
<ItemGroup>
1516
<None Include="..\..\README.md" Pack="true" PackagePath="\"/>

0 commit comments

Comments
 (0)