feat: support C# 11 required modifier during validation (Closes #76) - #101
feat: support C# 11 required modifier during validation (Closes #76)#101Moha-sami wants to merge 6 commits into
Conversation
DamianEdwards
left a comment
There was a problem hiding this comment.
The cache-layer approach makes sense, but the current implementation changes validation semantics beyond the behavior requested in #76. In particular, nullable required members become invalid, explicit TypeDescriptor validation can be overridden, and unrelated same-named attributes are treated as compiler metadata.
| { | ||
| skipRecursionAttribute = skipRecursionAttr; | ||
| } | ||
| else if (attr.GetType().Name == "RequiredMemberAttribute") |
There was a problem hiding this comment.
Could this compare the fully qualified name to System.Runtime.CompilerServices.RequiredMemberAttribute instead? Matching only Type.Name means an unrelated user-defined attribute named RequiredMemberAttribute in any namespace will silently add required validation.
| else if (attr.GetType().Name == "RequiredMemberAttribute") | ||
| { | ||
| validationAttributes ??= new(); | ||
| if (!validationAttributes.OfType<RequiredAttribute>().Any()) |
There was a problem hiding this comment.
Please defer this decision until after all attribute sources have been processed. TypeDescriptor attributes are concatenated after the property attributes, so a RequiredAttribute supplied there (for example, with AllowEmptyStrings = true) arrives too late to prevent this default attribute from being added. Both attributes then run and the explicit validation behavior cannot prevail.
| validationAttributes ??= new(); | ||
| if (!validationAttributes.OfType<RequiredAttribute>().Any()) | ||
| { | ||
| validationAttributes.Add(new RequiredAttribute()); |
There was a problem hiding this comment.
This should not synthesize [Required] for nullable members. The C# required modifier requires initialization, not a non-null value, so required string? Name and required int? Value can validly be initialized to null. Issue #76 explicitly calls out that nullable required members should be ignored, while the new test currently locks in the opposite behavior. Please gate this using nullability metadata and cover both nullable reference and value types.
…type name, and defer decision per review
Moha-sami
commented
Aug 13, 2026
Hi @DamianEdwards, thanks a lot for the thorough review and great feedback! I've updated the PR implementation based on your comments:
Added unit tests covering all these edge cases across .NET 8, 9, and 10. All 276 unit tests are passing cleanly! |
…for netstandard2.0 support
DamianEdwards
left a comment
There was a problem hiding this comment.
The previous netstandard2.0 compile failure and the original attribute identity/ordering concerns are addressed. Three correctness issues remain in the updated nullability implementation: non-nullable value types affect RequiresValidation, the shared nullability context is unsafe under concurrent cache initialization, and the netstandard2.0 fallback misses ambient nullable context metadata. Coverage for explicit RequiredAttribute options/subclasses retaining precedence would also be useful, but that is not the blocker here.
| } | ||
| } | ||
| if (hasRequiredMemberAttribute && !IsPropertyNullable(property)) |
There was a problem hiding this comment.
Please exclude non-nullable value types here. required int cannot be null, so adding [Required] does not change instance validation, but it does make the public RequiresValidation API return true for a type that otherwise has nothing to validate. Issue #76 explicitly says required value types should be ignored; a regression test for required int would catch this.
| } | ||
| #if NET6_0_OR_GREATER | ||
| private static readonly NullabilityInfoContext _nullabilityContext = new(); |
There was a problem hiding this comment.
A shared NullabilityInfoContext is not safe here because this cache can initialize details for different types concurrently, while NullabilityInfoContext mutates internal caches without synchronization. Concurrent first use can throw (for example, duplicate-key ArgumentException). Please create a context per lookup or synchronize access to this instance.
| #else | ||
| if (!property.PropertyType.IsValueType) | ||
| { | ||
| var nullableAttr = property.GetCustomAttributes(false) |
There was a problem hiding this comment.
This fallback misses nullable annotations represented by NullableContextAttribute on the declaring type (or an enclosing type/module) rather than a property-level NullableAttribute. Roslyn can encode required string? that way, so the netstandard2.0 target will classify it as non-nullable and reject null. Please resolve the ambient nullable context as well as an explicit property flag, and add a test that exercises this path.
…ext, and handle ambient NullableContextAttribute for netstandard2.0
Moha-sami
commented
Aug 13, 2026
Hi @DamianEdwards, thanks again for the sharp review! I've pushed an update addressing all three points:
All 279 unit tests across .NET 8, 9, and 10 are passing cleanly! |
DamianEdwards
left a comment
There was a problem hiding this comment.
All findings from the previous rounds are addressed. One cross-target correctness issue remains: the netstandard2.0 nullable-metadata fallback does not honor nullable flow annotations, so it can apply stricter validation than the .NET 8 asset. The inline comment includes a suggested implementation and coverage approach.
| var nullabilityInfo = nullabilityContext.Create(property); | ||
| return nullabilityInfo.WriteState == NullabilityState.Nullable || nullabilityInfo.ReadState == NullabilityState.Nullable; | ||
| #else | ||
| var nullableAttr = property.GetCustomAttributes(false) |
There was a problem hiding this comment.
The netstandard2.0 path still differs from NullabilityInfoContext for nullable flow annotations. For example, [AllowNull] public required string Value has nullable write state, and [MaybeNull] has nullable read state; this fallback currently sees neither and synthesizes [Required], so the same model validates differently depending on which target asset is loaded.
Please mirror the modern path before falling back to NullableAttribute/NullableContextAttribute: detect System.Diagnostics.CodeAnalysis.AllowNullAttribute for the write side and System.Diagnostics.CodeAnalysis.MaybeNullAttribute for the read side, inspecting the property and the setter value/getter return metadata that NullabilityInfoContext considers. Comparing full type names keeps this compatible where those attribute types are not directly referenceable. If either side permits null, return true.
Please also cover both annotations through the fallback path. Since the current test project targets only .NET 8+, it always selects the NullabilityInfoContext branch; either exercise the netstandard2.0 asset from a consumer test or extract the metadata parser into target-independent code that can be tested directly.
…supporting AllowNull and MaybeNull flow attributes
Moha-sami
commented
Aug 14, 2026
Hi @DamianEdwards, thanks for catching that! I've pushed an update (
Added tests covering |
Summary
Fixes#76. Adds support for C# 11's
requiredmodifier (RequiredMemberAttribute) inTypeDetailsCache.csso properties marked withrequiredare automatically treated as required duringMiniValidator.TryValidate.Includes new unit tests covering
requiredproperty validation across .NET 8, 9, and 10.