Use T? for unconstrained nullable types (#25261)
* Use T? for unconstrained nullable types * Apply suggestions from code review
This commit is contained in:
parent
690c717314
commit
36f8642f0b
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ namespace Microsoft.JSInterop.Implementation
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[return: MaybeNull]
|
||||
public TValue Invoke<TValue>(string identifier, params object?[]? args)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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(() =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue