From edfed86a3b2b42fc9c802217cd00a1e4566216df Mon Sep 17 00:00:00 2001 From: Maksym Koshovyi Date: Fri, 20 Aug 2021 00:40:48 +0300 Subject: [PATCH] [Group 1] Enable nullable annotations for `Microsoft.Extensions.Primitives` (#57395) * Annotate src * StringValues array is non-nullable * Anotate ref * Fix analizer error * Update Equals implementation * Result of IChangeToken.RegisterChangeCallback is nullable * Revert "Result of IChangeToken.RegisterChangeCallback is nullable" This reverts commit e005156a79b955310f04a799f703dd73c7cd8482. * Dont use using in ref * Commit to rerun CI * Revert "Commit to rerun CI" This reverts commit d7230db614995656839c3601ae898813dac62528. * StringValues can contain string?[] * Equals [NotNullWhen(true)] * text = null - retrun false * text = null - retrun false (tests) * Remove unneeded ?. * IChangeToken can be null * Update doc * NullTokenDisposeShouldNotThrow * _registeredCallbackProxy -> RegisteredCallbackProxy * StringValues can be created from string?[]? * Debug.Fail -> Assert.True(false) --- .../ref/Microsoft.Extensions.Primitives.cs | 111 +++++------ .../Microsoft.Extensions.Primitives.csproj | 1 + .../src/CancellationChangeToken.cs | 2 +- .../src/ChangeToken.cs | 26 +-- .../src/CompositeChangeToken.cs | 31 +-- .../src/IChangeToken.cs | 2 +- .../Microsoft.Extensions.Primitives.csproj | 1 + .../src/StringSegment.cs | 21 +- .../src/StringValues.cs | 185 +++++++++--------- .../src/ThrowHelper.cs | 6 + .../tests/ChangeTokenTest.cs | 7 +- .../tests/StringSegmentTest.cs | 13 +- 12 files changed, 208 insertions(+), 198 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.cs b/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.cs index e591020d7a680..d9ba35bb4daf6 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.cs @@ -11,12 +11,12 @@ public partial class CancellationChangeToken : Microsoft.Extensions.Primitives.I public CancellationChangeToken(System.Threading.CancellationToken cancellationToken) { } public bool ActiveChangeCallbacks { get { throw null; } } public bool HasChanged { get { throw null; } } - public System.IDisposable RegisterChangeCallback(System.Action callback, object state) { throw null; } + public System.IDisposable RegisterChangeCallback(System.Action callback, object? state) { throw null; } } public static partial class ChangeToken { - public static System.IDisposable OnChange(System.Func changeTokenProducer, System.Action changeTokenConsumer) { throw null; } - public static System.IDisposable OnChange(System.Func changeTokenProducer, System.Action changeTokenConsumer, TState state) { throw null; } + public static System.IDisposable OnChange(System.Func changeTokenProducer, System.Action changeTokenConsumer) { throw null; } + public static System.IDisposable OnChange(System.Func changeTokenProducer, System.Action changeTokenConsumer, TState state) { throw null; } } public partial class CompositeChangeToken : Microsoft.Extensions.Primitives.IChangeToken { @@ -24,7 +24,7 @@ public CompositeChangeToken(System.Collections.Generic.IReadOnlyList ChangeTokens { get { throw null; } } public bool HasChanged { get { throw null; } } - public System.IDisposable RegisterChangeCallback(System.Action callback, object state) { throw null; } + public System.IDisposable RegisterChangeCallback(System.Action callback, object? state) { throw null; } } public static partial class Extensions { @@ -34,7 +34,7 @@ public partial interface IChangeToken { bool ActiveChangeCallbacks { get; } bool HasChanged { get; } - System.IDisposable RegisterChangeCallback(System.Action callback, object state); + System.IDisposable RegisterChangeCallback(System.Action callback, object? state); } public readonly partial struct StringSegment : System.IEquatable, System.IEquatable { @@ -44,11 +44,12 @@ public partial interface IChangeToken public StringSegment(string buffer) { throw null; } public StringSegment(string buffer, int offset, int length) { throw null; } public string Buffer { get { throw null; } } + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, nameof(Buffer))] public bool HasValue { get { throw null; } } public char this[int index] { get { throw null; } } public int Length { get { throw null; } } public int Offset { get { throw null; } } - public string Value { get { throw null; } } + public string? Value { get { throw null; } } public System.ReadOnlyMemory AsMemory() { throw null; } public System.ReadOnlySpan AsSpan() { throw null; } public System.ReadOnlySpan AsSpan(int start) { throw null; } @@ -58,9 +59,9 @@ public partial interface IChangeToken public bool Equals(Microsoft.Extensions.Primitives.StringSegment other) { throw null; } public static bool Equals(Microsoft.Extensions.Primitives.StringSegment a, Microsoft.Extensions.Primitives.StringSegment b, System.StringComparison comparisonType) { throw null; } public bool Equals(Microsoft.Extensions.Primitives.StringSegment other, System.StringComparison comparisonType) { throw null; } - public override bool Equals(object obj) { throw null; } - public bool Equals(string text) { throw null; } - public bool Equals(string text, System.StringComparison comparisonType) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? text) { throw null; } + public bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? text, System.StringComparison comparisonType) { throw null; } public override int GetHashCode() { throw null; } public int IndexOf(char c) { throw null; } public int IndexOf(char c, int start) { throw null; } @@ -71,9 +72,9 @@ public partial interface IChangeToken public static bool IsNullOrEmpty(Microsoft.Extensions.Primitives.StringSegment value) { throw null; } public int LastIndexOf(char value) { throw null; } public static bool operator ==(Microsoft.Extensions.Primitives.StringSegment left, Microsoft.Extensions.Primitives.StringSegment right) { throw null; } - public static implicit operator System.ReadOnlyMemory (Microsoft.Extensions.Primitives.StringSegment segment) { throw null; } - public static implicit operator System.ReadOnlySpan (Microsoft.Extensions.Primitives.StringSegment segment) { throw null; } - public static implicit operator Microsoft.Extensions.Primitives.StringSegment (string value) { throw null; } + public static implicit operator System.ReadOnlyMemory(Microsoft.Extensions.Primitives.StringSegment segment) { throw null; } + public static implicit operator System.ReadOnlySpan(Microsoft.Extensions.Primitives.StringSegment segment) { throw null; } + public static implicit operator Microsoft.Extensions.Primitives.StringSegment(string value) { throw null; } public static bool operator !=(Microsoft.Extensions.Primitives.StringSegment left, Microsoft.Extensions.Primitives.StringSegment right) { throw null; } public Microsoft.Extensions.Primitives.StringTokenizer Split(char[] chars) { throw null; } public bool StartsWith(string text, System.StringComparison comparisonType) { throw null; } @@ -116,68 +117,68 @@ public void Dispose() { } public void Reset() { } } } - public readonly partial struct StringValues : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IReadOnlyList, System.Collections.IEnumerable, System.IEquatable, System.IEquatable, System.IEquatable + public readonly partial struct StringValues : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IReadOnlyList, System.Collections.IEnumerable, System.IEquatable, System.IEquatable, System.IEquatable { private readonly object _dummy; private readonly int _dummyPrimitive; public static readonly Microsoft.Extensions.Primitives.StringValues Empty; - public StringValues(string value) { throw null; } - public StringValues(string[] values) { throw null; } + public StringValues(string? value) { throw null; } + public StringValues(string?[]? values) { throw null; } public int Count { get { throw null; } } - public string this[int index] { get { throw null; } } - bool System.Collections.Generic.ICollection.IsReadOnly { get { throw null; } } - string System.Collections.Generic.IList.this[int index] { get { throw null; } set { } } + public string? this[int index] { get { throw null; } } + bool System.Collections.Generic.ICollection.IsReadOnly { get { throw null; } } + string? System.Collections.Generic.IList.this[int index] { get { throw null; } set { } } public static Microsoft.Extensions.Primitives.StringValues Concat(Microsoft.Extensions.Primitives.StringValues values1, Microsoft.Extensions.Primitives.StringValues values2) { throw null; } - public static Microsoft.Extensions.Primitives.StringValues Concat(in Microsoft.Extensions.Primitives.StringValues values, string value) { throw null; } - public static Microsoft.Extensions.Primitives.StringValues Concat(string value, in Microsoft.Extensions.Primitives.StringValues values) { throw null; } + public static Microsoft.Extensions.Primitives.StringValues Concat(in Microsoft.Extensions.Primitives.StringValues values, string? value) { throw null; } + public static Microsoft.Extensions.Primitives.StringValues Concat(string? value, in Microsoft.Extensions.Primitives.StringValues values) { throw null; } public bool Equals(Microsoft.Extensions.Primitives.StringValues other) { throw null; } public static bool Equals(Microsoft.Extensions.Primitives.StringValues left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } - public static bool Equals(Microsoft.Extensions.Primitives.StringValues left, string right) { throw null; } - public static bool Equals(Microsoft.Extensions.Primitives.StringValues left, string[] right) { throw null; } - public override bool Equals(object obj) { throw null; } - public bool Equals(string other) { throw null; } - public static bool Equals(string left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } - public bool Equals(string[] other) { throw null; } - public static bool Equals(string[] left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } + public static bool Equals(Microsoft.Extensions.Primitives.StringValues left, string? right) { throw null; } + public static bool Equals(Microsoft.Extensions.Primitives.StringValues left, string?[]? right) { throw null; } + public override bool Equals(object? obj) { throw null; } + public bool Equals(string? other) { throw null; } + public static bool Equals(string? left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } + public bool Equals(string?[]? other) { throw null; } + public static bool Equals(string?[]? left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } public Microsoft.Extensions.Primitives.StringValues.Enumerator GetEnumerator() { throw null; } public override int GetHashCode() { throw null; } public static bool IsNullOrEmpty(Microsoft.Extensions.Primitives.StringValues value) { throw null; } public static bool operator ==(Microsoft.Extensions.Primitives.StringValues left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } - public static bool operator ==(Microsoft.Extensions.Primitives.StringValues left, object right) { throw null; } - public static bool operator ==(Microsoft.Extensions.Primitives.StringValues left, string right) { throw null; } - public static bool operator ==(Microsoft.Extensions.Primitives.StringValues left, string[] right) { throw null; } - public static bool operator ==(object left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } - public static bool operator ==(string left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } - public static bool operator ==(string[] left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } - public static implicit operator string (Microsoft.Extensions.Primitives.StringValues values) { throw null; } - public static implicit operator string[] (Microsoft.Extensions.Primitives.StringValues value) { throw null; } - public static implicit operator Microsoft.Extensions.Primitives.StringValues (string value) { throw null; } - public static implicit operator Microsoft.Extensions.Primitives.StringValues (string[] values) { throw null; } + public static bool operator ==(Microsoft.Extensions.Primitives.StringValues left, object? right) { throw null; } + public static bool operator ==(Microsoft.Extensions.Primitives.StringValues left, string? right) { throw null; } + public static bool operator ==(Microsoft.Extensions.Primitives.StringValues left, string?[]? right) { throw null; } + public static bool operator ==(object? left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } + public static bool operator ==(string? left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } + public static bool operator ==(string?[]? left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } + public static implicit operator string?(Microsoft.Extensions.Primitives.StringValues values) { throw null; } + public static implicit operator string?[]?(Microsoft.Extensions.Primitives.StringValues value) { throw null; } + public static implicit operator Microsoft.Extensions.Primitives.StringValues(string? value) { throw null; } + public static implicit operator Microsoft.Extensions.Primitives.StringValues(string?[]? values) { throw null; } public static bool operator !=(Microsoft.Extensions.Primitives.StringValues left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } - public static bool operator !=(Microsoft.Extensions.Primitives.StringValues left, object right) { throw null; } - public static bool operator !=(Microsoft.Extensions.Primitives.StringValues left, string right) { throw null; } - public static bool operator !=(Microsoft.Extensions.Primitives.StringValues left, string[] right) { throw null; } - public static bool operator !=(object left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } - public static bool operator !=(string left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } - public static bool operator !=(string[] left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } - void System.Collections.Generic.ICollection.Add(string item) { } - void System.Collections.Generic.ICollection.Clear() { } - bool System.Collections.Generic.ICollection.Contains(string item) { throw null; } - void System.Collections.Generic.ICollection.CopyTo(string[] array, int arrayIndex) { } - bool System.Collections.Generic.ICollection.Remove(string item) { throw null; } - System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; } - int System.Collections.Generic.IList.IndexOf(string item) { throw null; } - void System.Collections.Generic.IList.Insert(int index, string item) { } - void System.Collections.Generic.IList.RemoveAt(int index) { } + public static bool operator !=(Microsoft.Extensions.Primitives.StringValues left, object? right) { throw null; } + public static bool operator !=(Microsoft.Extensions.Primitives.StringValues left, string? right) { throw null; } + public static bool operator !=(Microsoft.Extensions.Primitives.StringValues left, string?[]? right) { throw null; } + public static bool operator !=(object? left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } + public static bool operator !=(string? left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } + public static bool operator !=(string?[]? left, Microsoft.Extensions.Primitives.StringValues right) { throw null; } + void System.Collections.Generic.ICollection.Add(string? item) { } + void System.Collections.Generic.ICollection.Clear() { } + bool System.Collections.Generic.ICollection.Contains(string? item) { throw null; } + void System.Collections.Generic.ICollection.CopyTo(string?[] array, int arrayIndex) { } + bool System.Collections.Generic.ICollection.Remove(string? item) { throw null; } + System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; } + int System.Collections.Generic.IList.IndexOf(string? item) { throw null; } + void System.Collections.Generic.IList.Insert(int index, string? item) { } + void System.Collections.Generic.IList.RemoveAt(int index) { } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - public string[] ToArray() { throw null; } + public string?[] ToArray() { throw null; } public override string ToString() { throw null; } - public partial struct Enumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable + public partial struct Enumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable { private object _dummy; private int _dummyPrimitive; public Enumerator(ref Microsoft.Extensions.Primitives.StringValues values) { throw null; } - public string Current { get { throw null; } } + public string? Current { get { throw null; } } object System.Collections.IEnumerator.Current { get { throw null; } } public void Dispose() { } public bool MoveNext() { throw null; } diff --git a/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.csproj b/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.csproj index e6f45298de118..3c011149f7644 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.csproj +++ b/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.csproj @@ -1,6 +1,7 @@ $(NetCoreAppCurrent);netcoreapp3.1;netstandard2.0;net461 + enable diff --git a/src/libraries/Microsoft.Extensions.Primitives/src/CancellationChangeToken.cs b/src/libraries/Microsoft.Extensions.Primitives/src/CancellationChangeToken.cs index f66878f9e8eb9..758d287ad30fe 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/src/CancellationChangeToken.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/src/CancellationChangeToken.cs @@ -29,7 +29,7 @@ public CancellationChangeToken(CancellationToken cancellationToken) private CancellationToken Token { get; } /// - public IDisposable RegisterChangeCallback(Action callback, object state) + public IDisposable RegisterChangeCallback(Action callback, object? state) { #if NETCOREAPP || NETSTANDARD2_1 try diff --git a/src/libraries/Microsoft.Extensions.Primitives/src/ChangeToken.cs b/src/libraries/Microsoft.Extensions.Primitives/src/ChangeToken.cs index b5df90f760537..d0ddc8b8143f3 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/src/ChangeToken.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/src/ChangeToken.cs @@ -18,7 +18,7 @@ public static class ChangeToken /// Produces the change token. /// Action called when the token changes. /// - public static IDisposable OnChange(Func changeTokenProducer, Action changeTokenConsumer) + public static IDisposable OnChange(Func changeTokenProducer, Action changeTokenConsumer) { if (changeTokenProducer == null) { @@ -39,7 +39,7 @@ public static IDisposable OnChange(Func changeTokenProducer, Actio /// Action called when the token changes. /// state for the consumer. /// - public static IDisposable OnChange(Func changeTokenProducer, Action changeTokenConsumer, TState state) + public static IDisposable OnChange(Func changeTokenProducer, Action changeTokenConsumer, TState state) { if (changeTokenProducer == null) { @@ -55,20 +55,20 @@ public static IDisposable OnChange(Func changeTokenProduce private sealed class ChangeTokenRegistration : IDisposable { - private readonly Func _changeTokenProducer; + private readonly Func _changeTokenProducer; private readonly Action _changeTokenConsumer; private readonly TState _state; - private IDisposable _disposable; + private IDisposable? _disposable; private static readonly NoopDisposable _disposedSentinel = new NoopDisposable(); - public ChangeTokenRegistration(Func changeTokenProducer, Action changeTokenConsumer, TState state) + public ChangeTokenRegistration(Func changeTokenProducer, Action changeTokenConsumer, TState state) { _changeTokenProducer = changeTokenProducer; _changeTokenConsumer = changeTokenConsumer; _state = state; - IChangeToken token = changeTokenProducer(); + IChangeToken? token = changeTokenProducer(); RegisterChangeTokenCallback(token); } @@ -80,7 +80,7 @@ private void OnChangeTokenFired() // // If the token changes after we take the token, then we'll process the update immediately upon // registering the callback. - IChangeToken token = _changeTokenProducer(); + IChangeToken? token = _changeTokenProducer(); try { @@ -93,14 +93,14 @@ private void OnChangeTokenFired() } } - private void RegisterChangeTokenCallback(IChangeToken token) + private void RegisterChangeTokenCallback(IChangeToken? token) { if (token is null) { return; } - IDisposable registraton = token.RegisterChangeCallback(s => ((ChangeTokenRegistration)s).OnChangeTokenFired(), this); + IDisposable registraton = token.RegisterChangeCallback(s => ((ChangeTokenRegistration?)s)!.OnChangeTokenFired(), this); SetDisposable(registraton); } @@ -110,7 +110,7 @@ private void SetDisposable(IDisposable disposable) // We don't want to transition from _disposedSentinel => anything since it's terminal // but we want to allow going from previously assigned disposable, to another // disposable. - IDisposable current = Volatile.Read(ref _disposable); + IDisposable? current = Volatile.Read(ref _disposable); // If Dispose was called, then immediately dispose the disposable if (current == _disposedSentinel) @@ -120,7 +120,7 @@ private void SetDisposable(IDisposable disposable) } // Otherwise, try to update the disposable - IDisposable previous = Interlocked.CompareExchange(ref _disposable, disposable, current); + IDisposable? previous = Interlocked.CompareExchange(ref _disposable, disposable, current); if (previous == _disposedSentinel) { @@ -129,7 +129,7 @@ private void SetDisposable(IDisposable disposable) } else if (previous == current) { - // We successfuly assigned the _disposable field to disposable + // We successfully assigned the _disposable field to disposable } else { @@ -142,7 +142,7 @@ public void Dispose() { // If the previous value is disposable then dispose it, otherwise, // now we've set the disposed sentinel - Interlocked.Exchange(ref _disposable, _disposedSentinel).Dispose(); + Interlocked.Exchange(ref _disposable, _disposedSentinel)?.Dispose(); } private sealed class NoopDisposable : IDisposable diff --git a/src/libraries/Microsoft.Extensions.Primitives/src/CompositeChangeToken.cs b/src/libraries/Microsoft.Extensions.Primitives/src/CompositeChangeToken.cs index 6f20b657a5bfe..7df86773f7d72 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/src/CompositeChangeToken.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/src/CompositeChangeToken.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Threading; namespace Microsoft.Extensions.Primitives @@ -13,11 +14,14 @@ namespace Microsoft.Extensions.Primitives /// public class CompositeChangeToken : IChangeToken { - private static readonly Action _onChangeDelegate = OnChange; - private readonly object _callbackLock = new object(); - private CancellationTokenSource _cancellationTokenSource; - private bool _registeredCallbackProxy; - private List _disposables; + private static readonly Action _onChangeDelegate = OnChange; + private readonly object _callbackLock = new(); + private CancellationTokenSource? _cancellationTokenSource; + private List? _disposables; + + [MemberNotNullWhen(true, nameof(_cancellationTokenSource))] + [MemberNotNullWhen(true, nameof(_disposables))] + private bool RegisteredCallbackProxy { get; set; } /// /// Creates a new instance of . @@ -42,7 +46,7 @@ public CompositeChangeToken(IReadOnlyList changeTokens) public IReadOnlyList ChangeTokens { get; } /// - public IDisposable RegisterChangeCallback(Action callback, object state) + public IDisposable RegisterChangeCallback(Action callback, object? state) { EnsureCallbacksInitialized(); return _cancellationTokenSource.Token.Register(callback, state); @@ -74,16 +78,18 @@ public bool HasChanged /// public bool ActiveChangeCallbacks { get; } + [MemberNotNull(nameof(_cancellationTokenSource))] + [MemberNotNull(nameof(_disposables))] private void EnsureCallbacksInitialized() { - if (_registeredCallbackProxy) + if (RegisteredCallbackProxy) { return; } lock (_callbackLock) { - if (_registeredCallbackProxy) + if (RegisteredCallbackProxy) { return; } @@ -98,12 +104,14 @@ private void EnsureCallbacksInitialized() _disposables.Add(disposable); } } - _registeredCallbackProxy = true; + RegisteredCallbackProxy = true; } } - private static void OnChange(object state) + private static void OnChange(object? state) { + Debug.Assert(state != null); + var compositeChangeTokenState = (CompositeChangeToken)state; if (compositeChangeTokenState._cancellationTokenSource == null) { @@ -121,13 +129,12 @@ private static void OnChange(object state) } } - List disposables = compositeChangeTokenState._disposables; + List? disposables = compositeChangeTokenState._disposables; Debug.Assert(disposables != null); for (int i = 0; i < disposables.Count; i++) { disposables[i].Dispose(); } - } } } diff --git a/src/libraries/Microsoft.Extensions.Primitives/src/IChangeToken.cs b/src/libraries/Microsoft.Extensions.Primitives/src/IChangeToken.cs index b1c336d30bdc6..f34122c21f723 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/src/IChangeToken.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/src/IChangeToken.cs @@ -28,6 +28,6 @@ public interface IChangeToken /// The to invoke. /// State to be passed into the callback. /// An that is used to unregister the callback. - IDisposable RegisterChangeCallback(Action callback, object state); + IDisposable RegisterChangeCallback(Action callback, object? state); } } diff --git a/src/libraries/Microsoft.Extensions.Primitives/src/Microsoft.Extensions.Primitives.csproj b/src/libraries/Microsoft.Extensions.Primitives/src/Microsoft.Extensions.Primitives.csproj index 5f2911b00ecce..369c1d06dd1ce 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/src/Microsoft.Extensions.Primitives.csproj +++ b/src/libraries/Microsoft.Extensions.Primitives/src/Microsoft.Extensions.Primitives.csproj @@ -1,6 +1,7 @@ $(NetCoreAppCurrent);netcoreapp3.1;netstandard2.0;net461 + enable true true diff --git a/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs b/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs index c5a70270e5677..ef7c4df3909e7 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace Microsoft.Extensions.Primitives @@ -76,11 +77,12 @@ public StringSegment(string buffer, int offset, int length) /// /// Gets the value of this segment as a . /// - public string Value => HasValue ? Buffer.Substring(Offset, Length) : null; + public string? Value => HasValue ? Buffer.Substring(Offset, Length) : null; /// /// Gets whether this contains a valid value. /// + [MemberNotNullWhen(true, nameof(Buffer))] public bool HasValue => Buffer != null; /// @@ -188,7 +190,7 @@ public static int Compare(StringSegment a, StringSegment b, StringComparison com /// /// An object to compare with this object. /// if the current object is equal to the other parameter; otherwise, . - public override bool Equals(object obj) + public override bool Equals([NotNullWhen(true)] object? obj) { return obj is StringSegment segment && Equals(segment); } @@ -239,7 +241,7 @@ public static bool Equals(StringSegment a, StringSegment b, StringComparison com /// /// The to compare with the current . /// if the specified is equal to the current ; otherwise, . - public bool Equals(string text) + public bool Equals([NotNullWhen(true)] string? text) { return Equals(text, StringComparison.Ordinal); } @@ -250,15 +252,12 @@ public bool Equals(string text) /// The to compare with the current . /// One of the enumeration values that specifies the rules to use in the comparison. /// if the specified is equal to the current ; otherwise, . - /// - /// is . - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(string text, StringComparison comparisonType) + public bool Equals([NotNullWhen(true)] string? text, StringComparison comparisonType) { if (text == null) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text); + return false; } if (!HasValue) @@ -681,7 +680,8 @@ private static void CheckStringComparison(StringComparison comparisonType) // Methods that do no return (i.e. throw) are not inlined // https://github.com/dotnet/coreclr/pull/6103 - private static void ThrowInvalidArguments(string buffer, int offset, int length) + [DoesNotReturn] + private static void ThrowInvalidArguments(string? buffer, int offset, int length) { // Only have single throw in method so is marked as "does not return" and isn't inlined to caller throw GetInvalidArgumentsException(); @@ -707,6 +707,7 @@ Exception GetInvalidArgumentsException() } } + [DoesNotReturn] private void ThrowInvalidArguments(int offset, int length, ExceptionArgument offsetOrStart) { throw GetInvalidArgumentsException(HasValue); @@ -733,7 +734,7 @@ Exception GetInvalidArgumentsException(bool hasValue) } /// - bool IEquatable.Equals(string other) + bool IEquatable.Equals(string? other) { // Explicit interface implementation for IEquatable because // the interface's Equals method allows null strings, which we return diff --git a/src/libraries/Microsoft.Extensions.Primitives/src/StringValues.cs b/src/libraries/Microsoft.Extensions.Primitives/src/StringValues.cs index 7a7ec59457a93..029b6922965fd 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/src/StringValues.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/src/StringValues.cs @@ -14,7 +14,7 @@ namespace Microsoft.Extensions.Primitives /// /// Represents zero/null, one, or many strings in an efficient way. /// - public readonly struct StringValues : IList, IReadOnlyList, IEquatable, IEquatable, IEquatable + public readonly struct StringValues : IList, IReadOnlyList, IEquatable, IEquatable, IEquatable { /// /// A readonly instance of the struct whose value is an empty string array. @@ -24,13 +24,13 @@ namespace Microsoft.Extensions.Primitives /// public static readonly StringValues Empty = new StringValues(Array.Empty()); - private readonly object _values; + private readonly object? _values; /// /// Initializes a new instance of the structure using the specified string. /// /// A string value or null. - public StringValues(string value) + public StringValues(string? value) { _values = value; } @@ -39,7 +39,7 @@ public StringValues(string value) /// Initializes a new instance of the structure using the specified array of strings. /// /// A string array. - public StringValues(string[] values) + public StringValues(string?[]? values) { _values = values; } @@ -48,7 +48,7 @@ public StringValues(string[] values) /// Defines an implicit conversion of a given string to a . /// /// A string to implicitly convert. - public static implicit operator StringValues(string value) + public static implicit operator StringValues(string? value) { return new StringValues(value); } @@ -57,7 +57,7 @@ public static implicit operator StringValues(string value) /// Defines an implicit conversion of a given string array to a . /// /// A string array to implicitly convert. - public static implicit operator StringValues(string[] values) + public static implicit operator StringValues(string?[]? values) { return new StringValues(values); } @@ -69,7 +69,7 @@ public static implicit operator StringValues(string[] values) /// Returns null where has been initialized from an empty string array or is . /// /// A to implicitly convert. - public static implicit operator string (StringValues values) + public static implicit operator string? (StringValues values) { return values.GetStringValue(); } @@ -78,7 +78,7 @@ public static implicit operator string (StringValues values) /// Defines an implicit conversion of a given to a string array. /// /// A to implicitly convert. - public static implicit operator string[] (StringValues value) + public static implicit operator string?[]? (StringValues value) { return value.GetArrayValue(); } @@ -92,7 +92,7 @@ public int Count get { // Take local copy of _values so type checks remain valid even if the StringValues is overwritten in memory - object value = _values; + object? value = _values; if (value is null) { return 0; @@ -104,15 +104,12 @@ public int Count else { // Not string, not null, can only be string[] - return Unsafe.As(value).Length; + return Unsafe.As(value).Length; } } } - bool ICollection.IsReadOnly - { - get { return true; } - } + bool ICollection.IsReadOnly => true; /// /// Gets the at index. @@ -120,10 +117,10 @@ bool ICollection.IsReadOnly /// The string at the specified index. /// The zero-based index of the element to get. /// Set operations are not supported on readonly . - string IList.this[int index] + string? IList.this[int index] { - get { return this[index]; } - set { throw new NotSupportedException(); } + get => this[index]; + set => throw new NotSupportedException(); } /// @@ -131,13 +128,13 @@ string IList.this[int index] /// /// The string at the specified index. /// The zero-based index of the element to get. - public string this[int index] + public string? this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { // Take local copy of _values so type checks remain valid even if the StringValues is overwritten in memory - object value = _values; + object? value = _values; if (value is string str) { if (index == 0) @@ -148,7 +145,7 @@ public string this[int index] else if (value != null) { // Not string, not null, can only be string[] - return Unsafe.As(value)[index]; // may throw + return Unsafe.As(value)[index]; // may throw } return OutOfBounds(); // throws @@ -170,10 +167,10 @@ public override string ToString() return GetStringValue() ?? string.Empty; } - private string GetStringValue() + private string? GetStringValue() { // Take local copy of _values so type checks remain valid even if the StringValues is overwritten in memory - object value = _values; + object? value = _values; if (value is string s) { return s; @@ -183,7 +180,7 @@ private string GetStringValue() return GetStringValueFromArray(value); } - static string GetStringValueFromArray(object value) + static string? GetStringValueFromArray(object? value) { if (value is null) { @@ -192,28 +189,28 @@ static string GetStringValueFromArray(object value) Debug.Assert(value is string[]); // value is not null or string, array, can only be string[] - string[] values = Unsafe.As(value); - switch (values.Length) + string?[] values = Unsafe.As(value); + return values.Length switch { - case 0: return null; - case 1: return values[0]; - default: return GetJoinedStringValueFromArray(values); - } + 0 => null, + 1 => values[0], + _ => GetJoinedStringValueFromArray(values), + }; } - static string GetJoinedStringValueFromArray(string[] values) + static string GetJoinedStringValueFromArray(string?[] values) { // Calculate final length int length = 0; for (int i = 0; i < values.Length; i++) { - string value = values[i]; + string? value = values[i]; // Skip null and empty values if (value != null && value.Length > 0) { if (length > 0) { - // Add seperator + // Add separator length++; } @@ -227,12 +224,12 @@ static string GetJoinedStringValueFromArray(string[] values) // Skip null and empty values for (int i = 0; i < strings.Length; i++) { - string value = strings[i]; + string? value = strings[i]; if (value != null && value.Length > 0) { if (offset > 0) { - // Add seperator + // Add separator span[offset] = ','; offset++; } @@ -248,12 +245,12 @@ static string GetJoinedStringValueFromArray(string[] values) // Skip null and empty values for (int i = 0; i < values.Length; i++) { - string value = values[i]; + string? value = values[i]; if (value != null && value.Length > 0) { if (hasAdded) { - // Add seperator + // Add separator sb.Append(','); } @@ -275,15 +272,15 @@ static string GetJoinedStringValueFromArray(string[] values) /// If the contains a single string internally, it is copied to a new array. /// If the contains an array internally it returns that array instance. /// - public string[] ToArray() + public string?[] ToArray() { return GetArrayValue() ?? Array.Empty(); } - private string[] GetArrayValue() + private string?[]? GetArrayValue() { // Take local copy of _values so type checks remain valid even if the StringValues is overwritten in memory - object value = _values; + object? value = _values; if (value is string[] values) { return values; @@ -304,15 +301,15 @@ private string[] GetArrayValue() /// /// The string to locate in the . /// the zero-based index of the first occurrence of within the , if found; otherwise, -1. - int IList.IndexOf(string item) + int IList.IndexOf(string? item) { return IndexOf(item); } - private int IndexOf(string item) + private int IndexOf(string? item) { // Take local copy of _values so type checks remain valid even if the StringValues is overwritten in memory - object value = _values; + object? value = _values; if (value is string[] values) { for (int i = 0; i < values.Length; i++) @@ -337,7 +334,7 @@ private int IndexOf(string item) /// Determines whether a string is in the . /// The to locate in the . /// true if item is found in the ; otherwise, false. - bool ICollection.Contains(string item) + bool ICollection.Contains(string? item) { return IndexOf(item) >= 0; } @@ -350,15 +347,15 @@ bool ICollection.Contains(string item) /// array is null. /// arrayIndex is less than 0. /// The number of elements in the source is greater than the available space from arrayIndex to the end of the destination array. - void ICollection.CopyTo(string[] array, int arrayIndex) + void ICollection.CopyTo(string?[] array, int arrayIndex) { CopyTo(array, arrayIndex); } - private void CopyTo(string[] array, int arrayIndex) + private void CopyTo(string?[] array, int arrayIndex) { // Take local copy of _values so type checks remain valid even if the StringValues is overwritten in memory - object value = _values; + object? value = _values; if (value is string[] values) { Array.Copy(values, 0, array, arrayIndex, values.Length); @@ -386,15 +383,15 @@ private void CopyTo(string[] array, int arrayIndex) } } - void ICollection.Add(string item) => throw new NotSupportedException(); + void ICollection.Add(string? item) => throw new NotSupportedException(); - void IList.Insert(int index, string item) => throw new NotSupportedException(); + void IList.Insert(int index, string? item) => throw new NotSupportedException(); - bool ICollection.Remove(string item) => throw new NotSupportedException(); + bool ICollection.Remove(string? item) => throw new NotSupportedException(); - void IList.RemoveAt(int index) => throw new NotSupportedException(); + void IList.RemoveAt(int index) => throw new NotSupportedException(); - void ICollection.Clear() => throw new NotSupportedException(); + void ICollection.Clear() => throw new NotSupportedException(); /// Retrieves an object that can iterate through the individual strings in this . /// An enumerator that can be used to iterate through the . @@ -404,7 +401,7 @@ public Enumerator GetEnumerator() } /// - IEnumerator IEnumerable.GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } @@ -422,19 +419,19 @@ IEnumerator IEnumerable.GetEnumerator() /// true if value contains a single null or empty string or an empty array; otherwise, false. public static bool IsNullOrEmpty(StringValues value) { - object data = value._values; + object? data = value._values; if (data is null) { return true; } if (data is string[] values) { - switch (values.Length) + return values.Length switch { - case 0: return true; - case 1: return string.IsNullOrEmpty(values[0]); - default: return false; - } + 0 => true, + 1 => string.IsNullOrEmpty(values[0]), + _ => false, + }; } else { @@ -476,7 +473,7 @@ public static StringValues Concat(StringValues values1, StringValues values2) /// The to concatenate. /// The to concatenate. /// The concatenation of and . - public static StringValues Concat(in StringValues values, string value) + public static StringValues Concat(in StringValues values, string? value) { if (value == null) { @@ -501,7 +498,7 @@ public static StringValues Concat(in StringValues values, string value) /// The to concatenate. /// The to concatenate. /// The concatenation of and . - public static StringValues Concat(string value, in StringValues values) + public static StringValues Concat(string? value, in StringValues values) { if (value == null) { @@ -581,7 +578,7 @@ public static bool Equals(StringValues left, StringValues right) /// The to compare. /// The to compare. /// true if the value of is the same as the value of ; otherwise, false. If is null, the method returns false. - public static bool Equals(string left, StringValues right) => Equals(new StringValues(left), right); + public static bool Equals(string? left, StringValues right) => Equals(new StringValues(left), right); /// /// Determines whether the specified and objects have the same values. @@ -589,14 +586,14 @@ public static bool Equals(StringValues left, StringValues right) /// The to compare. /// The to compare. /// true if the value of is the same as the value of ; otherwise, false. If is null, the method returns false. - public static bool Equals(StringValues left, string right) => Equals(left, new StringValues(right)); + public static bool Equals(StringValues left, string? right) => Equals(left, new StringValues(right)); /// /// Determines whether this instance and a specified , have the same value. /// /// The to compare to this instance. /// true if the value of is the same as this instance; otherwise, false. If is null, returns false. - public bool Equals(string other) => Equals(this, new StringValues(other)); + public bool Equals(string? other) => Equals(this, new StringValues(other)); /// /// Determines whether the specified string array and objects have the same values. @@ -604,7 +601,7 @@ public static bool Equals(StringValues left, StringValues right) /// The string array to compare. /// The to compare. /// true if the value of is the same as the value of ; otherwise, false. - public static bool Equals(string[] left, StringValues right) => Equals(new StringValues(left), right); + public static bool Equals(string?[]? left, StringValues right) => Equals(new StringValues(left), right); /// /// Determines whether the specified and string array objects have the same values. @@ -612,17 +609,17 @@ public static bool Equals(StringValues left, StringValues right) /// The to compare. /// The string array to compare. /// true if the value of is the same as the value of ; otherwise, false. - public static bool Equals(StringValues left, string[] right) => Equals(left, new StringValues(right)); + public static bool Equals(StringValues left, string?[]? right) => Equals(left, new StringValues(right)); /// /// Determines whether this instance and a specified string array have the same values. /// /// The string array to compare to this instance. /// true if the value of is the same as this instance; otherwise, false. - public bool Equals(string[] other) => Equals(this, new StringValues(other)); + public bool Equals(string?[]? other) => Equals(this, new StringValues(other)); /// - public static bool operator ==(StringValues left, string right) => Equals(left, new StringValues(right)); + public static bool operator ==(StringValues left, string? right) => Equals(left, new StringValues(right)); /// /// Determines whether the specified and objects have different values. @@ -630,10 +627,10 @@ public static bool Equals(StringValues left, StringValues right) /// The to compare. /// The to compare. /// true if the value of is different to the value of ; otherwise, false. - public static bool operator !=(StringValues left, string right) => !Equals(left, new StringValues(right)); + public static bool operator !=(StringValues left, string? right) => !Equals(left, new StringValues(right)); /// - public static bool operator ==(string left, StringValues right) => Equals(new StringValues(left), right); + public static bool operator ==(string? left, StringValues right) => Equals(new StringValues(left), right); /// /// Determines whether the specified and objects have different values. @@ -644,7 +641,7 @@ public static bool Equals(StringValues left, StringValues right) public static bool operator !=(string left, StringValues right) => !Equals(new StringValues(left), right); /// - public static bool operator ==(StringValues left, string[] right) => Equals(left, new StringValues(right)); + public static bool operator ==(StringValues left, string?[]? right) => Equals(left, new StringValues(right)); /// /// Determines whether the specified and string array have different values. @@ -652,10 +649,10 @@ public static bool Equals(StringValues left, StringValues right) /// The to compare. /// The string array to compare. /// true if the value of is different to the value of ; otherwise, false. - public static bool operator !=(StringValues left, string[] right) => !Equals(left, new StringValues(right)); + public static bool operator !=(StringValues left, string?[]? right) => !Equals(left, new StringValues(right)); /// - public static bool operator ==(string[] left, StringValues right) => Equals(new StringValues(left), right); + public static bool operator ==(string?[]? left, StringValues right) => Equals(new StringValues(left), right); /// /// Determines whether the specified string array and have different values. @@ -663,7 +660,7 @@ public static bool Equals(StringValues left, StringValues right) /// The string array to compare. /// The to compare. /// true if the value of is different to the value of ; otherwise, false. - public static bool operator !=(string[] left, StringValues right) => !Equals(new StringValues(left), right); + public static bool operator !=(string?[]? left, StringValues right) => !Equals(new StringValues(left), right); /// /// Determines whether the specified and , which must be a @@ -672,7 +669,7 @@ public static bool Equals(StringValues left, StringValues right) /// The to compare. /// The to compare. /// true if the object is equal to the ; otherwise, false. - public static bool operator ==(StringValues left, object right) => left.Equals(right); + public static bool operator ==(StringValues left, object? right) => left.Equals(right); /// /// Determines whether the specified and , which must be a @@ -681,7 +678,7 @@ public static bool Equals(StringValues left, StringValues right) /// The to compare. /// The to compare. /// true if the object is equal to the ; otherwise, false. - public static bool operator !=(StringValues left, object right) => !left.Equals(right); + public static bool operator !=(StringValues left, object? right) => !left.Equals(right); /// /// Determines whether the specified , which must be a @@ -690,7 +687,7 @@ public static bool Equals(StringValues left, StringValues right) /// The to compare. /// The to compare. /// true if the object is equal to the ; otherwise, false. - public static bool operator ==(object left, StringValues right) => right.Equals(left); + public static bool operator ==(object? left, StringValues right) => right.Equals(left); /// /// Determines whether the specified and object have the same values. @@ -698,33 +695,33 @@ public static bool Equals(StringValues left, StringValues right) /// The to compare. /// The to compare. /// true if the object is equal to the ; otherwise, false. - public static bool operator !=(object left, StringValues right) => !right.Equals(left); + public static bool operator !=(object? left, StringValues right) => !right.Equals(left); /// /// Determines whether this instance and a specified object have the same value. /// /// An object to compare with this object. /// true if the current object is equal to ; otherwise, false. - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj == null) { return Equals(this, StringValues.Empty); } - if (obj is string) + if (obj is string str) { - return Equals(this, (string)obj); + return Equals(this, str); } - if (obj is string[]) + if (obj is string[] array) { - return Equals(this, (string[])obj); + return Equals(this, array); } - if (obj is StringValues) + if (obj is StringValues stringValues) { - return Equals(this, (StringValues)obj); + return Equals(this, stringValues); } return false; @@ -733,7 +730,7 @@ public override bool Equals(object obj) /// public override int GetHashCode() { - object value = _values; + object? value = _values; if (value is string[] values) { if (Count == 1) @@ -756,13 +753,13 @@ public override int GetHashCode() /// /// Enumerates the string values of a . /// - public struct Enumerator : IEnumerator + public struct Enumerator : IEnumerator { - private readonly string[] _values; - private string _current; + private readonly string?[]? _values; private int _index; + private string? _current; - internal Enumerator(object value) + internal Enumerator(object? value) { if (value is string str) { @@ -772,9 +769,9 @@ internal Enumerator(object value) else { _current = null; - _values = Unsafe.As(value); + _values = Unsafe.As(value); } - _index = 0; + _index = 0; } public Enumerator(ref StringValues values) : this(values._values) @@ -788,7 +785,7 @@ public bool MoveNext() return false; } - string[] values = _values; + string?[]? values = _values; if (values != null) { if ((uint)index < (uint)values.Length) @@ -806,9 +803,9 @@ public bool MoveNext() return _current != null; } - public string Current => _current; + public string? Current => _current; - object IEnumerator.Current => _current; + object? IEnumerator.Current => _current; void IEnumerator.Reset() { diff --git a/src/libraries/Microsoft.Extensions.Primitives/src/ThrowHelper.cs b/src/libraries/Microsoft.Extensions.Primitives/src/ThrowHelper.cs index 8b8a0e4d37ce0..66e8947ecad9d 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/src/ThrowHelper.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/src/ThrowHelper.cs @@ -3,31 +3,37 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.Extensions.Primitives { internal static class ThrowHelper { + [DoesNotReturn] internal static void ThrowArgumentNullException(ExceptionArgument argument) { throw new ArgumentNullException(GetArgumentName(argument)); } + [DoesNotReturn] internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument) { throw new ArgumentOutOfRangeException(GetArgumentName(argument)); } + [DoesNotReturn] internal static void ThrowArgumentException(ExceptionResource resource) { throw new ArgumentException(GetResourceText(resource)); } + [DoesNotReturn] internal static void ThrowInvalidOperationException(ExceptionResource resource) { throw new InvalidOperationException(GetResourceText(resource)); } + [DoesNotReturn] internal static void ThrowInvalidOperationException(ExceptionResource resource, params object[] args) { string message = string.Format(GetResourceText(resource), args); diff --git a/src/libraries/Microsoft.Extensions.Primitives/tests/ChangeTokenTest.cs b/src/libraries/Microsoft.Extensions.Primitives/tests/ChangeTokenTest.cs index 05c2df370959b..6cebf8a813806 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/tests/ChangeTokenTest.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/tests/ChangeTokenTest.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.Threading; using Xunit; @@ -234,6 +233,12 @@ public void DoubleDisposeDisposesOnce() Assert.Equal(2, provider.DisposeCalls); } + [Fact] + public void NullTokenDisposeShouldNotThrow() + { + ChangeToken.OnChange(() => null, () => Assert.True(false)).Dispose(); + } + public class TrackableChangeTokenProvider { private TrackableChangeToken _cts = new TrackableChangeToken(); diff --git a/src/libraries/Microsoft.Extensions.Primitives/tests/StringSegmentTest.cs b/src/libraries/Microsoft.Extensions.Primitives/tests/StringSegmentTest.cs index aea63a8df70b2..812a154e3bfc9 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/tests/StringSegmentTest.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/tests/StringSegmentTest.cs @@ -462,6 +462,8 @@ public static TheoryData EqualsStringData { { "eLLo", StringComparison.OrdinalIgnoreCase, true }, { "eLLo", StringComparison.Ordinal, false }, + { null, StringComparison.OrdinalIgnoreCase, false }, + { null, StringComparison.Ordinal, false }, }; } } @@ -480,17 +482,6 @@ public void StringSegment_Equals_String_Valid(string candidate, StringComparison Assert.Equal(expectedResult, result); } - [Fact] - public void StringSegment_Equals_NullString_Throws() - { - // Arrange - var segment = new StringSegment(); - - // Act & assert - Assert.Throws("text", () => segment.Equals((string)null)); - Assert.Throws("text", () => segment.Equals((string)null, StringComparison.Ordinal)); - } - [Fact] public void StringSegment_Equals_String_InvalidComparisonType_Throws() {