This commit is contained in:
Tyrrrz
2024-09-21 21:03:05 +03:00
parent 71fe231f28
commit 40beb283d5
12 changed files with 270 additions and 280 deletions

View File

@@ -1,5 +1,4 @@
using System; using System.Linq;
using System.Linq;
using CliFx.SourceGeneration.SemanticModel; using CliFx.SourceGeneration.SemanticModel;
using CliFx.SourceGeneration.Utils.Extensions; using CliFx.SourceGeneration.Utils.Extensions;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
@@ -21,13 +20,13 @@ public class CommandSchemaGenerator : IIncrementalGenerator
(n, _) => n is TypeDeclarationSyntax, (n, _) => n is TypeDeclarationSyntax,
(x, _) => (x, _) =>
{ {
// Predicate ensures that these casts are safe // Predicate above ensures that these casts are safe
var typeDeclarationSyntax = (TypeDeclarationSyntax)x.TargetNode; var commandTypeSyntax = (TypeDeclarationSyntax)x.TargetNode;
var namedTypeSymbol = (INamedTypeSymbol)x.TargetSymbol; var commandTypeSymbol = (INamedTypeSymbol)x.TargetSymbol;
// Check if the target type and all its containing types are partial // Check if the target type and all its containing types are partial
if ( if (
typeDeclarationSyntax commandTypeSyntax
.AncestorsAndSelf() .AncestorsAndSelf()
.Any(a => .Any(a =>
a is TypeDeclarationSyntax t a is TypeDeclarationSyntax t
@@ -39,14 +38,14 @@ public class CommandSchemaGenerator : IIncrementalGenerator
null, null,
Diagnostic.Create( Diagnostic.Create(
DiagnosticDescriptors.CommandMustBePartial, DiagnosticDescriptors.CommandMustBePartial,
typeDeclarationSyntax.Identifier.GetLocation() commandTypeSyntax.Identifier.GetLocation()
) )
); );
} }
// Check if the target type implements ICommand // Check if the target type implements ICommand
var hasCommandInterface = namedTypeSymbol.AllInterfaces.Any(i => var hasCommandInterface = commandTypeSymbol.AllInterfaces.Any(i =>
i.DisplayNameMatches("CliFx.ICommand") i.DisplayNameMatches(KnownSymbolNames.CliFxCommandInterface)
); );
if (!hasCommandInterface) if (!hasCommandInterface)
@@ -55,220 +54,22 @@ public class CommandSchemaGenerator : IIncrementalGenerator
null, null,
Diagnostic.Create( Diagnostic.Create(
DiagnosticDescriptors.CommandMustImplementInterface, DiagnosticDescriptors.CommandMustImplementInterface,
namedTypeSymbol.Locations.First() commandTypeSymbol.Locations.First()
) )
); );
} }
// Get the command name // Resolve the command
var commandAttribute = x.Attributes.First(a => var commandAttribute = x.Attributes.First(a =>
a.AttributeClass?.DisplayNameMatches(KnownSymbolNames.CliFxCommandAttribute) a.AttributeClass?.DisplayNameMatches(KnownSymbolNames.CliFxCommandAttribute)
== true == true
); );
var commandName = var command = CommandSymbol.FromSymbol(commandTypeSymbol, commandAttribute);
commandAttribute.ConstructorArguments.FirstOrDefault().Value as string;
var commandDescription = // TODO: validate command
commandAttribute
.NamedArguments.FirstOrDefault(a =>
string.Equals(a.Key, "Description", StringComparison.Ordinal)
)
.Value.Value as string;
// Get all parameter inputs return (command, null);
var parameterSymbols = namedTypeSymbol
.GetMembers()
.OfType<IPropertySymbol>()
.Select(p =>
{
var parameterAttribute = p.GetAttributes()
.FirstOrDefault(a =>
a.AttributeClass?.DisplayNameMatches(
KnownSymbolNames.CliFxCommandParameterAttribute
) == true
);
if (parameterAttribute is null)
return null;
var isSequence = false; // TODO
var order = parameterAttribute.ConstructorArguments.First().Value as int?;
var isRequired =
parameterAttribute
.NamedArguments.FirstOrDefault(a =>
string.Equals(a.Key, "IsRequired", StringComparison.Ordinal)
)
.Value.Value as bool?
?? true;
var name =
parameterAttribute
.NamedArguments.FirstOrDefault(a =>
string.Equals(a.Key, "Name", StringComparison.Ordinal)
)
.Value.Value as string;
var description =
parameterAttribute
.NamedArguments.FirstOrDefault(a =>
string.Equals(a.Key, "Description", StringComparison.Ordinal)
)
.Value.Value as string;
var converter =
parameterAttribute
.NamedArguments.FirstOrDefault(a =>
string.Equals(a.Key, "Converter", StringComparison.Ordinal)
)
.Value.Value as ITypeSymbol;
var validators = parameterAttribute
.NamedArguments.FirstOrDefault(a =>
string.Equals(a.Key, "Validators", StringComparison.Ordinal)
)
.Value.Values.CastArray<ITypeSymbol>();
return new CommandParameterSymbol(
new PropertyDescriptor(
new TypeDescriptor(
p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
),
p.Name
),
isSequence,
order,
isRequired,
name,
description,
converter
?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
?.Pipe(n => new TypeDescriptor(n)),
validators
.Select(v =>
v.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
)
.Select(n => new TypeDescriptor(n))
.ToArray()
);
})
.WhereNotNull()
.ToArray();
// Get all option inputs
var optionSymbols = namedTypeSymbol
.GetMembers()
.OfType<IPropertySymbol>()
.Select(p =>
{
var optionAttribute = p.GetAttributes()
.FirstOrDefault(a =>
a.AttributeClass?.DisplayNameMatches(
KnownSymbolNames.CliFxCommandOptionAttribute
) == true
);
if (optionAttribute is null)
return null;
var isSequence = false; // TODO
var name =
optionAttribute
.ConstructorArguments.Where(a =>
a.Type?.SpecialType == SpecialType.System_String
)
.Select(a => a.Value)
.FirstOrDefault() as string;
var shortName =
optionAttribute
.ConstructorArguments.Where(a =>
a.Type?.SpecialType == SpecialType.System_Char
)
.Select(a => a.Value)
.FirstOrDefault() as char?;
var environmentVariable =
optionAttribute
.NamedArguments.FirstOrDefault(a =>
string.Equals(
a.Key,
"EnvironmentVariable",
StringComparison.Ordinal
)
)
.Value.Value as string;
var isRequired =
optionAttribute
.NamedArguments.Where(a => a.Key == "IsRequired")
.Select(a => a.Value.Value)
.FirstOrDefault() as bool?
?? p.IsRequired;
var description =
optionAttribute
.NamedArguments.FirstOrDefault(a =>
string.Equals(a.Key, "Description", StringComparison.Ordinal)
)
.Value.Value as string;
var converter =
optionAttribute
.NamedArguments.FirstOrDefault(a =>
string.Equals(a.Key, "Converter", StringComparison.Ordinal)
)
.Value.Value as ITypeSymbol;
var validators = optionAttribute
.NamedArguments.FirstOrDefault(a =>
string.Equals(a.Key, "Validators", StringComparison.Ordinal)
)
.Value.Values.CastArray<ITypeSymbol>();
return new CommandOptionSymbol(
new PropertyDescriptor(
new TypeDescriptor(
p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
),
p.Name
),
isSequence,
name,
shortName,
environmentVariable,
isRequired,
description,
converter
?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
?.Pipe(n => new TypeDescriptor(n)),
validators
.Select(v =>
v.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
)
.Select(n => new TypeDescriptor(n))
.ToArray()
);
})
.WhereNotNull()
.ToArray();
return (
new CommandSymbol(
new TypeDescriptor(
namedTypeSymbol.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat
)
),
commandName,
commandDescription,
parameterSymbols.Concat<CommandInputSymbol>(optionSymbols).ToArray()
),
null
);
} }
); );
@@ -276,58 +77,54 @@ public class CommandSchemaGenerator : IIncrementalGenerator
var diagnostics = values.Select((v, _) => v.Item2).WhereNotNull(); var diagnostics = values.Select((v, _) => v.Item2).WhereNotNull();
context.RegisterSourceOutput(diagnostics, (x, d) => x.ReportDiagnostic(d)); context.RegisterSourceOutput(diagnostics, (x, d) => x.ReportDiagnostic(d));
// Generate source // Generate command schemas
var symbols = values.Select((v, _) => v.Item1).WhereNotNull(); var symbols = values.Select((v, _) => v.Item1).WhereNotNull();
context.RegisterSourceOutput( context.RegisterSourceOutput(
symbols, symbols,
(x, c) => (x, c) =>
{ x.AddSource(
var source = $"{c.Type.FullyQualifiedName}.CommandSchema.Generated.cs",
// lang=csharp // lang=csharp
$$""" $$"""
using System.Linq; namespace {{c.Type.Namespace}};
using CliFx.Schema;
using CliFx.Extensibility;
namespace {{ c.Type.Namespace }}; partial class {{c.Type.Name}}
partial class {{ c.Type.Name }}
{ {
public static CommandSchema<{{ c.Type.FullyQualifiedName }}> Schema { get; } = new( public static CliFx.Schema.CommandSchema<{{c.Type.FullyQualifiedName}}> Schema { get; } = {{c.GenerateSchemaInitializationCode()}};
{{ c.Name }}, }
{{ c.Description }}, """
[
{{ c.Inputs.Select(i => i switch {
CommandParameterSymbol parameter =>
// lang=csharp
$$"""
new CommandParameterSchema<{{ c.Type.FullyQualifiedName }}, {{ i.Property.Type.FullyQualifiedName }}>(
new PropertyBinding<{{ c.Type.FullyQualifiedName }}, {{ i.Property.Type.FullyQualifiedName }}>(
obj => obj.{{ i.Property.Name }},
(obj, value) => obj.{{ i.Property.Name }} = value
),
p.Order,
p.IsRequired,
p.Name,
p.Description,
new {{ i.ConverterType.FullyQualifiedName }}(),
[
{{ i.ValidatorTypes.Select(v =>
// lang=csharp
$"new {v.FullyQualifiedName}()").JoinToString(",\n")
}}
]
) )
""", );
CommandOptionSymbol option => ""
}).JoinToString(",\n")
}}
]
}
""";
x.AddSource($"{c.TypeName}.CommandSchema.Generated.cs", source); // Generate extension methods
var symbolsCollected = symbols.Collect();
context.RegisterSourceOutput(
symbolsCollected,
(x, cs) =>
x.AddSource(
"CommandSchemaExtensions.Generated.cs",
// lang=csharp
$$"""
namespace CliFx;
static partial class GeneratedExtensions
{
public static CliFx.CliApplicationBuilder AddCommandsFromThisAssembly(this CliFx.CliApplicationBuilder builder)
{
{{
cs.Select(c => c.Type.FullyQualifiedName)
.Select(t =>
// lang=csharp
$"builder.AddCommand({t}.Schema);"
)
.JoinToString("\n")
}}
return builder;
} }
}
"""
)
); );
} }
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.CodeAnalysis;
namespace CliFx.SourceGeneration.SemanticModel; namespace CliFx.SourceGeneration.SemanticModel;
@@ -54,3 +55,12 @@ internal partial class CommandInputSymbol : IEquatable<CommandInputSymbol>
public override int GetHashCode() => public override int GetHashCode() =>
HashCode.Combine(Property, IsSequence, Description, ConverterType, ValidatorTypes); HashCode.Combine(Property, IsSequence, Description, ConverterType, ValidatorTypes);
} }
internal partial class CommandInputSymbol
{
public static bool IsSequenceType(ITypeSymbol type) =>
type.AllInterfaces.Any(i =>
i.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T
)
&& type.SpecialType != SpecialType.System_String;
}

