Skip to content

Commit e234b1d

Browse files
committed
Target net8, add trimming support
1 parent c7d0dcc commit e234b1d

9 files changed

Lines changed: 117 additions & 40 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: 80 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
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+
public static TObject MakeObject<[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TObject>(KVObject keyValueObject, IObjectReflector reflector)
2123
{
2224
Require.NotNull(keyValueObject, nameof(keyValueObject));
2325
Require.NotNull(reflector, nameof(reflector));
@@ -43,7 +45,7 @@ public static TObject MakeObject<TObject>(KVObject keyValueObject, IObjectReflec
4345

4446
// The object must remain boxed until it is fully initiallized, as this is the only way
4547
// that we can build a struct due to the nature of struct copying.
46-
var typedObject = FormatterServices.GetUninitializedObject(typeof(TObject));
48+
var typedObject = RuntimeHelpers.GetUninitializedObject(typeof(TObject));
4749
CopyObject(keyValueObject, typedObject, reflector);
4850
return (TObject)typedObject;
4951
}
@@ -58,10 +60,18 @@ public static TObject MakeObject<TObject>(KVObject keyValueObject, IObjectReflec
5860
}
5961
}
6062

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

64-
static KVObject FromObjectCore(Type objectType, object managedObject, string topLevelName, IObjectReflector reflector, HashSet<object> visitedObjects)
69+
static KVObject FromObjectCore(
70+
[DynamicallyAccessedMembers(Trimming.Properties)] Type objectType,
71+
object managedObject,
72+
string topLevelName,
73+
IObjectReflector reflector,
74+
HashSet<object> visitedObjects)
6575
{
6676
if (managedObject == null)
6777
{
@@ -76,7 +86,11 @@ static KVObject FromObjectCore(Type objectType, object managedObject, string top
7686
return new KVObject(topLevelName, transformedValue);
7787
}
7888

79-
static KVValue ConvertObjectToValue(Type objectType, object managedObject, IObjectReflector reflector, HashSet<object> visitedObjects)
89+
static KVValue ConvertObjectToValue(
90+
[DynamicallyAccessedMembers(Trimming.Properties)] Type objectType,
91+
object managedObject,
92+
IObjectReflector reflector,
93+
HashSet<object> visitedObjects)
8094
{
8195
if (!objectType.IsValueType && objectType != typeof(string) && !visitedObjects.Add(managedObject))
8296
{
@@ -108,38 +122,40 @@ static KVValue ConvertObjectToValue(Type objectType, object managedObject, IObje
108122
var counter = 0;
109123
foreach (var child in (IEnumerable)managedObject)
110124
{
111-
var childKVObject = CopyObject(child, counter.ToString(), reflector, visitedObjects);
125+
var childKVObject = FromObjectCore(child.GetType(), child, counter.ToString(), reflector, visitedObjects);
112126
childObjects.Add(childKVObject);
113127

114128
counter++;
115129
}
116130
}
117131
else
118132
{
119-
foreach (var member in reflector.GetMembers(managedObject).OrderBy(p => p.Name, StringComparer.InvariantCulture))
133+
foreach (var member in reflector.GetMembers(objectType, managedObject).OrderBy(p => p.Name, StringComparer.InvariantCulture))
120134
{
121135
if (!member.MemberType.IsValueType && member.Value is null)
122136
{
123137
continue;
124138
}
125139

126-
childObjects.Add(CopyObject(member.Value, member.Name, reflector, visitedObjects));
140+
childObjects.Add(FromObjectCore(member.Value.GetType(), member.Value, member.Name, reflector, visitedObjects));
127141
}
128142
}
129143

130144
return childObjects;
131145
}
132146

133-
static KVObject CopyObject(object @object, string name, IObjectReflector reflector, HashSet<object> visitedObjects)
134-
=> FromObjectCore(@object.GetType(), @object, name, reflector, visitedObjects);
135-
136147
static void CopyObject(KVObject kv, object obj, IObjectReflector reflector)
137148
{
138149
Require.NotNull(kv, nameof(kv));
139-
Require.NotNull(obj, nameof(obj));
150+
151+
if (obj is null)
152+
{
153+
throw new ArgumentNullException(nameof(obj));
154+
}
155+
140156
Require.NotNull(reflector, nameof(reflector));
141157

142-
var members = reflector.GetMembers(obj).ToDictionary(m => m.Name, m => m, StringComparer.OrdinalIgnoreCase);
158+
var members = reflector.GetMembers(obj.GetType(), obj).ToDictionary(m => m.Name, m => m, StringComparer.OrdinalIgnoreCase);
143159

144160
foreach (var item in kv.Children)
145161
{
@@ -148,7 +164,8 @@ static void CopyObject(KVObject kv, object obj, IObjectReflector reflector)
148164
continue;
149165
}
150166

151-
member.Value = MakeObject(member.MemberType, item, reflector);
167+
var convertedValue = MakeObject(member.MemberType, item, reflector);
168+
member.Value = convertedValue;
152169
}
153170
}
154171

@@ -208,11 +225,18 @@ static bool IsLookupWithStringKey(Type type, out Type valueType)
208225
return true;
209226
}
210227

211-
static object MakeLookup(Type valueType, IEnumerable<KVObject> items, IObjectReflector reflector)
228+
static object MakeLookup(
229+
[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] Type valueType,
230+
IEnumerable<KVObject> items,
231+
IObjectReflector reflector)
212232
=> InvokeGeneric(nameof(MakeLookupCore), valueType, new object[] { items, reflector });
213233

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

217241
static readonly Dictionary<Type, Func<Type, object[], IObjectReflector, object>> EnumerableBuilders = new()
218242
{
@@ -223,7 +247,13 @@ static ILookup<string, TValue> MakeLookupCore<TValue>(IEnumerable<KVObject> item
223247
[typeof(ObservableCollection<>)] = (type, values, reflector) => InvokeGeneric(nameof(MakeObservableCollection), type.GetGenericArguments()[0], new object[] { values, reflector }),
224248
};
225249

226-
static bool ConstructTypedEnumerable(Type type, object[] values, IObjectReflector reflector, out object typedEnumerable)
250+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072", Justification = "If our T[] array exists then so much the element T.")]
251+
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "If our T[] array exists then so much the element T.")]
252+
static bool ConstructTypedEnumerable(
253+
Type type,
254+
object[] values,
255+
IObjectReflector reflector,
256+
out object typedEnumerable)
227257
{
228258
object listObject = null;
229259

@@ -274,6 +304,9 @@ static bool IsConstructibleEnumerableType(Type type)
274304
return false;
275305
}
276306

307+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060", Justification = "Analysis cannot follow MakeGenericMethod. All callers validated manually.")]
308+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111", Justification = "Analysis cannot follow MakeGenericMethod. All callers validated manually.")]
309+
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Analysis cannot follow MakeGenericMethod. All callers validated manually.")]
277310
static object InvokeGeneric(string methodName, Type genericType, params object[] parameters)
278311
{
279312
var method = typeof(ObjectCopier)
@@ -292,18 +325,22 @@ static object InvokeGeneric(string methodName, Type genericType, params object[]
292325
}
293326
}
294327

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

301-
static Collection<TElement> MakeCollection<TElement>(object[] items, IObjectReflector reflector)
338+
static Collection<TElement> MakeCollection<[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TElement>(object[] items, IObjectReflector reflector)
302339
{
303340
return new Collection<TElement>(MakeList<TElement>(items, reflector));
304341
}
305342

306-
static ObservableCollection<TElement> MakeObservableCollection<TElement>(object[] items, IObjectReflector reflector)
343+
static ObservableCollection<TElement> MakeObservableCollection<[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TElement>(object[] items, IObjectReflector reflector)
307344
{
308345
return new ObservableCollection<TElement>(MakeList<TElement>(items, reflector));
309346
}
@@ -334,20 +371,26 @@ static bool IsDictionary(Type type)
334371
return true;
335372
}
336373

337-
static object MakeDictionary(Type type, KVObject kv, IObjectReflector reflector)
374+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060", Justification = "Analysis cannot follow MakeGenericMethod but we should be clear by here anyway.")]
375+
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Analysis cannot follow MakeGenericMethod but we should be clear by here anyway.")]
376+
static object MakeDictionary(
377+
[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)]
378+
Type type,
379+
KVObject kv,
380+
IObjectReflector reflector)
338381
{
339382
var dictionary = Activator.CreateInstance(type);
340383
var genericArguments = type.GetGenericArguments();
341384

342-
typeof(ObjectCopier)
343-
.GetMethod(nameof(FillDictionary), BindingFlags.Static | BindingFlags.NonPublic)
344-
.MakeGenericMethod(genericArguments)
385+
var method = typeof(ObjectCopier)
386+
.GetMethod(nameof(FillDictionary), BindingFlags.Static | BindingFlags.NonPublic);
387+
method.MakeGenericMethod(genericArguments)
345388
.Invoke(null, new[] { dictionary, kv, reflector });
346389

347390
return dictionary;
348391
}
349392

350-
static void FillDictionary<TKey, TValue>(Dictionary<TKey, TValue> dictionary, KVObject kv, IObjectReflector reflector)
393+
static void FillDictionary<[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TKey, [DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TValue>(Dictionary<TKey, TValue> dictionary, KVObject kv, IObjectReflector reflector)
351394
{
352395
foreach (var item in kv.Children)
353396
{
@@ -363,9 +406,13 @@ static void FillDictionary<TKey, TValue>(Dictionary<TKey, TValue> dictionary, KV
363406
}
364407
}
365408

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

368-
static object ConvertValue(object value, Type valueType, IObjectReflector reflector)
412+
static object ConvertValue(
413+
object value,
414+
[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] Type valueType,
415+
IObjectReflector reflector)
369416
{
370417
if (value is KVCollectionValue collectionValue)
371418
{

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)