Skip to content

Commit 9184ba5

Browse files
committed
Improved code generation for ValidateAllProperties generator
Improved type safety, more AOT-friendly, removed Unsafe.As<T> hack
1 parent 4deae23 commit 9184ba5

File tree

2 files changed

+34
-11
lines changed

2 files changed

+34
-11
lines changed

Microsoft.Toolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,11 @@ public void Execute(GeneratorExecutionContext context)
6464

6565
foreach (INamedTypeSymbol classSymbol in syntaxReceiver.GatheredInfo)
6666
{
67-
// Create a static method to validate all properties in a given class.
67+
// Create a static factory method creating a delegate that can be used to validate all properties in a given class.
68+
// This pattern is used so that the library doesn't have to use MakeGenericType(...) at runtime, nor use unsafe casts
69+
// over the created delegate to be able to cache it as an Action<object> instance. This pattern enables the same
70+
// functionality and with almost identical performance (not noticeable in this context anyway), but while preserving
71+
// full runtime type safety (as a safe cast is used to validate the input argument), and with less reflection needed.
6872
// This code takes a class symbol and produces a compilation unit as follows:
6973
//
7074
// // Licensed to the .NET Foundation under one or more agreements.
@@ -84,9 +88,14 @@ public void Execute(GeneratorExecutionContext context)
8488
// {
8589
// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
8690
// [global::System.Obsolete("This method is not intended to be called directly by user code")]
87-
// public static void ValidateAllProperties(<INSTANCE_TYPE> instance)
91+
// public static global::System.Action<object> CreateAllPropertiesValidator(<INSTANCE_TYPE> _)
8892
// {
89-
// <BODY>
93+
// static void ValidateAllProperties(<INSTANCE_TYPE> instance)
94+
// {
95+
// <BODY>
96+
// }
97+
//
98+
// return static o => ValidateAllProperties((<INSTANCE_TYPE>)o);
9099
// }
91100
// }
92101
// }
@@ -102,8 +111,8 @@ public void Execute(GeneratorExecutionContext context)
102111
Token(SyntaxKind.StaticKeyword),
103112
Token(SyntaxKind.PartialKeyword)).AddAttributeLists(classAttributes).AddMembers(
104113
MethodDeclaration(
105-
PredefinedType(Token(SyntaxKind.VoidKeyword)),
106-
Identifier("ValidateAllProperties")).AddAttributeLists(
114+
GenericName("global::System.Action").AddTypeArgumentListArguments(PredefinedType(Token(SyntaxKind.ObjectKeyword))),
115+
Identifier("CreateAllPropertiesValidator")).AddAttributeLists(
107116
AttributeList(SingletonSeparatedList(
108117
Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments(
109118
AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never"))))),
@@ -114,8 +123,24 @@ public void Execute(GeneratorExecutionContext context)
114123
Literal("This method is not intended to be called directly by user code"))))))).AddModifiers(
115124
Token(SyntaxKind.PublicKeyword),
116125
Token(SyntaxKind.StaticKeyword)).AddParameterListParameters(
117-
Parameter(Identifier("instance")).WithType(IdentifierName(classSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))))
118-
.WithBody(Block(EnumerateValidationStatements(classSymbol, validationSymbol).ToArray())))))
126+
Parameter(Identifier("_")).WithType(IdentifierName(classSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))))
127+
.WithBody(Block(
128+
LocalFunctionStatement(
129+
PredefinedType(Token(SyntaxKind.VoidKeyword)),
130+
Identifier("ValidateAllProperties"))
131+
.AddModifiers(Token(SyntaxKind.StaticKeyword))
132+
.AddParameterListParameters(
133+
Parameter(Identifier("instance")).WithType(IdentifierName(classSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))))
134+
.WithBody(Block(EnumerateValidationStatements(classSymbol, validationSymbol).ToArray())),
135+
ReturnStatement(
136+
SimpleLambdaExpression(Parameter(Identifier("o")))
137+
.AddModifiers(Token(SyntaxKind.StaticKeyword))
138+
.WithExpressionBody(
139+
InvocationExpression(IdentifierName("ValidateAllProperties"))
140+
.AddArgumentListArguments(Argument(
141+
CastExpression(
142+
IdentifierName(classSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)),
143+
IdentifierName("o")))))))))))
119144
.NormalizeWhitespace()
120145
.ToFullString();
121146

Microsoft.Toolkit.Mvvm/ComponentModel/ObservableValidator.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -476,11 +476,9 @@ protected void ValidateAllProperties()
476476
static Action<object> GetValidationAction(Type type)
477477
{
478478
if (type.Assembly.GetType("Microsoft.Toolkit.Mvvm.ComponentModel.__Internals.__ObservableValidatorExtensions") is Type extensionsType &&
479-
extensionsType.GetMethod("ValidateAllProperties", new[] { type }) is MethodInfo methodInfo)
479+
extensionsType.GetMethod("CreateAllPropertiesValidator", new[] { type }) is MethodInfo methodInfo)
480480
{
481-
Type delegateType = typeof(Action<>).MakeGenericType(type);
482-
483-
return Unsafe.As<Action<object>>(methodInfo.CreateDelegate(delegateType));
481+
return (Action<object>)methodInfo.Invoke(null, new object?[] { null })!;
484482
}
485483

486484
return GetValidationActionFallback(type);

0 commit comments

Comments
 (0)