View File

@@ -1,5 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using CliFx.SourceGeneration.Utils.Extensions;
using Microsoft.CodeAnalysis;
namespace CliFx.SourceGeneration.SemanticModel; namespace CliFx.SourceGeneration.SemanticModel;
@@ -55,3 +58,33 @@ internal partial class CommandOptionSymbol : IEquatable<CommandOptionSymbol>
public override int GetHashCode() => public override int GetHashCode() =>
HashCode.Combine(base.GetHashCode(), Name, ShortName, EnvironmentVariable, IsRequired); HashCode.Combine(base.GetHashCode(), Name, ShortName, EnvironmentVariable, IsRequired);
} }
internal partial class CommandOptionSymbol
{
public static CommandOptionSymbol FromSymbol(
IPropertySymbol property,
AttributeData attribute
) =>
new(
PropertyDescriptor.FromSymbol(property),
IsSequenceType(property.Type),
attribute
.ConstructorArguments.FirstOrDefault(a =>
a.Type?.SpecialType == SpecialType.System_String
)
.Value as string,
attribute
.ConstructorArguments.FirstOrDefault(a =>
a.Type?.SpecialType == SpecialType.System_Char
)
.Value as char?,
attribute.GetNamedArgumentValue("EnvironmentVariable", default(string)),
attribute.GetNamedArgumentValue("IsRequired", property.IsRequired),
attribute.GetNamedArgumentValue("Description", default(string)),
TypeDescriptor.FromSymbol(attribute.GetNamedArgumentValue<ITypeSymbol?>("Converter")),
attribute
.GetNamedArgumentValues<ITypeSymbol>("Validators")
.Select(TypeDescriptor.FromSymbol)
.ToArray()
);
}

