Skip to content

Commit fa9dd9c

Browse files
authored
Slightly better support for NativeAOT and trimming (#35211)
* Slightly better support for NativeAOT and trimming - Use explicit reflection to make sure primitive TryParse method are preserved for trimming - Also use explicit reflection to preserve HttpContext properties when using expression tree compilation. - This doesn't work for custom types (those need to be preserved by the application).
1 parent 625cad3 commit fa9dd9c

File tree

2 files changed

+155
-119
lines changed

2 files changed

+155
-119
lines changed

src/Http/Http.Extensions/src/RequestDelegateFactory.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ public static partial class RequestDelegateFactory
4444
private static readonly ParameterExpression BodyValueExpr = Expression.Parameter(typeof(object), "bodyValue");
4545
private static readonly ParameterExpression WasParamCheckFailureExpr = Expression.Variable(typeof(bool), "wasParamCheckFailure");
4646

47-
private static readonly MemberExpression RequestServicesExpr = Expression.Property(HttpContextExpr, nameof(HttpContext.RequestServices));
48-
private static readonly MemberExpression HttpRequestExpr = Expression.Property(HttpContextExpr, nameof(HttpContext.Request));
49-
private static readonly MemberExpression HttpResponseExpr = Expression.Property(HttpContextExpr, nameof(HttpContext.Response));
50-
private static readonly MemberExpression RequestAbortedExpr = Expression.Property(HttpContextExpr, nameof(HttpContext.RequestAborted));
51-
private static readonly MemberExpression UserExpr = Expression.Property(HttpContextExpr, nameof(HttpContext.User));
52-
private static readonly MemberExpression RouteValuesExpr = Expression.Property(HttpRequestExpr, nameof(HttpRequest.RouteValues));
53-
private static readonly MemberExpression QueryExpr = Expression.Property(HttpRequestExpr, nameof(HttpRequest.Query));
54-
private static readonly MemberExpression HeadersExpr = Expression.Property(HttpRequestExpr, nameof(HttpRequest.Headers));
55-
private static readonly MemberExpression StatusCodeExpr = Expression.Property(HttpResponseExpr, nameof(HttpResponse.StatusCode));
47+
private static readonly MemberExpression RequestServicesExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.RequestServices))!);
48+
private static readonly MemberExpression HttpRequestExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.Request))!);
49+
private static readonly MemberExpression HttpResponseExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.Response))!);
50+
private static readonly MemberExpression RequestAbortedExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.RequestAborted))!);
51+
private static readonly MemberExpression UserExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.User))!);
52+
private static readonly MemberExpression RouteValuesExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.RouteValues))!);
53+
private static readonly MemberExpression QueryExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.Query))!);
54+
private static readonly MemberExpression HeadersExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.Headers))!);
55+
private static readonly MemberExpression StatusCodeExpr = Expression.Property(HttpResponseExpr, typeof(HttpResponse).GetProperty(nameof(HttpResponse.StatusCode))!);
5656
private static readonly MemberExpression CompletedTaskExpr = Expression.Property(null, (PropertyInfo)GetMemberInfo<Func<Task>>(() => Task.CompletedTask));
5757

5858
private static readonly BinaryExpression TempSourceStringNotNullExpr = Expression.NotEqual(TryParseMethodCache.TempSourceStringExpr, Expression.Constant(null));

src/Shared/TryParseMethodCache.cs

Lines changed: 146 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
54
using System.Collections.Concurrent;
65
using System.Diagnostics;
76
using System.Diagnostics.CodeAnalysis;
87
using System.Globalization;
9-
using System.Linq;
108
using System.Linq.Expressions;
119
using System.Numerics;
1210
using System.Reflection;
11+
using System.Runtime.CompilerServices;
1312

1413
#nullable enable
1514

@@ -24,7 +23,9 @@ internal sealed class TryParseMethodCache
2423

2524
internal readonly ParameterExpression TempSourceStringExpr = Expression.Variable(typeof(string), "tempSourceString");
2625

