Enable trimming support in the MVVM Toolkit

Also added linker annotations where needed
This commit is contained in:
Sergio Pedri 2021-12-17 16:43:06 +01:00
Родитель 14c6dc34fb
Коммит a7e0f2c370
6 изменённых файлов: 94 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if !NET6_0_OR_GREATER
namespace System.Diagnostics.CodeAnalysis;
/// <summary>
/// Indicates that the specified method requires dynamic access to code that is not referenced statically.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)]
[Conditional("DEBUG")]
internal sealed class RequiresUnreferencedCodeAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="RequiresUnreferencedCodeAttribute"/> class.
/// </summary>
/// <param name="message">A message that contains information about the usage of unreferenced code.</param>
public RequiresUnreferencedCodeAttribute(string message)
{
Message = message;
}
/// <summary>
/// Gets a message that contains information about the usage of unreferenced code.
/// </summary>
public string Message { get; }
/// <summary>
/// Gets or sets an optional URL that contains more information about the method,
/// why it requires unreferenced code, and what options a consumer has to deal with it.
/// </summary>
public string? Url { get; set; }
}
#endif

Просмотреть файл

@ -33,6 +33,12 @@
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>
<!-- Enable trimming support on .NET 6 -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net6.0'">
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
<!-- Source generator project reference for packing -->
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">

Просмотреть файл