View File

@@ -1,5 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using CliFx.SourceGeneration.Utils.Extensions;
using Microsoft.CodeAnalysis;
namespace CliFx.SourceGeneration.SemanticModel; namespace CliFx.SourceGeneration.SemanticModel;
@@ -51,3 +54,24 @@ internal partial class CommandParameterSymbol : IEquatable<CommandParameterSymbo
public override int GetHashCode() => public override int GetHashCode() =>
HashCode.Combine(base.GetHashCode(), Order, Name, IsRequired); HashCode.Combine(base.GetHashCode(), Order, Name, IsRequired);
} }
internal partial class CommandParameterSymbol
{
public static CommandParameterSymbol FromSymbol(
IPropertySymbol property,
AttributeData attribute
) =>
new(
PropertyDescriptor.FromSymbol(property),
IsSequenceType(property.Type),
(int)attribute.ConstructorArguments.First().Value!,
attribute.GetNamedArgumentValue("Name", default(string)),
attribute.GetNamedArgumentValue("IsRequired", true),
attribute.GetNamedArgumentValue("Description", default(string)),
TypeDescriptor.FromSymbol(attribute.GetNamedArgumentValue<ITypeSymbol>("Converter")),
attribute
.GetNamedArgumentValues<ITypeSymbol>("Validators")
.Select(TypeDescriptor.FromSymbol)
.ToArray()
);
}