27-
public TryParseMethodCache() : this(preferNonGenericEnumParseOverload: false)
26+
// If IsDynamicCodeSupported is false, we can't use the static Enum.TryParse<T> since there's no easy way for
27+
// this code to generate the specific instantiation for any enums used
28+
public TryParseMethodCache() : this(preferNonGenericEnumParseOverload: !RuntimeFeature.IsDynamicCodeSupported)
2829
{
2930
}
3031

@@ -121,146 +122,181 @@ public bool HasTryParseMethod(ParameterInfo parameter)
121122

122123
private static MethodInfo GetEnumTryParseMethod(bool preferNonGenericEnumParseOverload)
123124
{
124-
var staticEnumMethods = typeof(Enum).GetMethods(BindingFlags.Public | BindingFlags.Static);
125+
MethodInfo? methodInfo = null;
125126

126-
// With NativeAOT, if there's no static usage of Enum.TryParse<T>, it will be removed
127-
// we fallback to the non-generic version if that is the case
128-
MethodInfo? genericCandidate = null;
129-
MethodInfo? nonGenericCandidate = null;
130-
131-
foreach (var method in staticEnumMethods)
127+
if (preferNonGenericEnumParseOverload)
132128
{
133-
if (method.Name != nameof(Enum.TryParse) || method.ReturnType != typeof(bool))
134-
{
135-
continue;
136-
}
137-
138-
var tryParseParameters = method.GetParameters();
139-
140-
// Enum.TryParse<T>(string, out object)
141-
if (method.IsGenericMethod &&
142-
tryParseParameters.Length == 2 &&
143-
tryParseParameters[0].ParameterType == typeof(string) &&
144-
tryParseParameters[1].IsOut)
145-
{
146-
genericCandidate = method;
147-
}
148-
149-
// Enum.TryParse(type, string, out object)
150-
if (!method.IsGenericMethod &&
151-
tryParseParameters.Length == 3 &&
152-
tryParseParameters[0].ParameterType == typeof(Type) &&
153-
tryParseParameters[1].ParameterType == typeof(string) &&
154-
tryParseParameters[2].IsOut)
155-
{
156-
nonGenericCandidate = method;
157-
}
129+
methodInfo = typeof(Enum).GetMethod(
130+
nameof(Enum.TryParse),
131+
BindingFlags.Public | BindingFlags.Static,
132+
new[] { typeof(Type), typeof(string), typeof(object).MakeByRefType() });
158133
}
159-
160-
if (genericCandidate is null && nonGenericCandidate is null)
134+
else
161135
{
162-
Debug.Fail("No suitable System.Enum.TryParse method found.");
163-
throw new MissingMethodException("No suitable System.Enum.TryParse method found.");
136+
methodInfo = typeof(Enum).GetMethod(
137+
nameof(Enum.TryParse),
138+
genericParameterCount: 1,
139+
new[] { typeof(string), Type.MakeGenericMethodParameter(0).MakeByRefType() });
164140
}
165141

166-
if (preferNonGenericEnumParseOverload)
142+
if (methodInfo is null)
167143
{
168-
return nonGenericCandidate!;
144+
Debug.Fail("No suitable System.Enum.TryParse method found.");
145+
throw new MissingMethodException("No suitable System.Enum.TryParse method found.");
169146
}
170147

171-
return genericCandidate ?? nonGenericCandidate!;
148+
return methodInfo!;
172149
}
173150

