CommunityToolkit / dotnet

.NET Community Toolkit is a collection of helpers and APIs that work for all .NET developers and are agnostic of any specific UI platform. The toolkit is maintained and published by Microsoft, and part of the .NET Foundation.
https://docs.microsoft.com/dotnet/communitytoolkit/?WT.mc_id=dotnet-0000-bramin
Other
3.07k stars 299 forks source link

Support Primary Constructors #818

Closed olivegamestudio closed 10 months ago

olivegamestudio commented 10 months ago

Overview

I would like to use Primary Constructors with the Guard class e.g.

public abstract class ViewBase<TViewModel>(TViewModel viewModel) where TViewModel : IControlViewModelBase
{
    protected TViewModel ViewModel { get; } = Guard.IsNotNull(viewModel);
}

The issue is that Guard.IsNotNull() are void methods and therefore prevent returning and setting.

I've created a helper to workaround this:

public static class GuardHelper
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static T IsNotNull<T>(T? value, string name = "")
    {
        Guard.IsNotNull<T>(value, name);
        return value;
    }
}

API breakdown

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static T IsNotNull<T>([NotNull] T? value, [CallerArgumentExpression(nameof(value))] string name = "")
    {
        if (value is not null)
        {
            return value;
        }

        ThrowHelper.ThrowArgumentNullExceptionForIsNotNull<T>(name);
    }

This would affect others such as IsNull etc.

Usage example

public abstract class ViewBase<TViewModel>(TViewModel viewModel) where TViewModel : IControlViewModelBase
{
    protected TViewModel ViewModel { get; } = Guard.IsNotNull(viewModel);
}

instead of:

public abstract class ViewBase<TViewModel>(TViewModel viewModel) where TViewModel : IControlViewModelBase
{
    public ViewBase()
    {
        Guard.IsNotNull(viewModel);
        ViewModel = viewModel;
    }

    protected TViewModel ViewModel { get; }
}

Breaking change?

I'm not sure

Alternatives

I've created a helper to workaround this:

public static class GuardHelper
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static T IsNotNull<T>(T? value, string name = "")
    {
        Guard.IsNotNull<T>(value, name);
        return value;
    }
}

Additional context

No response

Help us help you

Yes, I'd like to be assigned to work on this item

AlexRadch commented 10 months ago

I see several implementation options:

  1. Create a new class FGuard (fluent guard) and repeat the methods of the Guard class in it, making them functions. Advantages: does not break backward compatibility, only a different class name, methods have the same names and behaviors. Disadvantages: more difficult to use, you need to know the two classes and their differences, more difficult to develop, and more difficult to support.

  2. Duplicate methods directly in the Guard class, make them functions, and add some kind of prefix or suffix to them. For example: IsNotNullValue, IfIsNotNull, ReturnIsNotNull, ReturnNotNull, WhenIsNotNull, CheckIsNotNull... Advantages: does not break backward compatibility. Disadvantages: more difficult to use, it is unclear why there are two sets of methods with similar behavior in the same class, more difficult to develop, and more difficult to support.

  3. Break backward compatibility and make the methods of the Guard class functions. Advantages: only one class, only one set of methods, easier to develop, easier to support. Disadvantages: Breaks backward compatibility, may cause compiler warnings and slightly reduce performance.

  4. Add a special method to Guard: ValidateAndReturn(T value; Action<T, string>, string name). The name may be different and shorter. Advantages: only one class, only one set of methods, easier to develop, easier to support. Disadvantages: more difficult to use, the check call becomes longer and not obvious Guard.ValidateAndReturn(param, Guard.IsNotNull), it is difficult to perform a chain of several checks for one parameter.

I'm most inclined to go with option 1 or option 3 is a close second for me.

AlexRadch commented 10 months ago

I figured out how to add Performance-effective Fluent API support to C#. This proposal would also allow us to automatically Support Primary Constructors without changing the Guard class and without creating a new FGuard class. If you like it, vote for it and tell the guys who are developing C# language and C# compiler, maybe they will also support it.

Sergio0694 commented 10 months ago

Duplicate of #187. Also worth noting, this would be a binary breaking change.

AlexRadch commented 10 months ago

Duplicate of #187. Also worth noting, this would be a binary breaking change.

@Sergio0694 It will not be a binary-breaking change if we create a new class FGuard (fluent guard) and repeat the methods of the Guard class in it, making them functions. Advantages: does not break backward compatibility, only a different class name, methods have the same names and behaviors. Disadvantages: more difficult to use, you need to know the two classes and their differences, more difficult to develop, and more difficult to support.