View File

@@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using CliFx.SourceGeneration.Utils.Extensions;
using Microsoft.CodeAnalysis;
namespace CliFx.SourceGeneration.SemanticModel; namespace CliFx.SourceGeneration.SemanticModel;
@@ -24,6 +26,73 @@ internal partial class CommandSymbol(
public IReadOnlyList<CommandOptionSymbol> Options => public IReadOnlyList<CommandOptionSymbol> Options =>
Inputs.OfType<CommandOptionSymbol>().ToArray(); Inputs.OfType<CommandOptionSymbol>().ToArray();
private string GeneratePropertyBindingInitializationCode(PropertyDescriptor property) =>
// lang=csharp
$$"""
new CliFx.Schema.PropertyBinding<{{Type.FullyQualifiedName}}, {{property
.Type
.FullyQualifiedName}}>(
(obj) => obj.{{property.Name}},
(obj, value) => obj.{{property.Name}} = value
)
""";
private string GenerateSchemaInitializationCode(CommandInputSymbol input) =>
input switch
{
CommandParameterSymbol parameter
=>
// lang=csharp
$$"""
new CliFx.Schema.CommandParameterSchema<{{Type.FullyQualifiedName}}, {{parameter
.Property
.Type
.FullyQualifiedName}}>(
{{GeneratePropertyBindingInitializationCode(parameter.Property)}},
{{parameter.IsSequence}},
{{parameter.Order}},
"{{parameter.Name}}",
{{parameter.IsRequired}},
"{{parameter.Description}}",
// TODO,
// TODO
);
""",
CommandOptionSymbol option
=>
// lang=csharp
$$"""
new CliFx.Schema.CommandOptionSchema<{{Type.FullyQualifiedName}}, {{option
.Property
.Type
.FullyQualifiedName}}>(
{{GeneratePropertyBindingInitializationCode(option.Property)}},
{{option.IsSequence}},
"{{option.Name}}",
'{{option.ShortName}}',
"{{option.EnvironmentVariable}}",
{{option.IsRequired}},
"{{option.Description}}",
// TODO,
// TODO
);
""",
_ => throw new ArgumentOutOfRangeException(nameof(input), input, null)
};
public string GenerateSchemaInitializationCode() =>
// lang=csharp
$$"""
new CliFx.Schema.CommandSchema<{{Type.FullyQualifiedName}}>(
"{{Name}}",
"{{Description}}",
new CliFx.Schema.CommandInputSchema[]
{
{{Inputs.Select(GenerateSchemaInitializationCode).JoinToString(",\n")}}
}
)
""";
} }
internal partial class CommandSymbol : IEquatable<CommandSymbol> internal partial class CommandSymbol : IEquatable<CommandSymbol>
@@ -55,3 +124,44 @@ internal partial class CommandSymbol : IEquatable<CommandSymbol>
public override int GetHashCode() => HashCode.Combine(Type, Name, Description, Inputs); public override int GetHashCode() => HashCode.Combine(Type, Name, Description, Inputs);
} }
internal partial class CommandSymbol
{
public static CommandSymbol FromSymbol(INamedTypeSymbol symbol, AttributeData attribute)
{
var inputs = new List<CommandInputSymbol>();
foreach (var property in symbol.GetMembers().OfType<IPropertySymbol>())
{
var parameterAttribute = property
.GetAttributes()
.FirstOrDefault(a =>
a.AttributeClass?.Name == KnownSymbolNames.CliFxCommandParameterAttribute
);
if (parameterAttribute is not null)
{
inputs.Add(CommandParameterSymbol.FromSymbol(property, parameterAttribute));
continue;
}
var optionAttribute = property
.GetAttributes()
.FirstOrDefault(a =>
a.AttributeClass?.Name == KnownSymbolNames.CliFxCommandOptionAttribute
);
if (optionAttribute is not null)
{
inputs.Add(CommandOptionSymbol.FromSymbol(property, optionAttribute));
continue;
}
}
return new CommandSymbol(
TypeDescriptor.FromSymbol(symbol),
attribute.ConstructorArguments.FirstOrDefault().Value as string,
attribute.GetNamedArgumentValue("Description", default(string)),
inputs
);
}
}