174151
private static bool TryGetDateTimeTryParseMethod(Type type, [NotNullWhen(true)] out MethodInfo? methodInfo)
175152
{
176153
methodInfo = null;
177-
if (type != typeof(DateTime) && type != typeof(DateOnly) &&
178-
type != typeof(DateTimeOffset) && type != typeof(TimeOnly))
154+
155+
if (type == typeof(DateTime))
179156
{
180-
return false;
157+
methodInfo = typeof(DateTime).GetMethod(
158+
nameof(DateTime.TryParse),
159+
BindingFlags.Public | BindingFlags.Static,
160+
new[] { typeof(string), typeof(IFormatProvider), typeof(DateTimeStyles), typeof(DateTime).MakeByRefType() });
161+
}
162+
else if (type == typeof(DateTimeOffset))
163+
{
164+
methodInfo = typeof(DateTimeOffset).GetMethod(
165+
nameof(DateTimeOffset.TryParse),
166+
BindingFlags.Public | BindingFlags.Static,
167+
new[] { typeof(string), typeof(IFormatProvider), typeof(DateTimeStyles), typeof(DateTimeOffset).MakeByRefType() });
168+
}
169+
else if (type == typeof(DateOnly))
170+
{
171+
methodInfo = typeof(DateOnly).GetMethod(
172+
nameof(DateOnly.TryParse),
173+
BindingFlags.Public | BindingFlags.Static,
174+
new[] { typeof(string), typeof(IFormatProvider), typeof(DateTimeStyles), typeof(DateOnly).MakeByRefType() });
175+
}
176+
else if (type == typeof(TimeOnly))
177+
{
178+
methodInfo = typeof(TimeOnly).GetMethod(
179+
nameof(TimeOnly.TryParse),
180+
BindingFlags.Public | BindingFlags.Static,
181+
new[] { typeof(string), typeof(IFormatProvider), typeof(DateTimeStyles), typeof(TimeOnly).MakeByRefType() });
181182
}
182-
183-
var staticTryParseDateMethod = type.GetMethod(
184-
"TryParse",
185-
BindingFlags.Public | BindingFlags.Static,
186-
new[] { typeof(string), typeof(IFormatProvider), typeof(DateTimeStyles), type.MakeByRefType() });
187-
188-
methodInfo = staticTryParseDateMethod;
189183

190184
return methodInfo != null;
191185
}
192186

193187
private static bool TryGetNumberStylesTryGetMethod(Type type, [NotNullWhen(true)] out MethodInfo? method, [NotNullWhen(true)] out NumberStyles? numberStyles)
194188
{
195189
method = null;
196-
numberStyles = null;
190+
numberStyles = NumberStyles.Integer;
197191

198-
if (!UseTryParseWithNumberStyleOption(type))
192+
if (type == typeof(long))
199193
{
200-
return false;
194+
method = typeof(long).GetMethod(
195+
nameof(long.TryParse),
196+
BindingFlags.Public | BindingFlags.Static,
197+
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(long).MakeByRefType() });
201198
}
202-
203-
var staticMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Static)
204-
.Where(m => m.Name == "TryParse" && m.ReturnType == typeof(bool))
205-
.OrderByDescending(m => m.GetParameters().Length);
206-
207-
var numberStylesToUse = NumberStyles.Integer;
208-
var methodToUse = default(MethodInfo);
209-
210-
foreach (var methodInfo in staticMethods)
199+
else if (type == typeof(ulong))
211200
{
212-
var tryParseParameters = methodInfo.GetParameters();
213-
214-
if (tryParseParameters.Length == 4 &&
215-
tryParseParameters[0].ParameterType == typeof(string) &&
216-
tryParseParameters[1].ParameterType == typeof(NumberStyles) &&
217-
tryParseParameters[2].ParameterType == typeof(IFormatProvider) &&
218-
tryParseParameters[3].IsOut &&
219-
tryParseParameters[3].ParameterType == type.MakeByRefType())
220-
{
221-
if (type == typeof(int) || type == typeof(short) || type == typeof(IntPtr) ||
222-
type == typeof(long) || type == typeof(byte) || type == typeof(sbyte) ||
223-
type == typeof(ushort) || type == typeof(uint) || type == typeof(ulong) ||
224-
type == typeof(BigInteger))
225-
{
226-
numberStylesToUse = NumberStyles.Integer;
227-
}
201+
method = typeof(ulong).GetMethod(
202+
nameof(ulong.TryParse),
203+
BindingFlags.Public | BindingFlags.Static,
204+
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(ulong).MakeByRefType() });
205+
}
206+
else if (type == typeof(int))
207+
{
208+
method = typeof(int).GetMethod(
209+
nameof(int.TryParse),
210+
BindingFlags.Public | BindingFlags.Static,
211+
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(int).MakeByRefType() });
212+
}
213+
else if (type == typeof(uint))
214+
{
215+
method = typeof(uint).GetMethod(
216+
nameof(uint.TryParse),
217+
BindingFlags.Public | BindingFlags.Static,
218+
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(uint).MakeByRefType() });
219+
}
220+
else if (type == typeof(short))
221+
{
222+
method = typeof(short).GetMethod(
223+
nameof(short.TryParse),
224+
BindingFlags.Public | BindingFlags.Static,
225+
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(short).MakeByRefType() });
226+
}
227+
else if (type == typeof(ushort))
228+
{
229+
method = typeof(ushort).GetMethod(
230+
nameof(ushort.TryParse),
231+
BindingFlags.Public | BindingFlags.Static,
232+
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(ushort).MakeByRefType() });
233+
}
234+
else if (type == typeof(byte))
235+
{
236+
method = typeof(byte).GetMethod(
237+
nameof(byte.TryParse),
238+
BindingFlags.Public | BindingFlags.Static,
239+
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(byte).MakeByRefType() });
240+
}
241+
else if (type == typeof(sbyte))
242+
{
243+
method = typeof(sbyte).GetMethod(
244+
nameof(sbyte.TryParse),
245+
BindingFlags.Public | BindingFlags.Static,
246+
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(sbyte).MakeByRefType() });
247+
}
248+
else if (type == typeof(double))
249+
{
250+
method = typeof(double).GetMethod(
251+
nameof(double.TryParse),
252+
BindingFlags.Public | BindingFlags.Static,
253+
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(double).MakeByRefType() });
228254