@ -57,6 +57,13 @@ public abstract class ObservableRecipient : ObservableObject
public bool IsActive
{
get => this.isActive;
[RequiresUnreferencedCode(
"When this property is set to true, the OnActivated() method will be invoked, which will register all necessary message handlers for this recipient. " +
"This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
"If this type is removed by the linker, or if the target recipient was created dynamically and was missed by the source generator, a slower fallback " +
"path using a compiled LINQ expression will be used. This will have more overhead in the first invocation of this method for any given recipient type. " +
"Alternatively, OnActivated() can be manually overwritten, and registration can be done individually for each required message for this recipient.")]
set
{
if (SetProperty(ref this.isActive, value, true))
@ -84,6 +91,11 @@ public abstract class ObservableRecipient : ObservableObject
/// If you need more fine tuned control, want to register messages individually or just prefer
/// the lambda-style syntax for message registration, override this method and register manually.
/// </remarks>
[RequiresUnreferencedCode(
"This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
"If this type is removed by the linker, or if the target recipient was created dynamically and was missed by the source generator, a slower fallback " +
"path using a compiled LINQ expression will be used. This will have more overhead in the first invocation of this method for any given recipient type. " +
"Alternatively, OnActivated() can be manually overwritten, and registration can be done individually for each required message for this recipient.")]
protected virtual void OnActivated()
{
Messenger.RegisterAll(this);

Просмотреть файл

@ -69,6 +69,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// be used to validate all properties, which will reference the current instance
/// and no additional services or validation properties and settings.
/// </summary>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected ObservableValidator()
{
this.validationContext = new ValidationContext(this);
@ -80,6 +81,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// be used to validate all properties, which will reference the current instance.
/// </summary>
/// <param name="items">A set of key/value pairs to make available to consumers.</param>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected ObservableValidator(IDictionary<object, object?>? items)
{
this.validationContext = new ValidationContext(this, items);
@ -92,6 +94,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// </summary>
/// <param name="serviceProvider">An <see cref="IServiceProvider"/> instance to make available during validation.</param>
/// <param name="items">A set of key/value pairs to make available to consumers.</param>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected ObservableValidator(IServiceProvider? serviceProvider, IDictionary<object, object?>? items)
{
this.validationContext = new ValidationContext(this, serviceProvider, items);
@ -141,6 +144,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// are not raised if the current and new value for the target property are the same.
/// </remarks>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="propertyName"/> is <see langword="null"/>.</exception>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected bool SetProperty<T>([NotNullIfNotNull("newValue")] ref T field, T newValue, bool validate, [CallerMemberName] string propertyName = null!)
{
ArgumentNullException.ThrowIfNull(propertyName);
@ -169,6 +173,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="comparer"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected bool SetProperty<T>([NotNullIfNotNull("newValue")] ref T field, T newValue, IEqualityComparer<T> comparer, bool validate, [CallerMemberName] string propertyName = null!)
{
ArgumentNullException.ThrowIfNull(comparer);
@ -205,6 +210,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// are not raised if the current and new value for the target property are the same.
/// </remarks>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, bool validate, [CallerMemberName] string propertyName = null!)
{
ArgumentNullException.ThrowIfNull(callback);
@ -235,6 +241,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="comparer"/>, <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> comparer, Action<T> callback, bool validate, [CallerMemberName] string propertyName = null!)
{
ArgumentNullException.ThrowIfNull(comparer);
@ -269,6 +276,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="model"/>, <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string propertyName = null!)
where TModel : class
{
@ -306,6 +314,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="comparer"/>, <paramref name="model"/>, <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string propertyName = null!)
where TModel : class
{
@ -335,6 +344,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="propertyName"/> is <see langword="null"/>.</exception>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected bool TrySetProperty<T>(ref T field, T newValue, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string propertyName = null!)
{
ArgumentNullException.ThrowIfNull(propertyName);
@ -355,6 +365,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="comparer"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected bool TrySetProperty<T>(ref T field, T newValue, IEqualityComparer<T> comparer, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string propertyName = null!)
{
ArgumentNullException.ThrowIfNull(comparer);
@ -376,6 +387,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected bool TrySetProperty<T>(T oldValue, T newValue, Action<T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string propertyName = null!)
{
ArgumentNullException.ThrowIfNull(callback);
@ -398,6 +410,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="comparer"/>, <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected bool TrySetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> comparer, Action<T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string propertyName = null!)
{
ArgumentNullException.ThrowIfNull(comparer);
@ -422,6 +435,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="model"/>, <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected bool TrySetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string propertyName = null!)
where TModel : class
{
@ -448,6 +462,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="comparer"/>, <paramref name="model"/>, <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected bool TrySetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string propertyName = null!)
where TModel : class
{
@ -523,8 +538,14 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// members in the current instance will be ignored. None of the processed properties
/// will be modified - they will only be used to retrieve their values and validate them.
/// </remarks>
[RequiresUnreferencedCode(
"This method requires the generated CommunityToolkit.Mvvm.ComponentModel.__Internals.__ObservableValidatorExtensions type not to be removed to use the fast path. " +
"If this type is removed by the linker, or if the target recipient was created dynamically and was missed by the source generator, a slower fallback " +
"path using a compiled LINQ expression will be used. This will have more overhead in the first invocation of this method for any given recipient type. " +
"Additionally, due to the usage of validation APIs, the type of the current instance cannot be statically discovered.")]
protected void ValidateAllProperties()
{
#pragma warning disable IL2026
// Fast path that tries to create a delegate from a generated type-specific method. This
// is used to make this method more AOT-friendly and faster, as there is no dynamic code.
static Action<object> GetValidationAction(Type type)
@ -537,6 +558,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
return GetValidationActionFallback(type);
}
#pragma warning restore IL2026
// Fallback method to create the delegate with a compiled LINQ expression
static Action<object> GetValidationActionFallback(Type type)
@ -603,6 +625,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// <param name="value">The value to test for the specified property.</param>
/// <param name="propertyName">The name of the property to validate.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="propertyName"/> is <see langword="null"/>.</exception>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
protected internal void ValidateProperty(object? value, [CallerMemberName] string propertyName = null!)
{
ArgumentNullException.ThrowIfNull(propertyName);
@ -679,6 +702,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
/// <param name="value">The value to test for the specified property.</param>
/// <param name="propertyName">The name of the property to validate.</param>
/// <param name="errors">The resulting validation errors, if any.</param>
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
private bool TryValidateProperty(object? value, string propertyName, out IReadOnlyCollection<ValidationResult> errors)
{
// Add the cached errors list for later use.

Просмотреть файл

@ -25,6 +25,8 @@ public static class __ObservableValidatorHelper
[Obsolete("This method is not intended to be called directly by user code")]
public static void ValidateProperty(ObservableValidator instance, object? value, string propertyName)
{
#pragma warning disable IL2026
instance.ValidateProperty(value, propertyName);
#pragma warning restore IL2026
}
}

Просмотреть файл

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
@ -85,11 +86,16 @@ public static class IMessengerExtensions
/// <param name="recipient">The recipient that will receive the messages.</param>
/// <remarks>See notes for <see cref="RegisterAll{TToken}(IMessenger,object,TToken)"/> for more info.</remarks>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="messenger"/> or <paramref name="recipient"/> are <see langword="null"/>.</exception>
[RequiresUnreferencedCode(
"This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
"If this type is removed by the linker, or if the target recipient was created dynamically and was missed by the source generator, a slower fallback " +
"path using a compiled LINQ expression will be used. This will have more overhead in the first invocation of this method for any given recipient type.")]
public static void RegisterAll(this IMessenger messenger, object recipient)
{
ArgumentNullException.ThrowIfNull(messenger);
ArgumentNullException.ThrowIfNull(recipient);
#pragma warning disable IL2026
// We use this method as a callback for the conditional weak table, which will handle
// thread-safety for us. This first callback will try to find a generated method for the
// target recipient type, and just invoke it to get the delegate to cache and use later.
@ -103,6 +109,7 @@ public static class IMessengerExtensions
return null;
}
#pragma warning restore IL2026
// Try to get the cached delegate, if the generatos has run correctly
Action<IMessenger, object>? registrationAction = DiscoveredRecipients.RegistrationMethods.GetValue(
@ -134,6 +141,10 @@ public static class IMessengerExtensions
/// registered directly through any of the other generic extensions for the <see cref="IMessenger"/> interface.
/// </remarks>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="messenger"/>, <paramref name="recipient"/> or <paramref name="token"/> are <see langword="null"/>.</exception>
[RequiresUnreferencedCode(
"This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
"If this type is removed by the linker, or if the target recipient was created dynamically and was missed by the source generator, a slower fallback " +
"path using a compiled LINQ expression will be used. This will have more overhead in the first invocation of this method for any given recipient type.")]
public static void RegisterAll<TToken>(this IMessenger messenger, object recipient, TToken token)
where TToken : IEquatable<TToken>
{
@ -141,6 +152,7 @@ public static class IMessengerExtensions
ArgumentNullException.ThrowIfNull(recipient);
ArgumentNullException.For<TToken>.ThrowIfNull(token);
#pragma warning disable IL2026
// We use this method as a callback for the conditional weak table, which will handle
// thread-safety for us. This first callback will try to find a generated method for the
// target recipient type, and just invoke it to get the delegate to cache and use later.
@ -212,6 +224,7 @@ public static class IMessengerExtensions
return Expression.Lambda<Action<IMessenger, object, TToken>>(body, arg0, arg1, arg2).Compile();
}
#pragma warning restore IL2026
// Get or compute the registration method for the current recipient type.
// As in CommunityToolkit.Diagnostics.TypeExtensions.ToTypeString, we use a lambda