View File

@@ -1,4 +1,5 @@
using System; using System;
using Microsoft.CodeAnalysis;
namespace CliFx.SourceGeneration.SemanticModel; namespace CliFx.SourceGeneration.SemanticModel;
@@ -35,3 +36,9 @@ internal partial class PropertyDescriptor : IEquatable<PropertyDescriptor>
public override int GetHashCode() => HashCode.Combine(Type, Name); public override int GetHashCode() => HashCode.Combine(Type, Name);
} }
internal partial class PropertyDescriptor
{
public static PropertyDescriptor FromSymbol(IPropertySymbol symbol) =>
new(TypeDescriptor.FromSymbol(symbol.Type), symbol.Name);
}

View File

@@ -1,5 +1,6 @@
using System; using System;
using CliFx.SourceGeneration.Utils.Extensions; using CliFx.SourceGeneration.Utils.Extensions;
using Microsoft.CodeAnalysis;
namespace CliFx.SourceGeneration.SemanticModel; namespace CliFx.SourceGeneration.SemanticModel;
@@ -38,3 +39,9 @@ internal partial class TypeDescriptor : IEquatable<TypeDescriptor>
public override int GetHashCode() => FullyQualifiedName.GetHashCode(); public override int GetHashCode() => FullyQualifiedName.GetHashCode();
} }
internal partial class TypeDescriptor
{
public static TypeDescriptor FromSymbol(ITypeSymbol symbol) =>
new(symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
}

View File

@@ -1,4 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
namespace CliFx.SourceGeneration.Utils.Extensions; namespace CliFx.SourceGeneration.Utils.Extensions;
@@ -13,6 +16,22 @@ internal static class RoslynExtensions
StringComparison.Ordinal StringComparison.Ordinal
); );
public static T GetNamedArgumentValue<T>(
this AttributeData attribute,
string name,
T defaultValue = default
) =>
attribute.NamedArguments.FirstOrDefault(i => i.Key == name).Value.Value is T valueAsT
? valueAsT
: defaultValue;
public static IReadOnlyList<T> GetNamedArgumentValues<T>(
this AttributeData attribute,
string name
)
where T : class =>
attribute.NamedArguments.FirstOrDefault(i => i.Key == name).Value.Values.CastArray<T>();
public static IncrementalValuesProvider<T> WhereNotNull<T>( public static IncrementalValuesProvider<T> WhereNotNull<T>(
this IncrementalValuesProvider<T?> values this IncrementalValuesProvider<T?> values
) )

