Use T? for unconstrained nullable types (#25261)

* Use T? for unconstrained nullable types

* Apply suggestions from code review
This commit is contained in:
Pranav K 2020-09-10 15:14:30 -07:00 committed by GitHub
parent 690c717314
commit 36f8642f0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 49 additions and 46 deletions

View File

@ -25,8 +25,8 @@ namespace Microsoft.AspNetCore.Components
private delegate object? BindFormatter<T>(T value, CultureInfo? culture);
private delegate object BindFormatterWithFormat<T>(T value, CultureInfo? culture, string format);
internal delegate bool BindParser<T>(object? obj, CultureInfo? culture, out T value);
internal delegate bool BindParserWithFormat<T>(object? obj, CultureInfo? culture, string? format, out T value);
internal delegate bool BindParser<T>(object? obj, CultureInfo? culture, [MaybeNullWhen(false)] out T value);
internal delegate bool BindParserWithFormat<T>(object? obj, CultureInfo? culture, string? format, [MaybeNullWhen(false)] out T value);
/// <summary>
/// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
@ -1276,7 +1276,7 @@ namespace Microsoft.AspNetCore.Components
/// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
/// <param name="value">The converted value.</param>
/// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
public static bool TryConvertTo<T>(object? obj, CultureInfo? culture, out T value)
public static bool TryConvertTo<T>(object? obj, CultureInfo? culture, [MaybeNullWhen(false)] out T value)
{
var converter = ParserDelegateCache.Get<T>();
return converter(obj, culture, out value);

View File

@ -46,11 +46,11 @@ namespace Microsoft.AspNetCore.Components
/// </summary>
/// <param name="arg">The argument.</param>
/// <returns>A <see cref="Task"/> which completes asynchronously once event processing has completed.</returns>
public Task InvokeAsync(TValue arg)
public Task InvokeAsync(TValue? arg)
{
if (Receiver == null)
{
return EventCallbackWorkItem.InvokeAsync<TValue>(Delegate, arg);
return EventCallbackWorkItem.InvokeAsync<TValue?>(Delegate, arg);
}
return Receiver.HandleEventAsync(new EventCallbackWorkItem(Delegate), arg);

View File

@ -88,9 +88,8 @@ namespace Microsoft.AspNetCore.Components
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <param name="parameterName">The name of the parameter.</param>
/// <returns>The parameter value if found; otherwise the default value for the specified type.</returns>
[return: MaybeNull]
public TValue GetValueOrDefault<TValue>(string parameterName)
=> GetValueOrDefault<TValue>(parameterName, default!);
public TValue? GetValueOrDefault<TValue>(string parameterName)
=> GetValueOrDefault<TValue?>(parameterName, default);
/// <summary>
/// Gets the value of the parameter with the specified name, or a specified default value

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
@ -29,7 +28,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
public string Secret { get; }
public bool Equals([AllowNull] CircuitId other)
public bool Equals(CircuitId other)
{
// We want to use a fixed time equality comparison for a *real* comparisons.
// The only use case for Secret being null is with a default struct value,

View File

@ -35,10 +35,8 @@ namespace Microsoft.AspNetCore.Components.Forms
/// <example>
/// @bind-Value="model.PropertyName"
/// </example>
[AllowNull]
[MaybeNull]
[Parameter]
public TValue Value { get; set; } = default;
public TValue? Value { get; set; }
/// <summary>
/// Gets or sets a callback that updates the bound value.
@ -69,17 +67,15 @@ namespace Microsoft.AspNetCore.Components.Forms
/// <summary>
/// Gets or sets the current value of the input.
/// </summary>
[AllowNull]
protected TValue CurrentValue
protected TValue? CurrentValue
{
[return: MaybeNull]
get => Value!;
get => Value;
set
{
var hasChanged = !EqualityComparer<TValue>.Default.Equals(value, Value);
if (hasChanged)
{
Value = value!;
Value = value;
_ = ValueChanged.InvokeAsync(Value);
EditContext.NotifyFieldChanged(FieldIdentifier);
}
@ -148,7 +144,7 @@ namespace Microsoft.AspNetCore.Components.Forms
/// </summary>
/// <param name="value">The value to format.</param>
/// <returns>A string representation of the value.</returns>
protected virtual string? FormatValueAsString([AllowNull] TValue value)
protected virtual string? FormatValueAsString(TValue? value)
=> value?.ToString();
/// <summary>
@ -159,7 +155,7 @@ namespace Microsoft.AspNetCore.Components.Forms
/// <param name="result">An instance of <typeparamref name="TValue"/>.</param>
/// <param name="validationErrorMessage">If the value could not be parsed, provides a validation error message.</param>
/// <returns>True if the value could be parsed; otherwise false.</returns>
protected abstract bool TryParseValueFromString(string? value, [MaybeNull] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage);
protected abstract bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage);
/// <summary>
/// Gets a string that indicates the status of the field being edited. This will include

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Microsoft.AspNetCore.Components.Rendering;
@ -34,7 +35,7 @@ namespace Microsoft.AspNetCore.Components.Forms
}
/// <inheritdoc />
protected override string FormatValueAsString([AllowNull] TValue value)
protected override string FormatValueAsString(TValue? value)
{
switch (value)
{
@ -48,7 +49,7 @@ namespace Microsoft.AspNetCore.Components.Forms
}
/// <inheritdoc />
protected override bool TryParseValueFromString(string? value, [MaybeNull] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
{
// Unwrap nullable types. We don't have to deal with receiving empty values for nullable
// types here, because the underlying InputBase already covers that.
@ -70,6 +71,7 @@ namespace Microsoft.AspNetCore.Components.Forms
if (success)
{
Debug.Assert(result != null);
validationErrorMessage = null;
return true;
}
@ -80,7 +82,7 @@ namespace Microsoft.AspNetCore.Components.Forms
}
}
static bool TryParseDateTime(string? value, [MaybeNullWhen(false)] out TValue result)
private static bool TryParseDateTime(string? value, [MaybeNullWhen(false)] out TValue result)
{
var success = BindConverter.TryConvertToDateTime(value, CultureInfo.InvariantCulture, DateFormat, out var parsedValue);
if (success)
@ -95,7 +97,7 @@ namespace Microsoft.AspNetCore.Components.Forms
}
}
static bool TryParseDateTimeOffset(string? value, [MaybeNullWhen(false)] out TValue result)
private static bool TryParseDateTimeOffset(string? value, [MaybeNullWhen(false)] out TValue result)
{
var success = BindConverter.TryConvertToDateTimeOffset(value, CultureInfo.InvariantCulture, DateFormat, out var parsedValue);
if (success)

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Components.Forms
{
internal static class InputExtensions
{
public static bool TryParseSelectableValueFromString<TValue>(this InputBase<TValue> input, string? value, [MaybeNull] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
public static bool TryParseSelectableValueFromString<TValue>(this InputBase<TValue> input, string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
{
try
{

View File

@ -73,6 +73,11 @@ namespace Microsoft.AspNetCore.Components.Forms
{
var imageFile = await JSRuntime.InvokeAsync<BrowserFile>(InputFileInterop.ToImageFile, _inputFileElement, file.Id, format, maxWidth, maxHeight);
if (imageFile is null)
{
throw new InvalidOperationException("ToImageFile returned an unexpected null result.");
}
imageFile.Owner = this;
return imageFile;

View File

@ -4,6 +4,7 @@
using System;
using System.Buffers;
using System.IO.Pipelines;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.JSInterop;
@ -64,10 +65,10 @@ namespace Microsoft.AspNetCore.Components.Forms
offset,
segmentSize);
if (bytes.Length != segmentSize)
if (bytes is null || bytes.Length != segmentSize)
{
throw new InvalidOperationException(
$"A segment with size {bytes.Length} bytes was received, but {segmentSize} bytes were expected.");
$"A segment with size {bytes?.Length ?? 0} bytes was received, but {segmentSize} bytes were expected.");
}
bytes.CopyTo(pipeBuffer);

View File

@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Components.Forms
}
/// <inheritdoc />
protected override bool TryParseValueFromString(string? value, [MaybeNull] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
{
if (BindConverter.TryConvertTo<TValue>(value, CultureInfo.InvariantCulture, out result))
{
@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Components.Forms
/// </summary>
/// <param name="value">The value to format.</param>
/// <returns>A string representation of the value.</returns>
protected override string? FormatValueAsString([AllowNull] TValue value)
protected override string? FormatValueAsString(TValue? value)
{
// Avoiding a cast to IFormattable to avoid boxing.
switch (value)

View File

@ -27,10 +27,8 @@ namespace Microsoft.AspNetCore.Components.Forms
/// <summary>
/// Gets or sets the value of this input.
/// </summary>
[AllowNull]
[MaybeNull]
[Parameter]
public TValue Value { get; set; } = default;
public TValue? Value { get; set; }
/// <summary>
/// Gets or sets the name of the parent input radio group.

View File

@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Components.Forms
}
/// <inheritdoc />
protected override bool TryParseValueFromString(string? value, [MaybeNull] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
=> this.TryParseSelectableValueFromString(value, out result, out validationErrorMessage);
}
}

View File

@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Components.Forms
}
/// <inheritdoc />
protected override bool TryParseValueFromString(string? value, [MaybeNull] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
=> this.TryParseSelectableValueFromString(value, out result, out validationErrorMessage);
}
}

View File

@ -11,10 +11,10 @@ namespace Microsoft.JSInterop
/// <summary>
/// Invokes the specified JavaScript function synchronously.
/// </summary>
/// <typeparam name="T">The JSON-serializable return type.</typeparam>
/// <typeparam name="TResult">The JSON-serializable return type.</typeparam>
/// <param name="identifier">An identifier for the function to invoke. For example, the value <c>"someScope.someFunction"</c> will invoke the function <c>window.someScope.someFunction</c>.</param>
/// <param name="args">JSON-serializable arguments.</param>
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
T Invoke<T>(string identifier, params object?[]? args);
/// <returns>An instance of <typeparamref name="TResult"/> obtained by JSON-deserializing the return value.</returns>
TResult Invoke<TResult>(string identifier, params object?[]? args);
}
}

View File

@ -23,7 +23,6 @@ namespace Microsoft.JSInterop.Implementation
}
/// <inheritdoc />
[return: MaybeNull]
public TValue Invoke<TValue>(string identifier, params object?[]? args)
{
ThrowIfDisposed();

View File

@ -23,7 +23,6 @@ namespace Microsoft.JSInterop
id => new JSInProcessObjectReference(this, id)));
}
[return: MaybeNull]
internal TValue Invoke<TValue>(string identifier, long targetInstanceId, params object?[]? args)
{
var resultJson = InvokeJS(
@ -32,12 +31,15 @@ namespace Microsoft.JSInterop
JSCallResultTypeHelper.FromGeneric<TValue>(),
targetInstanceId);
// While the result of deserialization could be null, we're making a
// quality of life decision and letting users explicitly determine if they expect
// null by specifying TValue? as the expected return type.
if (resultJson is null)
{
return default;
return default!;
}
return JsonSerializer.Deserialize<TValue>(resultJson, JsonSerializerOptions);
return JsonSerializer.Deserialize<TValue>(resultJson, JsonSerializerOptions)!;
}
/// <summary>
@ -47,7 +49,6 @@ namespace Microsoft.JSInterop
/// <param name="identifier">An identifier for the function to invoke. For example, the value <c>"someScope.someFunction"</c> will invoke the function <c>window.someScope.someFunction</c>.</param>
/// <param name="args">JSON-serializable arguments.</param>
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
[return: MaybeNull]
public TValue Invoke<TValue>(string identifier, params object?[]? args)
=> Invoke<TValue>(identifier, 0, args);

View File

@ -102,7 +102,7 @@ namespace Microsoft.JSInterop
{
var taskId = Interlocked.Increment(ref _nextPendingTaskId);
var tcs = new TaskCompletionSource<TValue>();
if (cancellationToken != default)
if (cancellationToken.CanBeCanceled)
{
_cancellationRegistrations[taskId] = cancellationToken.Register(() =>
{

View File

@ -41,7 +41,7 @@ namespace Microsoft.JSInterop
/// <param name="identifier">An identifier for the function to invoke. For example, the value <c>"someScope.someFunction"</c> will invoke the function <c>window.someScope.someFunction</c>.</param>
/// <param name="args">JSON-serializable arguments.</param>
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
public static ValueTask<TValue> InvokeAsync<TValue>(this IJSRuntime jsRuntime, string identifier, params object[] args)
public static ValueTask<TValue> InvokeAsync<TValue>(this IJSRuntime jsRuntime, string identifier, params object?[]? args)
{
if (jsRuntime is null)
{
@ -63,7 +63,7 @@ namespace Microsoft.JSInterop
/// </param>
/// <param name="args">JSON-serializable arguments.</param>
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
public static ValueTask<TValue> InvokeAsync<TValue>(this IJSRuntime jsRuntime, string identifier, CancellationToken cancellationToken, params object[] args)
public static ValueTask<TValue> InvokeAsync<TValue>(this IJSRuntime jsRuntime, string identifier, CancellationToken cancellationToken, params object?[]? args)
{
if (jsRuntime is null)
{
@ -102,7 +102,7 @@ namespace Microsoft.JSInterop
/// <param name="timeout">The duration after which to cancel the async operation. Overrides default timeouts (<see cref="JSRuntime.DefaultAsyncTimeout"/>).</param>
/// <param name="args">JSON-serializable arguments.</param>
/// <returns>A <see cref="ValueTask"/> that represents the asynchronous invocation operation.</returns>
public static async ValueTask<TValue> InvokeAsync<TValue>(this IJSRuntime jsRuntime, string identifier, TimeSpan timeout, params object[] args)
public static async ValueTask<TValue> InvokeAsync<TValue>(this IJSRuntime jsRuntime, string identifier, TimeSpan timeout, params object?[]? args)
{
if (jsRuntime is null)
{

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.Json;
@ -145,6 +146,7 @@ namespace Microsoft.JSInterop
ref reader);
Assert.True(task.IsCompleted);
var poco = task.Result;
Debug.Assert(poco != null);
Assert.Equal(10, poco.Id);
Assert.Equal("Test", poco.Name);
}
@ -167,6 +169,7 @@ namespace Microsoft.JSInterop
ref reader);
Assert.True(task.IsCompleted);
var poco = task.Result;
Debug.Assert(poco != null);
Assert.Equal(10, poco.Id);
Assert.Equal("Test", poco.Name);
}