├── .gitignore ├── nuget.config ├── Argument ├── Internal │ ├── ValidatedNotNullAttribute.cs │ ├── NotNullAtribute.cs │ └── JetBrains.Annotations.cs ├── Argument.csproj └── Argument.cs ├── Argument.Tests ├── GlobalSuppressions.cs ├── Argument.Tests.csproj ├── CodeAnalysisTests.cs ├── NullableFlowTests.cs └── ArgumentTests.cs ├── Directory.Build.props ├── License.txt ├── .github └── workflows │ └── build-and-publish.yml ├── .editorconfig ├── Readme.md └── Argument.sln /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .vs/ 3 | *.user 4 | artifacts/ 5 | project.lock.json 6 | bin/ 7 | obj/ -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Argument/Internal/ValidatedNotNullAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ArgumentInternal { 4 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] 5 | internal sealed class ValidatedNotNullAttribute : Attribute 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Argument/Internal/NotNullAtribute.cs: -------------------------------------------------------------------------------- 1 | namespace System.Diagnostics.CodeAnalysis { 2 | // https://github.com/dotnet/roslyn/issues/37544#issuecomment-533639747 3 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] 4 | internal sealed class NotNullAttribute : Attribute {} 5 | } -------------------------------------------------------------------------------- /Argument.Tests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] 9 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9.0 4 | enable 5 | true 6 | 7 | 8 | 9 | 10 | all 11 | runtime; build; native; contentfiles; analyzers; buildtransitive 12 | 13 | 14 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Andrey Shchekin 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose 4 | with or without fee is hereby granted, provided that the above copyright notice 5 | and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD 8 | TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. 9 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR 10 | CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 11 | PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 12 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/build-and-publish.yml: -------------------------------------------------------------------------------- 1 | name: 'Build and Publish' 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build-and-publish: 6 | name: 'Build and Publish' 7 | runs-on: windows-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-dotnet@v1 11 | with: 12 | dotnet-version: 5.0.x 13 | 14 | - run: dotnet build --configuration Release 15 | - run: dotnet test --no-build --configuration Release 16 | - run: dotnet pack --no-build --output . --configuration Release 17 | 18 | - run: dotnet nuget push Argument.*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{secrets.NUGET_API_KEY}} --skip-duplicate 19 | if: github.ref == 'refs/heads/main' 20 | 21 | - uses: actions/upload-artifact@v2 22 | with: 23 | name: Package 24 | path: Argument.*.nupkg -------------------------------------------------------------------------------- /Argument.Tests/Argument.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net5.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | trim_trailing_whitespace = true 8 | insert_final_newline = false 9 | 10 | [*.csproj] 11 | indent_size = 2 12 | 13 | [*.cs] 14 | csharp_new_line_before_open_brace = none 15 | csharp_style_var_for_built_in_types = true:warning 16 | csharp_style_var_when_type_is_apparent = true:warning 17 | csharp_style_var_elsewhere = true:warning 18 | 19 | # CA1822: Mark members as static 20 | dotnet_diagnostic.CA1822.severity = none 21 | 22 | # CA1062: Validate arguments of public methods 23 | dotnet_diagnostic.CA1062.severity = error 24 | 25 | # IDE rules should not need explicit levels in the future, 26 | # but for now Microsoft.CodeAnalysis.CSharp.CodeStyle requires 27 | # this during the build. Also warnings-as-errors do not work 28 | # on those, so we set them to error. 29 | 30 | # IDE0007: Use 'var' instead of explicit type 31 | dotnet_diagnostic.IDE0007.severity = error -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This project includes a single class, `Argument`, useful for argument validation/guard methods. 4 | You can get the NuGet package at: https://nuget.org/packages/Argument. 5 | 6 | [![Build status](https://ci.appveyor.com/api/projects/status/m15csoxhl0hg6t13)](https://ci.appveyor.com/project/ashmind/argument) 7 | 8 | ## Example 9 | 10 | public MyService(OtherService other) { 11 | _other = Argument.NotNull("other", other); 12 | } 13 | 14 | ## Features 15 | 16 | 1. ReSharper annotations (Argument.ExternalAnnotations.xml). 17 | Note that there is `[NotNull]` annotation on values that are being tested — that is so you do not forget to add `[NotNull]` to your arguments. 18 | 2. For .NET 4.5 Code Contracts: `[ContractArgumentValidator]`. 19 | I did not have time to test if it actually works in Visual Studio though. 20 | 3. `Argument.Ex` is an extensibility point. Example: 21 | 22 | public static T Magic(this Argument.Extensible _, string name, T value) { 23 | //.. 24 | } 25 | 26 | Argument.Ex.Magic("name", value); 27 | -------------------------------------------------------------------------------- /Argument.Tests/CodeAnalysisTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | // nothing here should generate warnings (which are set to produce errors anyways) 4 | public class CodeAnalysisTests { 5 | public int CA1062_ArgumentCheck_NotRequired_NotNull(string argument) { 6 | Argument.NotNull(nameof(argument), argument); 7 | return argument.Length; 8 | } 9 | 10 | public int CA1062_ArgumentCheck_NotRequired_NotNullOrEmpty_String(string argument) { 11 | Argument.NotNullOrEmpty(nameof(argument), argument); 12 | return argument.Length; 13 | } 14 | 15 | public int CA1062_ArgumentCheck_NotRequired_NotNullOrEmpty_Array(int[] argument) { 16 | Argument.NotNullOrEmpty(nameof(argument), argument); 17 | return argument.Length; 18 | } 19 | 20 | public int CA1062_ArgumentCheck_NotRequired_NotNullOrEmpty_List(IReadOnlyList argument) { 21 | Argument.NotNullOrEmpty(nameof(argument), argument); 22 | return argument.Count; 23 | } 24 | 25 | public int CA1062_ArgumentCheck_NotRequired_NotNullOrWhiteSpace_String(string argument) { 26 | Argument.NotNullOrWhiteSpace(nameof(argument), argument); 27 | return argument.Length; 28 | } 29 | 30 | public int CA1062_ArgumentCheck_NotRequired_NotNullAndCast(string argument) { 31 | Argument.NotNullAndCast(nameof(argument), argument); 32 | return argument.Length; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Argument.Tests/NullableFlowTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | // nothing here should generate warnings (which are set to produce errors anyways) 4 | public class NullableFlowTests { 5 | public int CS8602_ValueIsNotNullable_After_NotNull(string? argument) { 6 | Argument.NotNull(nameof(argument), argument!); 7 | return argument.Length; 8 | } 9 | 10 | public int CS8602_ValueIsNotNullable_After_NotNullOrEmpty_String(string? argument) { 11 | Argument.NotNullOrEmpty(nameof(argument), argument!); 12 | return argument.Length; 13 | } 14 | 15 | public int CS8602_ValueIsNotNullable_After_NotNullOrEmpty_Array(int[]? argument) { 16 | Argument.NotNullOrEmpty(nameof(argument), argument!); 17 | return argument.Length; 18 | } 19 | 20 | public int CS8602_ValueIsNotNullable_After_NotNullOrEmpty_List(IReadOnlyList? argument) { 21 | Argument.NotNullOrEmpty(nameof(argument), argument!); 22 | return argument.Count; 23 | } 24 | 25 | public int CS8602_ValueIsNotNullable_After_NotNullOrWhiteSpace_String(string? argument) { 26 | Argument.NotNullOrWhiteSpace(nameof(argument), argument!); 27 | return argument.Length; 28 | } 29 | 30 | public int CS8602_ValueIsNotNullable_After_NotNullAndCast(string? argument) { 31 | Argument.NotNullAndCast(nameof(argument), argument!); 32 | return argument.Length; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Argument/Argument.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | 7 | Argument validation methods, for example: 8 | this.user = Argument.NotNull("user", user). 9 | 10 | Less fancy than approaches based on lambdas/anonymous classes, but less scary performance-wise (given that argument checks are often all over the place). 11 | 12 | Annotations: C# 8 nullable reference types, ReSharper, Microsoft.CodeQuality.Analyzers, Code Contracts (.NET 4.5 only). 13 | Copyright (c) 2013–2020 Andrey Shchekin 14 | 1.4.0 15 | Andrey Shchekin 16 | net45;netstandard11 17 | Argument 18 | Argument 19 | contracts;validation;argument 20 | https://github.com/ashmind/argument 21 | ISC 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Argument.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.2015 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{48B9BF9A-498F-4238-9F1E-CE9FAB616B09}" 7 | ProjectSection(SolutionItems) = preProject 8 | Argument.cs = Argument.cs 9 | Argument.ExternalAnnotations.xml = Argument.ExternalAnnotations.xml 10 | License.txt = License.txt 11 | EndProjectSection 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Properties", "Properties", "{E40A2F3C-917C-47F5-A3F7-C89DF7C5939A}" 14 | ProjectSection(SolutionItems) = preProject 15 | Properties\AssemblyInfo.cs = Properties\AssemblyInfo.cs 16 | EndProjectSection 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Argument", "Argument\Argument.csproj", "{8C077462-1F3C-493A-924D-F1DA430E1E1C}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Argument.Tests", "Argument.Tests\Argument.Tests.csproj", "{0F628144-FFB1-4095-BFC6-FFD4295D869D}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Debug|Mixed Platforms = Debug|Mixed Platforms 26 | Debug|x86 = Debug|x86 27 | Release|Any CPU = Release|Any CPU 28 | Release|Mixed Platforms = Release|Mixed Platforms 29 | Release|x86 = Release|x86 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {8C077462-1F3C-493A-924D-F1DA430E1E1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {8C077462-1F3C-493A-924D-F1DA430E1E1C}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {8C077462-1F3C-493A-924D-F1DA430E1E1C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 35 | {8C077462-1F3C-493A-924D-F1DA430E1E1C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 36 | {8C077462-1F3C-493A-924D-F1DA430E1E1C}.Debug|x86.ActiveCfg = Debug|Any CPU 37 | {8C077462-1F3C-493A-924D-F1DA430E1E1C}.Debug|x86.Build.0 = Debug|Any CPU 38 | {8C077462-1F3C-493A-924D-F1DA430E1E1C}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {8C077462-1F3C-493A-924D-F1DA430E1E1C}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {8C077462-1F3C-493A-924D-F1DA430E1E1C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 41 | {8C077462-1F3C-493A-924D-F1DA430E1E1C}.Release|Mixed Platforms.Build.0 = Release|Any CPU 42 | {8C077462-1F3C-493A-924D-F1DA430E1E1C}.Release|x86.ActiveCfg = Release|Any CPU 43 | {8C077462-1F3C-493A-924D-F1DA430E1E1C}.Release|x86.Build.0 = Release|Any CPU 44 | {0F628144-FFB1-4095-BFC6-FFD4295D869D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {0F628144-FFB1-4095-BFC6-FFD4295D869D}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {0F628144-FFB1-4095-BFC6-FFD4295D869D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 47 | {0F628144-FFB1-4095-BFC6-FFD4295D869D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 48 | {0F628144-FFB1-4095-BFC6-FFD4295D869D}.Debug|x86.ActiveCfg = Debug|Any CPU 49 | {0F628144-FFB1-4095-BFC6-FFD4295D869D}.Debug|x86.Build.0 = Debug|Any CPU 50 | {0F628144-FFB1-4095-BFC6-FFD4295D869D}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {0F628144-FFB1-4095-BFC6-FFD4295D869D}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {0F628144-FFB1-4095-BFC6-FFD4295D869D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 53 | {0F628144-FFB1-4095-BFC6-FFD4295D869D}.Release|Mixed Platforms.Build.0 = Release|Any CPU 54 | {0F628144-FFB1-4095-BFC6-FFD4295D869D}.Release|x86.ActiveCfg = Release|Any CPU 55 | {0F628144-FFB1-4095-BFC6-FFD4295D869D}.Release|x86.Build.0 = Release|Any CPU 56 | EndGlobalSection 57 | GlobalSection(SolutionProperties) = preSolution 58 | HideSolutionNode = FALSE 59 | EndGlobalSection 60 | GlobalSection(NestedProjects) = preSolution 61 | {E40A2F3C-917C-47F5-A3F7-C89DF7C5939A} = {48B9BF9A-498F-4238-9F1E-CE9FAB616B09} 62 | EndGlobalSection 63 | GlobalSection(ExtensibilityGlobals) = postSolution 64 | SolutionGuid = {D1B12DA8-F185-4BA1-B149-B87D3554872D} 65 | EndGlobalSection 66 | EndGlobal 67 | -------------------------------------------------------------------------------- /Argument/Internal/JetBrains.Annotations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | #pragma warning disable 1591 6 | // ReSharper disable UnusedMember.Global 7 | // ReSharper disable MemberCanBePrivate.Global 8 | // ReSharper disable UnusedAutoPropertyAccessor.Global 9 | // ReSharper disable IntroduceOptionalParameters.Global 10 | // ReSharper disable MemberCanBeProtected.Global 11 | // ReSharper disable InconsistentNaming 12 | 13 | // ReSharper disable once CheckNamespace 14 | namespace JetBrains.Annotations { 15 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event)] 16 | internal sealed class CanBeNullAttribute : Attribute { } 17 | 18 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event)] 19 | internal sealed class NotNullAttribute : Attribute { } 20 | 21 | [AttributeUsage(AttributeTargets.Parameter)] 22 | internal sealed class InvokerParameterNameAttribute : Attribute { } 23 | 24 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 25 | internal sealed class ContractAnnotationAttribute : Attribute { 26 | public ContractAnnotationAttribute([NotNull] string contract) 27 | : this(contract, false) { } 28 | 29 | public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) { 30 | Contract = contract; 31 | ForceFullStates = forceFullStates; 32 | } 33 | 34 | public string Contract { get; private set; } 35 | public bool ForceFullStates { get; private set; } 36 | } 37 | 38 | [AttributeUsage(AttributeTargets.All)] 39 | [Conditional("JETBRAINS_ANNOTATIONS")] 40 | internal sealed class UsedImplicitlyAttribute : Attribute { 41 | public UsedImplicitlyAttribute() 42 | : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } 43 | 44 | public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) 45 | : this(useKindFlags, ImplicitUseTargetFlags.Default) { } 46 | 47 | public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) 48 | : this(ImplicitUseKindFlags.Default, targetFlags) { } 49 | 50 | public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) { 51 | UseKindFlags = useKindFlags; 52 | TargetFlags = targetFlags; 53 | } 54 | 55 | public ImplicitUseKindFlags UseKindFlags { get; private set; } 56 | public ImplicitUseTargetFlags TargetFlags { get; private set; } 57 | } 58 | 59 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)] 60 | [Conditional("JETBRAINS_ANNOTATIONS")] 61 | internal sealed class MeansImplicitUseAttribute : Attribute { 62 | public MeansImplicitUseAttribute() : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } 63 | 64 | public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) 65 | : this(useKindFlags, ImplicitUseTargetFlags.Default) { } 66 | 67 | public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) 68 | : this(ImplicitUseKindFlags.Default, targetFlags) { } 69 | 70 | public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) { 71 | UseKindFlags = useKindFlags; 72 | TargetFlags = targetFlags; 73 | } 74 | 75 | [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; } 76 | [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; } 77 | } 78 | 79 | [Flags] 80 | internal enum ImplicitUseKindFlags { 81 | Default = Access | Assign | InstantiatedWithFixedConstructorSignature, 82 | Access = 1, 83 | Assign = 2, 84 | InstantiatedWithFixedConstructorSignature = 4, 85 | InstantiatedNoFixedConstructorSignature = 8, 86 | } 87 | 88 | [Flags] 89 | internal enum ImplicitUseTargetFlags { 90 | Default = Itself, 91 | Itself = 1, 92 | Members = 2, 93 | WithMembers = Itself | Members 94 | } 95 | 96 | [MeansImplicitUse] 97 | [Conditional("JETBRAINS_ANNOTATIONS")] 98 | internal sealed class PublicAPIAttribute : Attribute { 99 | public PublicAPIAttribute() { } 100 | public PublicAPIAttribute([NotNull] string comment) { 101 | Comment = comment; 102 | } 103 | 104 | public string? Comment { get; private set; } 105 | } 106 | 107 | [AttributeUsage(AttributeTargets.Parameter)] 108 | internal sealed class InstantHandleAttribute : Attribute { } 109 | 110 | [AttributeUsage(AttributeTargets.Method)] 111 | internal sealed class AssertionMethodAttribute : Attribute { } 112 | 113 | [AttributeUsage(AttributeTargets.Parameter)] 114 | internal sealed class AssertionConditionAttribute : Attribute { 115 | public AssertionConditionAttribute(AssertionConditionType conditionType) { 116 | ConditionType = conditionType; 117 | } 118 | 119 | public AssertionConditionType ConditionType { get; private set; } 120 | } 121 | 122 | internal enum AssertionConditionType { 123 | IS_TRUE = 0, 124 | IS_FALSE = 1, 125 | IS_NULL = 2, 126 | IS_NOT_NULL = 3, 127 | } 128 | 129 | [AttributeUsage(AttributeTargets.Parameter)] 130 | internal sealed class NoEnumerationAttribute : Attribute { } 131 | } -------------------------------------------------------------------------------- /Argument.Tests/ArgumentTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using Xunit; 8 | 9 | // Broken by nullable reference types 10 | #pragma warning disable xUnit1019 // MemberData must reference a member providing a valid data type 11 | 12 | // ReSharper disable CheckNamespace 13 | [SuppressMessage("Design", "CA1062:Validate arguments of public methods")] 14 | public class ArgumentTests { 15 | [Theory] 16 | [MemberData(nameof(NullVariants))] 17 | public void NotNullVariant_ThrowsArgumentNullException_WithCorrectMessage_WhenValueIsNull(Expression> validateExpression) { 18 | var validate = validateExpression.Compile(); 19 | var exception = Assert.Throws(() => validate("x")); 20 | Assert.Equal(new ArgumentNullException("x").Message, exception.Message); 21 | } 22 | 23 | [Theory] 24 | [MemberData(nameof(NullVariants))] 25 | public void NotNullVariant_ThrowsArgumentNullException_WithCorrectParamName_WhenValueIsNull(Expression> validateExpression) { 26 | var validate = validateExpression.Compile(); 27 | var exception = Assert.Throws(() => validate("x")); 28 | Assert.Equal("x", exception.ParamName); 29 | } 30 | 31 | [Theory] 32 | [MemberData(nameof(EmptyVariants))] 33 | public void NotNullOrEmptyVariant_ThrowsArgumentException_WithCorrectParamName_WhenValueIsEmpty(Expression> validateExpression) { 34 | var validate = validateExpression.Compile(); 35 | var exception = Assert.Throws(() => validate("x")); 36 | Assert.Equal("x", exception.ParamName); 37 | } 38 | 39 | [Theory] 40 | [MemberData(nameof(WhiteSpaceVariants))] 41 | public void NotNullOrWhiteSpaceVariant_ThrowsArgumentException_WithCorrectParamName_WhenValueIsWhiteSpace(Expression> validateExpression) { 42 | var validate = validateExpression.Compile(); 43 | var exception = Assert.Throws(() => validate("x")); 44 | Assert.Equal("x", exception.ParamName); 45 | } 46 | 47 | [Theory] 48 | [MemberData(nameof(IncorrectTypeVariants))] 49 | public void CastVariant_ThrowsArgumentException_WithCorrectParamName_WhenValueHasIncorrectType(Expression> validateExpression) { 50 | var validate = validateExpression.Compile(); 51 | var exception = Assert.Throws(() => validate("x")); 52 | Assert.Equal("x", exception.ParamName); 53 | } 54 | 55 | [Theory] 56 | [MemberData(nameof(OutOfRangeVariants))] 57 | public void RangeVariant_ThrowsArgumentOutOfRangeException_WithCorrectParamName_WhenValueIsOutOfRange(Expression> validateExpression) { 58 | var validate = validateExpression.Compile(); 59 | var exception = Assert.Throws(() => validate("x")); 60 | Assert.Equal("x", exception.ParamName); 61 | } 62 | 63 | [Theory] 64 | [MemberData(nameof(SuccessVariants))] 65 | public void AnyVariant_ReturnsArgumentValue_WhenSuccessful(T argument, Expression> validateExpression) { 66 | var validate = validateExpression.Compile(); 67 | var result = validate(argument); 68 | 69 | if (typeof(T).GetTypeInfo().IsValueType) { 70 | Assert.Equal(argument, result); 71 | } 72 | else { 73 | #pragma warning disable xUnit2005 // Do not use identity check on value type 74 | Assert.Same(argument, result); 75 | #pragma warning restore xUnit2005 // Do not use identity check on value type 76 | } 77 | } 78 | 79 | public static IEnumerable NullVariants { 80 | get { 81 | yield return SimpleDataRow(name => Argument.NotNull(name, (object?)null!)); 82 | yield return SimpleDataRow(name => Argument.NotNull(name, (int?)null)); 83 | yield return SimpleDataRow(name => Argument.NotNullOrEmpty(name, null!)); 84 | yield return SimpleDataRow(name => Argument.NotNullOrEmpty(name, (object[]?)null!)); 85 | yield return SimpleDataRow(name => Argument.NotNullOrEmpty(name, (List?)null!)); 86 | yield return SimpleDataRow(name => Argument.NotNullOrWhiteSpace(name, null!)); 87 | yield return SimpleDataRow(name => Argument.NotNullAndCast(name, null!)); 88 | } 89 | } 90 | 91 | public static IEnumerable EmptyVariants { 92 | get { 93 | yield return SimpleDataRow(name => Argument.NotNullOrEmpty(name, "")); 94 | yield return SimpleDataRow(name => Argument.NotNullOrEmpty(name, new object[0])); 95 | yield return SimpleDataRow(name => Argument.NotNullOrEmpty(name, new List())); 96 | yield return SimpleDataRow(name => Argument.NotNullOrEmpty(name, new List())); 97 | yield return SimpleDataRow(name => Argument.NotNullOrEmpty(name, (IReadOnlyCollection)new List())); 98 | yield return SimpleDataRow(name => Argument.NotNullOrEmpty(name, ImmutableList.Create())); 99 | yield return SimpleDataRow(name => Argument.NotNullOrWhiteSpace(name, "")); 100 | } 101 | } 102 | 103 | public static IEnumerable WhiteSpaceVariants { 104 | get { 105 | yield return SimpleDataRow(name => Argument.NotNullOrWhiteSpace(name, "\t")); 106 | } 107 | } 108 | 109 | public static IEnumerable IncorrectTypeVariants { 110 | get { 111 | yield return SimpleDataRow(name => Argument.Cast(name, new object())); 112 | } 113 | } 114 | 115 | public static IEnumerable OutOfRangeVariants { 116 | get { 117 | yield return SimpleDataRow(name => Argument.PositiveNonZero(name, -1)); 118 | yield return SimpleDataRow(name => Argument.PositiveNonZero(name, int.MinValue)); 119 | yield return SimpleDataRow(name => Argument.PositiveNonZero(name, 0)); 120 | yield return SimpleDataRow(name => Argument.PositiveOrZero(name, -1)); 121 | yield return SimpleDataRow(name => Argument.PositiveOrZero(name, int.MinValue)); 122 | 123 | 124 | yield return SimpleDataRow(name => Argument.PositiveNonZero(name, -1L)); 125 | yield return SimpleDataRow(name => Argument.PositiveNonZero(name, long.MinValue)); 126 | yield return SimpleDataRow(name => Argument.PositiveNonZero(name, 0L)); 127 | yield return SimpleDataRow(name => Argument.PositiveOrZero(name, -1L)); 128 | yield return SimpleDataRow(name => Argument.PositiveOrZero(name, long.MinValue)); 129 | 130 | } 131 | } 132 | 133 | public static IEnumerable SuccessVariants { 134 | get { 135 | yield return SuccessDataRow(new object(), value => Argument.NotNull("x", value)); 136 | yield return SuccessDataRow("abc", value => Argument.NotNullOrEmpty("x", value)); 137 | yield return SuccessDataRow(new object[1], value => Argument.NotNullOrEmpty("x", value)); 138 | yield return SuccessDataRow(new List { new object() }, value => Argument.NotNullOrEmpty("x", value)); 139 | yield return SuccessDataRow(" abc ", value => Argument.NotNullOrWhiteSpace("x", value)); 140 | 141 | yield return SuccessDataRow("abc", value => Argument.Cast("x", value)); 142 | yield return SuccessDataRow("abc", value => Argument.NotNullAndCast("x", value)); 143 | 144 | yield return SuccessDataRow(1, value => Argument.PositiveNonZero("x", value)); 145 | yield return SuccessDataRow(int.MaxValue, value => Argument.PositiveNonZero("x", value)); 146 | yield return SuccessDataRow(1, value => Argument.PositiveOrZero("x", value)); 147 | yield return SuccessDataRow(int.MaxValue, value => Argument.PositiveOrZero("x", value)); 148 | yield return SuccessDataRow(0, value => Argument.PositiveOrZero("x", value)); 149 | 150 | yield return SuccessDataRow(1L, value => Argument.PositiveNonZero("x", value)); 151 | yield return SuccessDataRow(long.MaxValue, value => Argument.PositiveNonZero("x", value)); 152 | yield return SuccessDataRow(1L, value => Argument.PositiveOrZero("x", value)); 153 | yield return SuccessDataRow(long.MaxValue, value => Argument.PositiveOrZero("x", value)); 154 | yield return SuccessDataRow(0L, value => Argument.PositiveOrZero("x", value)); 155 | 156 | } 157 | } 158 | 159 | private static object[] SimpleDataRow(Expression> action) { 160 | return new object[] { action }; 161 | } 162 | 163 | private static object[] SuccessDataRow(T argument, Expression> validate) 164 | where T: notnull 165 | { 166 | return new object[] { argument, validate }; 167 | } 168 | } -------------------------------------------------------------------------------- /Argument/Argument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Diagnostics.Contracts; 6 | using CodeAnalysis = System.Diagnostics.CodeAnalysis; 7 | using JetBrains.Annotations; 8 | using ArgumentInternal; 9 | 10 | // ReSharper disable CheckNamespace 11 | /// 12 | /// Provides methods for verification of argument preconditions. 13 | /// 14 | public static class Argument { 15 | // ReSharper restore CheckNamespace 16 | /// 17 | /// Verifies that a given argument value is not null and returns the value provided. 18 | /// 19 | /// Type of the . 20 | /// Argument name. 21 | /// Argument value. 22 | /// Thrown if is null. 23 | /// if it is not null. 24 | [NotNull, AssertionMethod] 25 | [ContractAnnotation("value:null => halt;value:notnull => notnull")] 26 | [ContractArgumentValidator] 27 | public static T NotNull( 28 | [NotNull, InvokerParameterName] string name, 29 | [NotNull, AssertionCondition(AssertionConditionType.IS_NOT_NULL), NoEnumeration, ValidatedNotNull, CodeAnalysis.NotNull] T value 30 | ) 31 | where T : class 32 | { 33 | if (value == null) 34 | throw new ArgumentNullException(name); 35 | Contract.EndContractBlock(); 36 | 37 | return value; 38 | } 39 | 40 | /// 41 | /// Verifies that a given argument value is not null and returns the value provided. 42 | /// 43 | /// Type of the . 44 | /// Argument name. 45 | /// Argument value. 46 | /// Thrown if is null. 47 | /// if it is not null. 48 | [NotNull, AssertionMethod] 49 | [ContractAnnotation("value:null => halt;value:notnull => notnull")] 50 | [ContractArgumentValidator] 51 | public static T NotNull( 52 | [NotNull, InvokerParameterName] string name, 53 | [NotNull, AssertionCondition(AssertionConditionType.IS_NOT_NULL), NoEnumeration, ValidatedNotNull, CodeAnalysis.NotNull] T? value 54 | ) 55 | where T : struct 56 | { 57 | if (value == null) 58 | throw new ArgumentNullException(name); 59 | Contract.EndContractBlock(); 60 | 61 | return value.Value; 62 | } 63 | 64 | /// 65 | /// Verifies that a given argument value is not null or empty and returns the value provided. 66 | /// 67 | /// Argument name. 68 | /// Argument value. 69 | /// Thrown if is null. 70 | /// Thrown if is empty. 71 | /// if it is not null or empty. 72 | [NotNull, AssertionMethod] 73 | [ContractAnnotation("value:null => halt;value:notnull => notnull")] 74 | [ContractArgumentValidator] 75 | public static string NotNullOrEmpty( 76 | [NotNull, InvokerParameterName] string name, 77 | [NotNull, AssertionCondition(AssertionConditionType.IS_NOT_NULL), ValidatedNotNull, CodeAnalysis.NotNull] string value 78 | ) { 79 | Argument.NotNull(name, value); 80 | if (value.Length == 0) 81 | throw NewArgumentEmptyException(name); 82 | Contract.EndContractBlock(); 83 | 84 | return value; 85 | } 86 | 87 | /// 88 | /// Verifies that a given argument value is not null or empty and returns the value provided. 89 | /// 90 | /// Argument name. 91 | /// Argument value. 92 | /// Thrown if is null. 93 | /// Thrown if is empty. 94 | /// if it is not null or empty. 95 | [NotNull, AssertionMethod] 96 | [ContractAnnotation("value:null => halt;value:notnull => notnull")] 97 | [ContractArgumentValidator] 98 | public static T[] NotNullOrEmpty( 99 | [NotNull, InvokerParameterName] string name, 100 | [NotNull, AssertionCondition(AssertionConditionType.IS_NOT_NULL), ValidatedNotNull, CodeAnalysis.NotNull] T[] value 101 | ) { 102 | Argument.NotNull(name, value); 103 | if (value.Length == 0) 104 | throw NewArgumentEmptyException(name); 105 | Contract.EndContractBlock(); 106 | 107 | return value; 108 | } 109 | 110 | /// 111 | /// Verifies that a given argument value is not null or empty and returns the value provided. 112 | /// 113 | /// Argument name. 114 | /// Argument value. 115 | /// Thrown if is null. 116 | /// Thrown if is empty. 117 | /// if it is not null or empty. 118 | [NotNull, AssertionMethod] 119 | [ContractAnnotation("value:null => halt;value:notnull => notnull")] 120 | [ContractArgumentValidator] 121 | public static TCollection NotNullOrEmpty( 122 | [NotNull, InvokerParameterName] string name, 123 | [NotNull, AssertionCondition(AssertionConditionType.IS_NOT_NULL), ValidatedNotNull, CodeAnalysis.NotNull] TCollection value 124 | ) 125 | where TCollection : class, IEnumerable 126 | { 127 | Argument.NotNull(name, value); 128 | var enumerator = value.GetEnumerator(); 129 | try { 130 | if (!enumerator.MoveNext()) 131 | throw NewArgumentEmptyException(name); 132 | } 133 | finally { 134 | var disposable = enumerator as IDisposable; 135 | if (disposable != null) 136 | disposable.Dispose(); 137 | } 138 | Contract.EndContractBlock(); 139 | 140 | return value; 141 | } 142 | 143 | /// 144 | /// Verifies that a given argument value is not null, empty, or consists only of white-space characters and returns the value provided. 145 | /// 146 | /// Argument name. 147 | /// Argument value. 148 | /// Thrown if is null. 149 | /// Thrown if is empty. 150 | /// Thrown if consists only of white-space characters. 151 | /// if it is not null, empty, or consists only of white-space characters. 152 | [NotNull, AssertionMethod] 153 | [ContractAnnotation("value:null => halt;value:notnull => notnull")] 154 | [ContractArgumentValidator] 155 | public static string NotNullOrWhiteSpace( 156 | [NotNull, InvokerParameterName] string name, 157 | [NotNull, AssertionCondition(AssertionConditionType.IS_NOT_NULL), ValidatedNotNull, CodeAnalysis.NotNull] string value 158 | ) { 159 | Argument.NotNullOrEmpty(name, value); 160 | var containsOnlyWhiteSpace = true; 161 | for (var i = 0; i < value.Length; i++) { 162 | if (!char.IsWhiteSpace(value[i])) { 163 | containsOnlyWhiteSpace = false; 164 | break; 165 | } 166 | } 167 | if (containsOnlyWhiteSpace) 168 | throw new ArgumentException("Value cannot consist only of white-space characters.", name); 169 | Contract.EndContractBlock(); 170 | 171 | return value; 172 | } 173 | 174 | private const string PotentialDoubleEnumeration = "Using NotNullOrEmpty with plain IEnumerable may cause double enumeration. Please use a collection instead."; 175 | 176 | /// 177 | /// (DO NOT USE) Ensures that NotNullOrEmpty can not be used with plain , 178 | /// as this may cause double enumeration. 179 | /// 180 | [EditorBrowsable(EditorBrowsableState.Never)] 181 | [Obsolete(PotentialDoubleEnumeration, true)] 182 | // ReSharper disable UnusedParameter.Global 183 | public static void NotNullOrEmpty(string name, IEnumerable value) { 184 | // ReSharper restore UnusedParameter.Global 185 | throw new Exception(PotentialDoubleEnumeration); 186 | } 187 | 188 | /// 189 | /// (DO NOT USE) Ensures that NotNullOrEmpty can not be used with plain , 190 | /// as this may cause double enumeration. 191 | /// 192 | [EditorBrowsable(EditorBrowsableState.Never)] 193 | [Obsolete(PotentialDoubleEnumeration, true)] 194 | // ReSharper disable UnusedParameter.Global 195 | public static void NotNullOrEmpty(string name, IEnumerable value) { 196 | // ReSharper restore UnusedParameter.Global 197 | throw new Exception(PotentialDoubleEnumeration); 198 | } 199 | 200 | private static Exception NewArgumentEmptyException(string name) { 201 | return new ArgumentException("Value cannot be empty.", name); 202 | } 203 | 204 | /// 205 | /// Casts a given argument into a given type if possible. 206 | /// 207 | /// Type to cast into. 208 | /// Argument name. 209 | /// Argument value. 210 | /// Thrown if can not be cast into type . 211 | /// cast into . 212 | [AssertionMethod] 213 | [ContractArgumentValidator] 214 | public static T Cast([NotNull, InvokerParameterName] string name, object? value) { 215 | if (!(value is T)) 216 | throw new ArgumentException($"Value \"{value}\" is not of type \"{typeof(T)}\".", name); 217 | Contract.EndContractBlock(); 218 | 219 | return (T)value; 220 | } 221 | 222 | /// 223 | /// Verfies that a given argument is not null and casts it into a given type if possible. 224 | /// 225 | /// Type to cast into. 226 | /// Argument name. 227 | /// Argument value. 228 | /// Thrown if is null. 229 | /// Thrown if can not be cast into type . 230 | /// cast into . 231 | [NotNull, AssertionMethod] 232 | [ContractAnnotation("value:null => halt;value:notnull => notnull")] 233 | [ContractArgumentValidator] 234 | public static T NotNullAndCast( 235 | [NotNull, InvokerParameterName] string name, 236 | [NotNull, AssertionCondition(AssertionConditionType.IS_NOT_NULL), NoEnumeration, ValidatedNotNull, CodeAnalysis.NotNull] object value 237 | ) { 238 | Argument.NotNull(name, value); 239 | return Argument.Cast(name, value); 240 | } 241 | 242 | /// 243 | /// Verifies that a given argument value is greater than or equal to zero and returns the value provided. 244 | /// 245 | /// Argument name. 246 | /// Argument value. 247 | /// Thrown if is less than zero. 248 | /// if it is greater than or equal to zero. 249 | [AssertionMethod] 250 | [ContractArgumentValidator] 251 | public static int PositiveOrZero([NotNull, InvokerParameterName] string name, int value) { 252 | if (value < 0) 253 | throw new ArgumentOutOfRangeException(name, value, "Value cannot be negative."); 254 | Contract.EndContractBlock(); 255 | 256 | return value; 257 | } 258 | 259 | /// 260 | /// Verifies that a given argument value is greater than or equal to zero and returns the value provided. 261 | /// 262 | /// Argument name. 263 | /// Argument value. 264 | /// Thrown if is less than zero. 265 | /// if it is greater than or equal to zero. 266 | [AssertionMethod] 267 | [ContractArgumentValidator] 268 | public static long PositiveOrZero([NotNull, InvokerParameterName] string name, long value) { 269 | if (value < 0) 270 | throw new ArgumentOutOfRangeException(name, value, "Value cannot be negative."); 271 | Contract.EndContractBlock(); 272 | 273 | return value; 274 | } 275 | 276 | /// 277 | /// Verifies that a given argument value is greater than zero and returns the value provided. 278 | /// 279 | /// Argument name. 280 | /// Argument value. 281 | /// Thrown if is less than or equal to zero. 282 | /// if it is greater than zero. 283 | [AssertionMethod] 284 | [ContractArgumentValidator] 285 | public static int PositiveNonZero([NotNull, InvokerParameterName] string name, int value) { 286 | if (value < 0) 287 | throw new ArgumentOutOfRangeException(name, value, "Value cannot be negative."); 288 | if (value == 0) 289 | throw new ArgumentOutOfRangeException(name, value, "Value cannot be zero."); 290 | Contract.EndContractBlock(); 291 | 292 | return value; 293 | } 294 | 295 | /// 296 | /// Verifies that a given argument value is greater than or equal to zero and returns the value provided. 297 | /// 298 | /// Argument name. 299 | /// Argument value. 300 | /// Thrown if is less than zero. 301 | /// if it is greater than or equal to zero. 302 | [AssertionMethod] 303 | [ContractArgumentValidator] 304 | public static long PositiveNonZero([NotNull, InvokerParameterName] string name, long value) { 305 | if (value < 0) 306 | throw new ArgumentOutOfRangeException(name, value, "Value cannot be negative."); 307 | if (value == 0) 308 | throw new ArgumentOutOfRangeException(name, value, "Value cannot be zero."); 309 | Contract.EndContractBlock(); 310 | 311 | return value; 312 | } 313 | 314 | /// 315 | /// Provides an extensibility point that can be used by custom argument validation extension methods. 316 | /// 317 | /// Always returns null, extension methods for should ignore this value. 318 | public static Extensible? Ex { 319 | get { return null; } 320 | } 321 | 322 | /// 323 | /// Provides an extensibility point that can be used by custom argument validation extension methods. 324 | /// 325 | /// 326 | [EditorBrowsable(EditorBrowsableState.Never)] 327 | public sealed class Extensible { 328 | #pragma warning disable 1591 329 | [EditorBrowsable(EditorBrowsableState.Never)] 330 | public override bool Equals(object obj) => throw new NotSupportedException(); 331 | 332 | // ReSharper disable once NonReadonlyFieldInGetHashCode 333 | [EditorBrowsable(EditorBrowsableState.Never)] 334 | public override int GetHashCode() => throw new NotSupportedException(); 335 | 336 | [EditorBrowsable(EditorBrowsableState.Never)] 337 | public new Type GetType() => base.GetType(); 338 | 339 | [EditorBrowsable(EditorBrowsableState.Never)] 340 | public override string ToString() => throw new NotSupportedException(); 341 | #pragma warning disable 1591 342 | } 343 | } 344 | --------------------------------------------------------------------------------