View File

@@ -125,10 +125,7 @@ public abstract class CommandInputSchema(
/// </remarks> /// </remarks>
public abstract class CommandInputSchema< public abstract class CommandInputSchema<
TCommand, TCommand,
[DynamicallyAccessedMembers( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TProperty
DynamicallyAccessedMemberTypes.PublicMethods
)]
TProperty
>( >(
PropertyBinding<TCommand, TProperty> property, PropertyBinding<TCommand, TProperty> property,
bool isSequence, bool isSequence,

View File

@@ -63,10 +63,7 @@ public class CommandOptionSchema(
/// </remarks> /// </remarks>
public class CommandOptionSchema< public class CommandOptionSchema<
TCommand, TCommand,
[DynamicallyAccessedMembers( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TProperty
DynamicallyAccessedMemberTypes.PublicMethods
)]
TProperty
>( >(
PropertyBinding<TCommand, TProperty> property, PropertyBinding<TCommand, TProperty> property,
bool isSequence, bool isSequence,

View File

@@ -42,10 +42,7 @@ public class CommandParameterSchema(
/// </remarks> /// </remarks>
public class CommandParameterSchema< public class CommandParameterSchema<
TCommand, TCommand,
[DynamicallyAccessedMembers( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TProperty
DynamicallyAccessedMemberTypes.PublicMethods
)]
TProperty
>( >(
PropertyBinding<TCommand, TProperty> property, PropertyBinding<TCommand, TProperty> property,
bool isSequence, bool isSequence,

View File

@@ -9,10 +9,7 @@ namespace CliFx.Schema;
/// Provides read and write access to a CLR property. /// Provides read and write access to a CLR property.
/// </summary> /// </summary>
public class PropertyBinding( public class PropertyBinding(
[DynamicallyAccessedMembers( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type,
DynamicallyAccessedMemberTypes.PublicMethods
)]
Type type,
Func<object, object?> getValue, Func<object, object?> getValue,
Action<object, object?> setValue Action<object, object?> setValue
) )
@@ -20,9 +17,7 @@ public class PropertyBinding(
/// <summary> /// <summary>
/// Underlying CLR type of the property. /// Underlying CLR type of the property.
/// </summary> /// </summary>
[DynamicallyAccessedMembers( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
DynamicallyAccessedMemberTypes.PublicMethods
)]
public Type Type { get; } = type; public Type Type { get; } = type;
/// <summary> /// <summary>
@@ -63,10 +58,7 @@ public class PropertyBinding(
/// </remarks> /// </remarks>
public class PropertyBinding< public class PropertyBinding<
TObject, TObject,
[DynamicallyAccessedMembers( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TProperty
DynamicallyAccessedMemberTypes.PublicMethods
)]
TProperty
>(Func<TObject, TProperty?> getValue, Action<TObject, TProperty?> setValue) >(Func<TObject, TProperty?> getValue, Action<TObject, TProperty?> setValue)
: PropertyBinding( : PropertyBinding(
typeof(TProperty), typeof(TProperty),