Support Primary Constructors
olivegamestudio opened this issue · comments
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
I see several implementation options:
-
Create a new class
FGuard
(fluent guard) and repeat the methods of theGuard
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. -
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. -
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. -
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 obviousGuard.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.
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.
Duplicate of #187. Also worth noting, this would be a binary breaking change.
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.