Use T? for unconstrained nullable types (#25772)

This commit is contained in:
Pranav K 2020-09-10 15:15:11 -07:00 committed by GitHub
parent 36f8642f0b
commit 18d261b20c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 43 additions and 56 deletions

View File

@ -144,8 +144,7 @@ namespace Microsoft.AspNetCore.Authentication
/// <typeparam name="T">Parameter type.</typeparam> /// <typeparam name="T">Parameter type.</typeparam>
/// <param name="key">Parameter key.</param> /// <param name="key">Parameter key.</param>
/// <returns>Retrieved value or the default value if the property is not set.</returns> /// <returns>Retrieved value or the default value if the property is not set.</returns>
[return: MaybeNull] public T? GetParameter<T>(string key)
public T GetParameter<T>(string key)
=> Parameters.TryGetValue(key, out var obj) && obj is T value ? value : default; => Parameters.TryGetValue(key, out var obj) && obj is T value ? value : default;
/// <summary> /// <summary>
@ -154,7 +153,7 @@ namespace Microsoft.AspNetCore.Authentication
/// <typeparam name="T">Parameter type.</typeparam> /// <typeparam name="T">Parameter type.</typeparam>
/// <param name="key">Parameter key.</param> /// <param name="key">Parameter key.</param>
/// <param name="value">Value to set.</param> /// <param name="value">Value to set.</param>
public void SetParameter<T>(string key, [MaybeNull] T value) public void SetParameter<T>(string key, T value)
=> Parameters[key] = value; => Parameters[key] = value;
/// <summary> /// <summary>

View File

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers namespace Microsoft.Net.Http.Headers
@ -13,9 +12,9 @@ namespace Microsoft.Net.Http.Headers
{ {
} }
protected abstract int GetParsedValueLength(StringSegment value, int startIndex, [MaybeNull] out T parsedValue); protected abstract int GetParsedValueLength(StringSegment value, int startIndex, out T? parsedValue);
public sealed override bool TryParseValue(StringSegment value, ref int index, [MaybeNull] out T parsedValue) public sealed override bool TryParseValue(StringSegment value, ref int index, out T? parsedValue)
{ {
parsedValue = default; parsedValue = default;

View File

@ -32,7 +32,7 @@ namespace Microsoft.Net.Http.Headers
// Cache-Control headers, only one instance of CacheControlHeaderValue is created (if all headers contain valid // Cache-Control headers, only one instance of CacheControlHeaderValue is created (if all headers contain valid
// values, otherwise we may have multiple strings containing the invalid values). // values, otherwise we may have multiple strings containing the invalid values).
private static readonly HttpHeaderParser<CacheControlHeaderValue> Parser private static readonly HttpHeaderParser<CacheControlHeaderValue> Parser
= new GenericHeaderParser<CacheControlHeaderValue>(true, GetCacheControlLength!); = new GenericHeaderParser<CacheControlHeaderValue>(true, GetCacheControlLength);
private static readonly Action<StringSegment> CheckIsValidTokenAction = CheckIsValidToken; private static readonly Action<StringSegment> CheckIsValidTokenAction = CheckIsValidToken;

View File

@ -27,7 +27,7 @@ namespace Microsoft.Net.Http.Headers
private static readonly char[] SingleQuote = new char[] { '\'' }; private static readonly char[] SingleQuote = new char[] { '\'' };
private static readonly HttpHeaderParser<ContentDispositionHeaderValue> Parser private static readonly HttpHeaderParser<ContentDispositionHeaderValue> Parser
= new GenericHeaderParser<ContentDispositionHeaderValue>(false, GetDispositionTypeLength!); = new GenericHeaderParser<ContentDispositionHeaderValue>(false, GetDispositionTypeLength);
// Use list instead of dictionary since we may have multiple parameters with the same name. // Use list instead of dictionary since we may have multiple parameters with the same name.
private ObjectCollection<NameValueHeaderValue>? _parameters; private ObjectCollection<NameValueHeaderValue>? _parameters;

View File

@ -13,7 +13,7 @@ namespace Microsoft.Net.Http.Headers
public class ContentRangeHeaderValue public class ContentRangeHeaderValue
{ {
private static readonly HttpHeaderParser<ContentRangeHeaderValue> Parser private static readonly HttpHeaderParser<ContentRangeHeaderValue> Parser
= new GenericHeaderParser<ContentRangeHeaderValue>(false, GetContentRangeLength!); = new GenericHeaderParser<ContentRangeHeaderValue>(false, GetContentRangeLength);
private StringSegment _unit; private StringSegment _unit;
private long? _from; private long? _from;

View File

@ -14,13 +14,13 @@ namespace Microsoft.Net.Http.Headers
// Note that the ETag header does not allow a * but we're not that strict: We allow both '*' and ETag values in a single value. // Note that the ETag header does not allow a * but we're not that strict: We allow both '*' and ETag values in a single value.
// We can't guarantee that a single parsed value will be used directly in an ETag header. // We can't guarantee that a single parsed value will be used directly in an ETag header.
private static readonly HttpHeaderParser<EntityTagHeaderValue> SingleValueParser private static readonly HttpHeaderParser<EntityTagHeaderValue> SingleValueParser
= new GenericHeaderParser<EntityTagHeaderValue>(false, GetEntityTagLength!); = new GenericHeaderParser<EntityTagHeaderValue>(false, GetEntityTagLength);
// Note that if multiple ETag values are allowed (e.g. 'If-Match', 'If-None-Match'), according to the RFC // Note that if multiple ETag values are allowed (e.g. 'If-Match', 'If-None-Match'), according to the RFC
// the value must either be '*' or a list of ETag values. It's not allowed to have both '*' and a list of // the value must either be '*' or a list of ETag values. It's not allowed to have both '*' and a list of
// ETag values. We're not that strict: We allow both '*' and ETag values in a list. If the server sends such // ETag values. We're not that strict: We allow both '*' and ETag values in a list. If the server sends such
// an invalid list, we want to be able to represent it using the corresponding header property. // an invalid list, we want to be able to represent it using the corresponding header property.
private static readonly HttpHeaderParser<EntityTagHeaderValue> MultipleValueParser private static readonly HttpHeaderParser<EntityTagHeaderValue> MultipleValueParser
= new GenericHeaderParser<EntityTagHeaderValue>(true, GetEntityTagLength!); = new GenericHeaderParser<EntityTagHeaderValue>(true, GetEntityTagLength);
private static EntityTagHeaderValue? AnyType; private static EntityTagHeaderValue? AnyType;

View File

@ -2,14 +2,13 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers namespace Microsoft.Net.Http.Headers
{ {
internal sealed class GenericHeaderParser<T> : BaseHeaderParser<T> internal sealed class GenericHeaderParser<T> : BaseHeaderParser<T>
{ {
internal delegate int GetParsedValueLengthDelegate(StringSegment value, int startIndex, [MaybeNull] out T parsedValue); internal delegate int GetParsedValueLengthDelegate(StringSegment value, int startIndex, out T? parsedValue);
private GetParsedValueLengthDelegate _getParsedValueLength; private GetParsedValueLengthDelegate _getParsedValueLength;
@ -24,7 +23,7 @@ namespace Microsoft.Net.Http.Headers
_getParsedValueLength = getParsedValueLength; _getParsedValueLength = getParsedValueLength;
} }
protected override int GetParsedValueLength(StringSegment value, int startIndex, [MaybeNull] out T parsedValue) protected override int GetParsedValueLength(StringSegment value, int startIndex, out T? parsedValue)
{ {
return _getParsedValueLength(value, startIndex, out parsedValue); return _getParsedValueLength(value, startIndex, out parsedValue);
} }

View File

@ -28,10 +28,9 @@ namespace Microsoft.Net.Http.Headers
// pointing to the next non-whitespace character after a delimiter. E.g. if called with a start index of 0 // pointing to the next non-whitespace character after a delimiter. E.g. if called with a start index of 0
// for string "value , second_value", then after the call completes, 'index' must point to 's', i.e. the first // for string "value , second_value", then after the call completes, 'index' must point to 's', i.e. the first
// non-whitespace after the separator ','. // non-whitespace after the separator ','.
public abstract bool TryParseValue(StringSegment value, ref int index, [MaybeNull] out T parsedValue); public abstract bool TryParseValue(StringSegment value, ref int index, out T? parsedValue);
[return: MaybeNull] public T? ParseValue(StringSegment value, ref int index)
public T ParseValue(StringSegment value, ref int index)
{ {
// Index may be value.Length (e.g. both 0). This may be allowed for some headers (e.g. Accept but not // Index may be value.Length (e.g. both 0). This may be allowed for some headers (e.g. Accept but not
// allowed by others (e.g. Content-Length). The parser has to decide if this is valid or not. // allowed by others (e.g. Content-Length). The parser has to decide if this is valid or not.

View File

@ -30,9 +30,9 @@ namespace Microsoft.Net.Http.Headers
private static readonly char[] PeriodCharacterArray = new char[] { PeriodCharacter }; private static readonly char[] PeriodCharacterArray = new char[] { PeriodCharacter };
private static readonly HttpHeaderParser<MediaTypeHeaderValue> SingleValueParser private static readonly HttpHeaderParser<MediaTypeHeaderValue> SingleValueParser
= new GenericHeaderParser<MediaTypeHeaderValue>(false, GetMediaTypeLength!); = new GenericHeaderParser<MediaTypeHeaderValue>(false, GetMediaTypeLength);
private static readonly HttpHeaderParser<MediaTypeHeaderValue> MultipleValueParser private static readonly HttpHeaderParser<MediaTypeHeaderValue> MultipleValueParser
= new GenericHeaderParser<MediaTypeHeaderValue>(true, GetMediaTypeLength!); = new GenericHeaderParser<MediaTypeHeaderValue>(true, GetMediaTypeLength);
// Use a collection instead of a dictionary since we may have multiple parameters with the same name. // Use a collection instead of a dictionary since we may have multiple parameters with the same name.
private ObjectCollection<NameValueHeaderValue>? _parameters; private ObjectCollection<NameValueHeaderValue>? _parameters;

View File

@ -17,9 +17,9 @@ namespace Microsoft.Net.Http.Headers
public class NameValueHeaderValue public class NameValueHeaderValue
{ {
private static readonly HttpHeaderParser<NameValueHeaderValue> SingleValueParser private static readonly HttpHeaderParser<NameValueHeaderValue> SingleValueParser
= new GenericHeaderParser<NameValueHeaderValue>(false, GetNameValueLength!); = new GenericHeaderParser<NameValueHeaderValue>(false, GetNameValueLength);
internal static readonly HttpHeaderParser<NameValueHeaderValue> MultipleValueParser internal static readonly HttpHeaderParser<NameValueHeaderValue> MultipleValueParser
= new GenericHeaderParser<NameValueHeaderValue>(true, GetNameValueLength!); = new GenericHeaderParser<NameValueHeaderValue>(true, GetNameValueLength);
private StringSegment _name; private StringSegment _name;
private StringSegment _value; private StringSegment _value;

View File

@ -11,7 +11,7 @@ namespace Microsoft.Net.Http.Headers
public class RangeConditionHeaderValue public class RangeConditionHeaderValue
{ {
private static readonly HttpHeaderParser<RangeConditionHeaderValue> Parser private static readonly HttpHeaderParser<RangeConditionHeaderValue> Parser
= new GenericHeaderParser<RangeConditionHeaderValue>(false, GetRangeConditionLength!); = new GenericHeaderParser<RangeConditionHeaderValue>(false, GetRangeConditionLength);
private DateTimeOffset? _lastModified; private DateTimeOffset? _lastModified;
private EntityTagHeaderValue? _entityTag; private EntityTagHeaderValue? _entityTag;

View File

@ -13,7 +13,7 @@ namespace Microsoft.Net.Http.Headers
public class RangeHeaderValue public class RangeHeaderValue
{ {
private static readonly HttpHeaderParser<RangeHeaderValue> Parser private static readonly HttpHeaderParser<RangeHeaderValue> Parser
= new GenericHeaderParser<RangeHeaderValue>(false, GetRangeLength!); = new GenericHeaderParser<RangeHeaderValue>(false, GetRangeLength);
private StringSegment _unit; private StringSegment _unit;
private ICollection<RangeItemHeaderValue>? _ranges; private ICollection<RangeItemHeaderValue>? _ranges;

View File

@ -33,9 +33,9 @@ namespace Microsoft.Net.Http.Headers
private const string ExpiresDateFormat = "r"; private const string ExpiresDateFormat = "r";
private static readonly HttpHeaderParser<SetCookieHeaderValue> SingleValueParser private static readonly HttpHeaderParser<SetCookieHeaderValue> SingleValueParser
= new GenericHeaderParser<SetCookieHeaderValue>(false, GetSetCookieLength!); = new GenericHeaderParser<SetCookieHeaderValue>(false, GetSetCookieLength);
private static readonly HttpHeaderParser<SetCookieHeaderValue> MultipleValueParser private static readonly HttpHeaderParser<SetCookieHeaderValue> MultipleValueParser
= new GenericHeaderParser<SetCookieHeaderValue>(true, GetSetCookieLength!); = new GenericHeaderParser<SetCookieHeaderValue>(true, GetSetCookieLength);
private StringSegment _name; private StringSegment _name;
private StringSegment _value; private StringSegment _value;

View File

@ -13,9 +13,9 @@ namespace Microsoft.Net.Http.Headers
public class StringWithQualityHeaderValue public class StringWithQualityHeaderValue
{ {
private static readonly HttpHeaderParser<StringWithQualityHeaderValue> SingleValueParser private static readonly HttpHeaderParser<StringWithQualityHeaderValue> SingleValueParser
= new GenericHeaderParser<StringWithQualityHeaderValue>(false, GetStringWithQualityLength!); = new GenericHeaderParser<StringWithQualityHeaderValue>(false, GetStringWithQualityLength);
private static readonly HttpHeaderParser<StringWithQualityHeaderValue> MultipleValueParser private static readonly HttpHeaderParser<StringWithQualityHeaderValue> MultipleValueParser
= new GenericHeaderParser<StringWithQualityHeaderValue>(true, GetStringWithQualityLength!); = new GenericHeaderParser<StringWithQualityHeaderValue>(true, GetStringWithQualityLength);
private StringSegment _value; private StringSegment _value;
private double? _quality; private double? _quality;

View File

@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Http
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
public static Task WriteAsJsonAsync<TValue>( public static Task WriteAsJsonAsync<TValue>(
this HttpResponse response, this HttpResponse response,
[AllowNull] TValue value, TValue value,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return response.WriteAsJsonAsync<TValue>(value, options: null, contentType: null, cancellationToken); return response.WriteAsJsonAsync<TValue>(value, options: null, contentType: null, cancellationToken);
@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Http
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
public static Task WriteAsJsonAsync<TValue>( public static Task WriteAsJsonAsync<TValue>(
this HttpResponse response, this HttpResponse response,
[AllowNull] TValue value, TValue value,
JsonSerializerOptions? options, JsonSerializerOptions? options,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Http
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
public static Task WriteAsJsonAsync<TValue>( public static Task WriteAsJsonAsync<TValue>(
this HttpResponse response, this HttpResponse response,
[AllowNull] TValue value, TValue value,
JsonSerializerOptions? options, JsonSerializerOptions? options,
string? contentType, string? contentType,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
@ -81,7 +81,7 @@ namespace Microsoft.AspNetCore.Http
options ??= ResolveSerializerOptions(response.HttpContext); options ??= ResolveSerializerOptions(response.HttpContext);
response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset; response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset;
return JsonSerializer.SerializeAsync<TValue>(response.Body, value!, options, cancellationToken); return JsonSerializer.SerializeAsync<TValue>(response.Body, value, options, cancellationToken);
} }
/// <summary> /// <summary>

View File

@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Http.Extensions.Tests
context.Response.Body = body; context.Response.Body = body;
// Act // Act
await context.Response.WriteAsJsonAsync<Uri>(value: null); await context.Response.WriteAsJsonAsync<Uri?>(value: null);
// Assert // Assert
Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType); Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);

View File

@ -4,7 +4,6 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
namespace Microsoft.AspNetCore.Http.Features namespace Microsoft.AspNetCore.Http.Features
@ -94,10 +93,9 @@ namespace Microsoft.AspNetCore.Http.Features
} }
} }
[return: MaybeNull] public TFeature? Get<TFeature>()
public TFeature Get<TFeature>()
{ {
return (TFeature)this[typeof(TFeature)]; return (TFeature?)this[typeof(TFeature)];
} }
public void Set<TFeature>(TFeature instance) public void Set<TFeature>(TFeature instance)

View File

@ -1,31 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNetCore.Http.Features namespace Microsoft.AspNetCore.Http.Features
{ {
public struct FeatureReference<T> public struct FeatureReference<T>
{ {
private T _feature; private T? _feature;
private int _revision; private int _revision;
private FeatureReference(T feature, int revision) private FeatureReference(T? feature, int revision)
{ {
_feature = feature; _feature = feature;
_revision = revision; _revision = revision;
} }
public static readonly FeatureReference<T> Default = new FeatureReference<T>(default(T)!, -1); public static readonly FeatureReference<T> Default = new FeatureReference<T>(default(T), -1);
[return: MaybeNull] public T? Fetch(IFeatureCollection features)
public T Fetch(IFeatureCollection features)
{ {
if (_revision == features.Revision) if (_revision == features.Revision)
{ {
return _feature; return _feature;
} }
_feature = (T)features[typeof(T)]!; _feature = (T)features[typeof(T)];
_revision = features.Revision; _revision = features.Revision;
return _feature; return _feature;
} }

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Microsoft.AspNetCore.Http.Features namespace Microsoft.AspNetCore.Http.Features
@ -37,8 +36,7 @@ namespace Microsoft.AspNetCore.Http.Features
// be able to pass ref values that "dot through" the TCache struct memory, // be able to pass ref values that "dot through" the TCache struct memory,
// if it was a Property then that getter would return a copy of the memory // if it was a Property then that getter would return a copy of the memory
// preventing the use of "ref" // preventing the use of "ref"
[AllowNull, MaybeNull] public TCache? Cache;
public TCache Cache;
// Careful with modifications to the Fetch method; it is carefully constructed for inlining // Careful with modifications to the Fetch method; it is carefully constructed for inlining
// See: https://github.com/aspnet/HttpAbstractions/pull/704 // See: https://github.com/aspnet/HttpAbstractions/pull/704
@ -61,10 +59,10 @@ namespace Microsoft.AspNetCore.Http.Features
// //
// Generally Fetch is called at a ratio > x4 of UpdateCached so this is a large gain // Generally Fetch is called at a ratio > x4 of UpdateCached so this is a large gain
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public TFeature Fetch<TFeature, TState>( public TFeature? Fetch<TFeature, TState>(
[AllowNull, MaybeNull] ref TFeature cached, ref TFeature? cached,
TState state, TState state,
Func<TState, TFeature> factory) where TFeature : class? Func<TState, TFeature?> factory) where TFeature : class?
{ {
var flush = false; var flush = false;
var revision = Collection?.Revision ?? ContextDisposed(); var revision = Collection?.Revision ?? ContextDisposed();
@ -80,7 +78,7 @@ namespace Microsoft.AspNetCore.Http.Features
} }
// Update and cache clearing logic, when the fast-path in Fetch isn't applicable // Update and cache clearing logic, when the fast-path in Fetch isn't applicable
private TFeature UpdateCached<TFeature, TState>(ref TFeature cached, TState state, Func<TState, TFeature> factory, int revision, bool flush) where TFeature : class? private TFeature? UpdateCached<TFeature, TState>(ref TFeature? cached, TState state, Func<TState, TFeature?> factory, int revision, bool flush) where TFeature : class?
{ {
if (flush) if (flush)
{ {
@ -108,8 +106,8 @@ namespace Microsoft.AspNetCore.Http.Features
return cached; return cached;
} }
public TFeature Fetch<TFeature>([AllowNull, MaybeNull] ref TFeature cached, Func<IFeatureCollection, TFeature> factory) public TFeature? Fetch<TFeature>(ref TFeature? cached, Func<IFeatureCollection, TFeature?> factory)
where TFeature : class? => Fetch(ref cached!, Collection, factory); where TFeature : class? => Fetch(ref cached, Collection, factory);
private static int ContextDisposed() private static int ContextDisposed()
{ {

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -58,8 +57,7 @@ namespace Microsoft.AspNetCore.Builder
public IDictionary<string, object?> Properties { get; } public IDictionary<string, object?> Properties { get; }
[return: MaybeNull] private T? GetProperty<T>(string key)
private T GetProperty<T>(string key)
{ {
return Properties.TryGetValue(key, out var value) ? (T)value : default(T); return Properties.TryGetValue(key, out var value) ? (T)value : default(T);
} }