mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
asd
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CliFx.Tests.Dummy;
|
||||
@@ -13,7 +12,7 @@ public static class Program
|
||||
public static string FilePath { get; } =
|
||||
Path.ChangeExtension(
|
||||
Assembly.GetExecutingAssembly().Location,
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "exe" : null
|
||||
OperatingSystem.IsWindows() ? "exe" : null
|
||||
);
|
||||
|
||||
public static async Task Main()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CliFx.Extensibility;
|
||||
|
||||
namespace CliFx.Attributes;
|
||||
@@ -15,8 +16,9 @@ public class CommandParameterAttribute(int order) : Attribute
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All parameters in a command must have unique order.
|
||||
/// Parameter whose type is a non-scalar (e.g. array), must always be the last in order.
|
||||
/// Only one non-scalar parameter is allowed in a command.
|
||||
/// Parameter whose type is a sequence (e.g. Array, <see cref="List{T}" />; except <see cref="string" />),
|
||||
/// must always be the last parameter based on order.
|
||||
/// Only one sequential parameter is allowed in a command.
|
||||
/// </remarks>
|
||||
public int Order { get; } = order;
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace CliFx.Extensibility;
|
||||
using System;
|
||||
|
||||
namespace CliFx.Extensibility;
|
||||
|
||||
/// <summary>
|
||||
/// Base type for custom converters.
|
||||
@@ -8,7 +10,8 @@ public abstract class BindingConverter<T> : IBindingConverter
|
||||
/// <summary>
|
||||
/// Parses the value from a raw command-line argument.
|
||||
/// </summary>
|
||||
public abstract T? Convert(string? rawValue);
|
||||
public abstract T? Convert(string? rawValue, IFormatProvider? formatProvider);
|
||||
|
||||
object? IBindingConverter.Convert(string? rawValue) => Convert(rawValue);
|
||||
object? IBindingConverter.Convert(string? rawValue, IFormatProvider? formatProvider) =>
|
||||
Convert(rawValue, formatProvider);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace CliFx.Extensibility;
|
||||
using System;
|
||||
|
||||
namespace CliFx.Extensibility;
|
||||
|
||||
/// <summary>
|
||||
/// Converter for binding inputs to properties of type <see cref="bool" />.
|
||||
@@ -6,5 +8,6 @@
|
||||
public class BoolBindingConverter : BindingConverter<bool>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool Convert(string? rawValue) => string.IsNullOrWhiteSpace(rawValue) || bool.Parse(rawValue);
|
||||
}
|
||||
public override bool Convert(string? rawValue, IFormatProvider? formatProvider) =>
|
||||
string.IsNullOrWhiteSpace(rawValue) || bool.Parse(rawValue);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@ namespace CliFx.Extensibility;
|
||||
/// <summary>
|
||||
/// Converter for binding inputs to properties that implement <see cref="IConvertible" />.
|
||||
/// </summary>
|
||||
public class ConvertibleBindingConverter<T>(IFormatProvider formatProvider) : BindingConverter<T> where T: IConvertible
|
||||
public class ConvertibleBindingConverter<T> : BindingConverter<T>
|
||||
where T : IConvertible
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override T? Convert(string? rawValue) =>
|
||||
public override T? Convert(string? rawValue, IFormatProvider? formatProvider) =>
|
||||
(T?)System.Convert.ChangeType(rawValue, typeof(T), formatProvider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ namespace CliFx.Extensibility;
|
||||
/// <summary>
|
||||
/// Converter for binding inputs to properties of type <see cref="DateTimeOffset" />.
|
||||
/// </summary>
|
||||
public class DateTimeOffsetBindingConverter(IFormatProvider formatProvider) : BindingConverter<DateTimeOffset>
|
||||
public class DateTimeOffsetBindingConverter : BindingConverter<DateTimeOffset>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override DateTimeOffset Convert(string? rawValue) => DateTimeOffset.Parse(rawValue!, formatProvider);
|
||||
}
|
||||
public override DateTimeOffset Convert(string? rawValue, IFormatProvider? formatProvider) =>
|
||||
DateTimeOffset.Parse(rawValue!, formatProvider);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,16 @@ namespace CliFx.Extensibility;
|
||||
/// <summary>
|
||||
/// Converter for binding inputs to properties using a custom delegate.
|
||||
/// </summary>
|
||||
public class DelegateBindingConverter<T>(Func<string?, T> convert) : BindingConverter<T>
|
||||
public class DelegateBindingConverter<T>(Func<string?, IFormatProvider?, T> convert)
|
||||
: BindingConverter<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="DelegateBindingConverter{T}" />
|
||||
/// </summary>
|
||||
public DelegateBindingConverter(Func<string?, T> convert)
|
||||
: this((rawValue, _) => convert(rawValue)) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override T? Convert(string? rawValue) => convert(rawValue);
|
||||
}
|
||||
public override T Convert(string? rawValue, IFormatProvider? formatProvider) =>
|
||||
convert(rawValue, formatProvider);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ namespace CliFx.Extensibility;
|
||||
/// <summary>
|
||||
/// Converter for binding inputs to properties of type <see cref="Enum" />.
|
||||
/// </summary>
|
||||
public class EnumBindingConverter<T> : BindingConverter<T> where T : struct, Enum
|
||||
public class EnumBindingConverter<T> : BindingConverter<T>
|
||||
where T : struct, Enum
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override T Convert(string? rawValue) => (T)Enum.Parse(typeof(T), rawValue!, true);
|
||||
}
|
||||
public override T Convert(string? rawValue, IFormatProvider? formatProvider) =>
|
||||
(T)Enum.Parse(typeof(T), rawValue!, true);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace CliFx.Extensibility;
|
||||
using System;
|
||||
|
||||
namespace CliFx.Extensibility;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a custom conversion for binding command-line arguments to command inputs.
|
||||
@@ -11,5 +13,5 @@ public interface IBindingConverter
|
||||
/// <summary>
|
||||
/// Parses the value from a raw command-line argument.
|
||||
/// </summary>
|
||||
object? Convert(string? rawValue);
|
||||
}
|
||||
object? Convert(string? rawValue, IFormatProvider? formatProvider);
|
||||
}
|
||||
|
||||
@@ -13,4 +13,4 @@ public interface IBindingValidator
|
||||
/// Returns null if validation is successful, or an error in case of failure.
|
||||
/// </summary>
|
||||
BindingValidationError? Validate(object? value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace CliFx.Extensibility;
|
||||
using System;
|
||||
|
||||
namespace CliFx.Extensibility;
|
||||
|
||||
/// <summary>
|
||||
/// Converter for binding inputs to properties without any conversion.
|
||||
@@ -6,5 +8,5 @@
|
||||
public class NoopBindingConverter : IBindingConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object? Convert(string? rawValue) => rawValue;
|
||||
}
|
||||
public object? Convert(string? rawValue, IFormatProvider? formatProvider) => rawValue;
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ namespace CliFx.Extensibility;
|
||||
/// <summary>
|
||||
/// Converter for binding inputs to properties of type <see cref="Nullable{T}" />.
|
||||
/// </summary>
|
||||
public class NullableBindingConverter<T>(BindingConverter<T> innerConverter) : BindingConverter<T?> where T : struct
|
||||
public class NullableBindingConverter<T>(BindingConverter<T> innerConverter) : BindingConverter<T?>
|
||||
where T : struct
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override T? Convert(string? rawValue) =>
|
||||
public override T? Convert(string? rawValue, IFormatProvider? formatProvider) =>
|
||||
!string.IsNullOrWhiteSpace(rawValue)
|
||||
? innerConverter.Convert(rawValue)
|
||||
? innerConverter.Convert(rawValue, formatProvider)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ namespace CliFx.Extensibility;
|
||||
/// <summary>
|
||||
/// Converter for binding inputs to properties of type <see cref="TimeSpan" />.
|
||||
/// </summary>
|
||||
public class TimeSpanBindingConverter(IFormatProvider formatProvider) : BindingConverter<TimeSpan>
|
||||
public class TimeSpanBindingConverter : BindingConverter<TimeSpan>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override TimeSpan Convert(string? rawValue) =>
|
||||
public override TimeSpan Convert(string? rawValue, IFormatProvider? formatProvider) =>
|
||||
TimeSpan.Parse(rawValue!, formatProvider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,10 @@ namespace CliFx;
|
||||
// Fallback command used when the application doesn't have one configured.
|
||||
// This command is only used as a stub for help text.
|
||||
[Command]
|
||||
internal partial class FallbackDefaultCommand : IBindableCommand, ICommandWithHelpOption, ICommandWithVersionOption
|
||||
internal partial class FallbackDefaultCommand
|
||||
: IBindableCommand,
|
||||
ICommandWithHelpOption,
|
||||
ICommandWithVersionOption
|
||||
{
|
||||
[CommandHelpOption]
|
||||
public bool IsHelpRequested { get; init; }
|
||||
|
||||
@@ -17,4 +17,4 @@ public interface IBindableCommand : ICommand
|
||||
/// Binds the command input to the current instance.
|
||||
/// </summary>
|
||||
void Bind(CommandInput input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace CliFx.Schema;
|
||||
|
||||
@@ -11,8 +12,7 @@ public class CommandSchema(
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type,
|
||||
string? name,
|
||||
string? description,
|
||||
IReadOnlyList<ParameterSchema> parameters,
|
||||
IReadOnlyList<OptionSchema> options
|
||||
IReadOnlyList<InputSchema> inputs
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
@@ -36,15 +36,21 @@ public class CommandSchema(
|
||||
/// </summary>
|
||||
public string? Description { get; } = description;
|
||||
|
||||
/// <summary>
|
||||
/// Inputs (parameters and options) of the command.
|
||||
/// </summary>
|
||||
public IReadOnlyList<InputSchema> Inputs { get; } = inputs;
|
||||
|
||||
/// <summary>
|
||||
/// Parameter inputs of the command.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ParameterSchema> Parameters { get; } = parameters;
|
||||
public IReadOnlyList<ParameterSchema> Parameters { get; } =
|
||||
inputs.OfType<ParameterSchema>().ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Option inputs of the command.
|
||||
/// </summary>
|
||||
public IReadOnlyList<OptionSchema> Options { get; } = options;
|
||||
public IReadOnlyList<OptionSchema> Options { get; } = inputs.OfType<OptionSchema>().ToArray();
|
||||
|
||||
internal bool MatchesName(string? name) =>
|
||||
!string.IsNullOrWhiteSpace(Name)
|
||||
@@ -57,16 +63,26 @@ public class CommandSchema(
|
||||
|
||||
foreach (var parameterSchema in Parameters)
|
||||
{
|
||||
var value = parameterSchema.Property.GetValue(instance);
|
||||
var value = parameterSchema.Property.Get(instance);
|
||||
result[parameterSchema] = value;
|
||||
}
|
||||
|
||||
foreach (var optionSchema in Options)
|
||||
{
|
||||
var value = optionSchema.Property.GetValue(instance);
|
||||
var value = optionSchema.Property.Get(instance);
|
||||
result[optionSchema] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Generic version of the type is used to simplify initialization from the source-generated code
|
||||
// and to enforce static references to all the types used in the binding.
|
||||
// The non-generic version is used internally by the framework when operating in a dynamic context.
|
||||
/// <inheritdoc cref="CommandSchema" />
|
||||
public class CommandSchema<
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommand
|
||||
>(string? name, string? description, IReadOnlyList<InputSchema> inputs)
|
||||
: CommandSchema(typeof(TCommand), name, description, inputs)
|
||||
where TCommand : ICommand;
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Extensibility;
|
||||
using CliFx.Utils.Extensions;
|
||||
|
||||
namespace CliFx.Schema;
|
||||
|
||||
@@ -8,28 +14,99 @@ namespace CliFx.Schema;
|
||||
/// </summary>
|
||||
public abstract class InputSchema(
|
||||
PropertyBinding property,
|
||||
bool isSequence,
|
||||
IBindingConverter? converter,
|
||||
IBindingConverter converter,
|
||||
IReadOnlyList<IBindingValidator> validators
|
||||
)
|
||||
{
|
||||
internal bool IsSequence { get; } =
|
||||
property.Type != typeof(string)
|
||||
&& property.Type.TryGetEnumerableUnderlyingType() is not null;
|
||||
|
||||
/// <summary>
|
||||
/// CLR property to which this input is bound.
|
||||
/// </summary>
|
||||
public PropertyBinding Property { get; } = property;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the input can accept more than one value.
|
||||
/// </summary>
|
||||
public bool IsSequence { get; } = isSequence;
|
||||
|
||||
/// <summary>
|
||||
/// Optional binding converter for this input.
|
||||
/// </summary>
|
||||
public IBindingConverter? Converter { get; } = converter;
|
||||
public IBindingConverter Converter { get; } = converter;
|
||||
|
||||
/// <summary>
|
||||
/// Optional binding validator(s) for this input.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IBindingValidator> Validators { get; } = validators;
|
||||
|
||||
internal void Validate(object? value)
|
||||
{
|
||||
var errors = new List<BindingValidationError>();
|
||||
|
||||
foreach (var validator in validators)
|
||||
{
|
||||
var error = validator.Validate(value);
|
||||
|
||||
if (error is not null)
|
||||
errors.Add(error);
|
||||
}
|
||||
|
||||
if (errors.Any())
|
||||
{
|
||||
throw CliFxException.UserError(
|
||||
$"""
|
||||
{memberSchema.GetKind()} {memberSchema.GetFormattedIdentifier()} has been provided with an invalid value.
|
||||
Error(s):
|
||||
{errors.Select(e => "- " + e.Message).JoinToString(Environment.NewLine)}
|
||||
"""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Set(ICommand command, IReadOnlyList<string?> rawInputs)
|
||||
{
|
||||
var formatProvider = CultureInfo.InvariantCulture;
|
||||
|
||||
// Multiple values expected, single or multiple values provided
|
||||
if (IsSequence)
|
||||
{
|
||||
var value = rawInputs.Select(v => Converter.Convert(v, formatProvider)).ToArray();
|
||||
Validate(value);
|
||||
|
||||
Property.Set(command, value);
|
||||
}
|
||||
// Single value expected, single value provided
|
||||
else if (rawInputs.Count <= 1)
|
||||
{
|
||||
var value = Converter.Convert(rawInputs.SingleOrDefault(), formatProvider);
|
||||
Validate(value);
|
||||
|
||||
Property.Set(command, value);
|
||||
}
|
||||
// Single value expected, multiple values provided
|
||||
else
|
||||
{
|
||||
throw CliFxException.UserError(
|
||||
$"""
|
||||
{memberSchema.GetKind()} {memberSchema.GetFormattedIdentifier()} expects a single argument, but provided with multiple:
|
||||
{rawInputs.Select(v => '<' + v + '>').JoinToString(" ")}
|
||||
"""
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generic version of the type is used to simplify initialization from the source-generated code
|
||||
// and to enforce static references to all the types used in the binding.
|
||||
// The non-generic version is used internally by the framework when operating in a dynamic context.
|
||||
/// <inheritdoc cref="InputSchema" />
|
||||
public abstract class InputSchema<
|
||||
TCommand,
|
||||
[DynamicallyAccessedMembers(
|
||||
DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods
|
||||
)]
|
||||
TProperty
|
||||
>(
|
||||
PropertyBinding<TCommand, TProperty> property,
|
||||
BindingConverter<TProperty> converter,
|
||||
IReadOnlyList<BindingValidator<TProperty>> validators
|
||||
) : InputSchema(property, converter, validators)
|
||||
where TCommand : ICommand;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using CliFx.Extensibility;
|
||||
|
||||
@@ -10,15 +11,14 @@ namespace CliFx.Schema;
|
||||
/// </summary>
|
||||
public class OptionSchema(
|
||||
PropertyBinding property,
|
||||
bool isSequence,
|
||||
string? name,
|
||||
char? shortName,
|
||||
string? environmentVariable,
|
||||
bool isRequired,
|
||||
string? description,
|
||||
IBindingConverter? converter,
|
||||
IBindingConverter converter,
|
||||
IReadOnlyList<IBindingValidator> validators
|
||||
) : InputSchema(property, isSequence, converter, validators)
|
||||
) : InputSchema(property, converter, validators)
|
||||
{
|
||||
/// <summary>
|
||||
/// Option name.
|
||||
@@ -84,3 +84,35 @@ public class OptionSchema(
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// Generic version of the type is used to simplify initialization from the source-generated code
|
||||
// and to enforce static references to all the types used in the binding.
|
||||
// The non-generic version is used internally by the framework when operating in a dynamic context.
|
||||
/// <inheritdoc cref="OptionSchema" />
|
||||
public class OptionSchema<
|
||||
TCommand,
|
||||
[DynamicallyAccessedMembers(
|
||||
DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods
|
||||
)]
|
||||
TProperty
|
||||
>(
|
||||
PropertyBinding<TCommand, TProperty> property,
|
||||
string? name,
|
||||
char? shortName,
|
||||
string? environmentVariable,
|
||||
bool isRequired,
|
||||
string? description,
|
||||
BindingConverter<TProperty> converter,
|
||||
IReadOnlyList<BindingValidator<TProperty>> validators
|
||||
)
|
||||
: OptionSchema(
|
||||
property,
|
||||
name,
|
||||
shortName,
|
||||
environmentVariable,
|
||||
isRequired,
|
||||
description,
|
||||
converter,
|
||||
validators
|
||||
)
|
||||
where TCommand : ICommand;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using CliFx.Extensibility;
|
||||
|
||||
namespace CliFx.Schema;
|
||||
@@ -8,14 +9,13 @@ namespace CliFx.Schema;
|
||||
/// </summary>
|
||||
public class ParameterSchema(
|
||||
PropertyBinding property,
|
||||
bool isSequence,
|
||||
int order,
|
||||
string name,
|
||||
bool isRequired,
|
||||
string? description,
|
||||
IBindingConverter? converter,
|
||||
IBindingConverter converter,
|
||||
IReadOnlyList<IBindingValidator> validators
|
||||
) : InputSchema(property, isSequence, converter, validators)
|
||||
) : InputSchema(property, converter, validators)
|
||||
{
|
||||
/// <summary>
|
||||
/// Order, in which the parameter is bound from the command-line arguments.
|
||||
@@ -39,3 +39,24 @@ public class ParameterSchema(
|
||||
|
||||
internal string GetFormattedIdentifier() => IsSequence ? $"<{Name}>" : $"<{Name}...>";
|
||||
}
|
||||
|
||||
// Generic version of the type is used to simplify initialization from the source-generated code
|
||||
// and to enforce static references to all the types used in the binding.
|
||||
// The non-generic version is used internally by the framework when operating in a dynamic context.
|
||||
/// <inheritdoc cref="ParameterSchema" />
|
||||
public class ParameterSchema<
|
||||
TCommand,
|
||||
[DynamicallyAccessedMembers(
|
||||
DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods
|
||||
)]
|
||||
TProperty
|
||||
>(
|
||||
PropertyBinding<TCommand, TProperty> property,
|
||||
int order,
|
||||
string name,
|
||||
bool isRequired,
|
||||
string? description,
|
||||
BindingConverter<TProperty> converter,
|
||||
IReadOnlyList<BindingValidator<TProperty>> validators
|
||||
) : ParameterSchema(property, order, name, isRequired, description, converter, validators)
|
||||
where TCommand : ICommand;
|
||||
|
||||
@@ -13,8 +13,8 @@ public class PropertyBinding(
|
||||
DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods
|
||||
)]
|
||||
Type type,
|
||||
Func<object, object?> getValue,
|
||||
Action<object, object?> setValue
|
||||
Func<object, object?> get,
|
||||
Action<object, object?> set
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
@@ -28,12 +28,12 @@ public class PropertyBinding(
|
||||
/// <summary>
|
||||
/// Gets the current value of the property on the specified instance.
|
||||
/// </summary>
|
||||
public object? GetValue(object instance) => getValue(instance);
|
||||
public object? Get(object instance) => get(instance);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current value of the property on the specified instance.
|
||||
/// </summary>
|
||||
public void SetValue(object instance, object? value) => setValue(instance, value);
|
||||
public void Set(object instance, object? value) => set(instance, value);
|
||||
|
||||
internal IReadOnlyList<object?>? TryGetValidValues()
|
||||
{
|
||||
@@ -54,3 +54,20 @@ public class PropertyBinding(
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Generic version of the type is used to simplify initialization from the source-generated code
|
||||
// and to enforce static references to all the types used in the binding.
|
||||
// The non-generic version is used internally by the framework when operating in a dynamic context.
|
||||
/// <inheritdoc cref="PropertyBinding" />
|
||||
public class PropertyBinding<
|
||||
TObject,
|
||||
[DynamicallyAccessedMembers(
|
||||
DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods
|
||||
)]
|
||||
TProperty
|
||||
>(Func<TObject, TProperty?> get, Action<TObject, TProperty?> set)
|
||||
: PropertyBinding(
|
||||
typeof(TProperty),
|
||||
o => get((TObject)o),
|
||||
(o, v) => set((TObject)o, (TProperty?)v)
|
||||
);
|
||||
|
||||
@@ -31,7 +31,7 @@ internal static class TypeExtensions
|
||||
return type.GetGenericArguments().FirstOrDefault();
|
||||
|
||||
return type.GetInterfaces()
|
||||
.Select(TryGetEnumerableUnderlyingType)
|
||||
.Select(t => TryGetEnumerableUnderlyingType(t))
|
||||
.Where(t => t is not null)
|
||||
// Every IEnumerable<T> implements IEnumerable (which is essentially IEnumerable<object>),
|
||||
// so we try to get a more specific underlying type. Still, if the type only implements
|
||||
|
||||
Reference in New Issue
Block a user