229-
if (type == typeof(double) || type == typeof(float) || type == typeof(Half))
230-
{
231-
numberStylesToUse = NumberStyles.AllowThousands | NumberStyles.Float;
232-
}
255+
numberStyles = NumberStyles.AllowThousands | NumberStyles.Float;
256+
}
257+
else if (type == typeof(float))
258+
{
259+
method = typeof(float).GetMethod(
260+
nameof(float.TryParse),
261+
BindingFlags.Public | BindingFlags.Static,
262+
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(float).MakeByRefType() });
233263

234-
if (type == typeof(decimal))
235-
{
236-
numberStylesToUse = NumberStyles.Number;
237-
}
264+
numberStyles = NumberStyles.AllowThousands | NumberStyles.Float;
265+
}
266+
else if (type == typeof(Half))
267+
{
268+
method = typeof(Half).GetMethod(
269+
nameof(Half.TryParse),
270+
BindingFlags.Public | BindingFlags.Static,
271+
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(Half).MakeByRefType() });
238272

239-
methodToUse = methodInfo!;
240-
break;
241-
}
273+
numberStyles = NumberStyles.AllowThousands | NumberStyles.Float;
242274
}
275+
else if (type == typeof(decimal))
276+
{
277+
method = typeof(decimal).GetMethod(
278+
nameof(decimal.TryParse),
279+
BindingFlags.Public | BindingFlags.Static,
280+
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(decimal).MakeByRefType() });
243281

244-
numberStyles = numberStylesToUse!;
245-
method = methodToUse!;
282+
numberStyles = NumberStyles.Number;
283+
}
284+
else if (type == typeof(IntPtr))
285+
{
286+
method = typeof(IntPtr).GetMethod(
287+
nameof(IntPtr.TryParse),
288+
BindingFlags.Public | BindingFlags.Static,
289+
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(IntPtr).MakeByRefType() });
290+
}
291+
else if (type == typeof(BigInteger))
292+
{
293+
method = typeof(BigInteger).GetMethod(
294+
nameof(BigInteger.TryParse),
295+
BindingFlags.Public | BindingFlags.Static,
296+
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(BigInteger).MakeByRefType() });
297+
}
246298

247-
return true;
299+
return method != null;
248300
}
249-
250-
internal static bool UseTryParseWithNumberStyleOption(Type type)
251-
=> type == typeof(int) ||
252-
type == typeof(double) ||
253-
type == typeof(decimal) ||
254-
type == typeof(float) ||
255-
type == typeof(Half) ||
256-
type == typeof(short) ||
257-
type == typeof(long) ||
258-
type == typeof(IntPtr) ||
259-
type == typeof(byte) ||
260-
type == typeof(sbyte) ||
261-
type == typeof(ushort) ||
262-
type == typeof(uint) ||
263-
type == typeof(ulong) ||
264-
type == typeof(BigInteger);
265301
}
266302
}

0 commit comments

Comments
 (0)