From 3265d463a7413835ed1fbb43a31ffd4e1ec0826a Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 23 Sep 2017 00:17:27 +0100 Subject: [PATCH] Faster IFeatureCollection.Get --- .../HttpProtocolFeatureCollection.cs | 94 ++-- .../Internal/Http/Http1Connection.cs | 2 +- .../Http/HttpProtocol.FeatureCollection.cs | 15 +- .../Internal/Http/HttpProtocol.Generated.cs | 495 ++++++++++++------ .../Internal/Http2/Http2Stream.cs | 2 +- .../HttpProtocolFeatureCollectionTests.cs | 209 ++++++++ .../HttpProtocolFeatureCollection.cs | 96 +++- 7 files changed, 687 insertions(+), 226 deletions(-) create mode 100644 test/Kestrel.Core.Tests/HttpProtocolFeatureCollectionTests.cs diff --git a/benchmarks/Kestrel.Performance/HttpProtocolFeatureCollection.cs b/benchmarks/Kestrel.Performance/HttpProtocolFeatureCollection.cs index f637269c3..4ec95df9d 100644 --- a/benchmarks/Kestrel.Performance/HttpProtocolFeatureCollection.cs +++ b/benchmarks/Kestrel.Performance/HttpProtocolFeatureCollection.cs @@ -1,8 +1,10 @@ // 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. -using System; using System.IO.Pipelines; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; @@ -14,69 +16,70 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance [Config(typeof(CoreConfig))] public class HttpProtocolFeatureCollection { - private readonly Http1Connection _http1Connection; - private IFeatureCollection _collection; - - [Benchmark(Baseline = true)] - public IHttpRequestFeature GetFirstViaFastFeature() - { - return (IHttpRequestFeature)GetFastFeature(typeof(IHttpRequestFeature)); - } + private readonly IFeatureCollection _collection; [Benchmark] - public IHttpRequestFeature GetFirstViaType() + [MethodImpl(MethodImplOptions.NoInlining)] + public IHttpRequestFeature GetViaTypeOf_First() { - return (IHttpRequestFeature)Get(typeof(IHttpRequestFeature)); + return (IHttpRequestFeature)_collection[typeof(IHttpRequestFeature)]; } [Benchmark] - public IHttpRequestFeature GetFirstViaExtension() - { - return _collection.GetType(); - } - - [Benchmark] - public IHttpRequestFeature GetFirstViaGeneric() + [MethodImpl(MethodImplOptions.NoInlining)] + public IHttpRequestFeature GetViaGeneric_First() { return _collection.Get(); } [Benchmark] - public IHttpSendFileFeature GetLastViaFastFeature() + [MethodImpl(MethodImplOptions.NoInlining)] + public IHttpSendFileFeature GetViaTypeOf_Last() { - return (IHttpSendFileFeature)GetFastFeature(typeof(IHttpSendFileFeature)); + return (IHttpSendFileFeature)_collection[typeof(IHttpSendFileFeature)]; } [Benchmark] - public IHttpSendFileFeature GetLastViaType() + [MethodImpl(MethodImplOptions.NoInlining)] + public IHttpSendFileFeature GetViaGeneric_Last() { - return (IHttpSendFileFeature)Get(typeof(IHttpSendFileFeature)); + return _collection.Get(); } [Benchmark] - public IHttpSendFileFeature GetLastViaExtension() + [MethodImpl(MethodImplOptions.NoInlining)] + public object GetViaTypeOf_Custom() { - return _collection.GetType(); + return (IHttpCustomFeature)_collection[typeof(IHttpCustomFeature)]; } [Benchmark] - public IHttpSendFileFeature GetLastViaGeneric() + [MethodImpl(MethodImplOptions.NoInlining)] + public object GetViaGeneric_Custom() { - return _collection.Get(); + return _collection.Get(); } - private object Get(Type type) + + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public object GetViaTypeOf_NotFound() { - return _collection[type]; + return (IHttpNotFoundFeature)_collection[typeof(IHttpNotFoundFeature)]; } - private object GetFastFeature(Type type) + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public object GetViaGeneric_NotFound() { - return _http1Connection.FastFeatureGet(type); + return _collection.Get(); } public HttpProtocolFeatureCollection() { + var pipeFactory = new PipeFactory(); + var pair = pipeFactory.CreateConnectionPair(); + var serviceContext = new ServiceContext { HttpParserFactory = _ => NullParser.Instance, @@ -86,24 +89,35 @@ public HttpProtocolFeatureCollection() { ServiceContext = serviceContext, ConnectionFeatures = new FeatureCollection(), - PipeFactory = new PipeFactory() + PipeFactory = pipeFactory, + Application = pair.Application, + Transport = pair.Transport }; - _http1Connection = new Http1Connection(application: null, context: http1ConnectionContext); + var http1Connection = new Http1Connection(application: null, context: http1ConnectionContext); + http1Connection.Reset(); + + _collection = http1Connection; + + IHttpSendFileFeature sendFileFeature = new SendFileFeature(); + _collection.Set(sendFileFeature); } - [IterationSetup] - public void Setup() + + private class SendFileFeature : IHttpSendFileFeature { - _collection = _http1Connection; + public Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellation) + { + throw new System.NotImplementedException(); + } } - } - public static class IFeatureCollectionExtensions - { - public static T GetType(this IFeatureCollection collection) + private interface IHttpCustomFeature + { + } + + private interface IHttpNotFoundFeature { - return (T)collection[typeof(T)]; } } } diff --git a/src/Kestrel.Core/Internal/Http/Http1Connection.cs b/src/Kestrel.Core/Internal/Http/Http1Connection.cs index 9eb06465c..a665d55ce 100644 --- a/src/Kestrel.Core/Internal/Http/Http1Connection.cs +++ b/src/Kestrel.Core/Internal/Http/Http1Connection.cs @@ -405,7 +405,7 @@ protected void EnsureHostHeaderExists() protected override void OnReset() { - FastFeatureSet(typeof(IHttpUpgradeFeature), this); + ResetIHttpUpgradeFeature(); _requestTimedOut = false; _requestTargetForm = HttpRequestTarget.Unknown; diff --git a/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs b/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs index 230ba868a..cc0337a83 100644 --- a/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs +++ b/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; -using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { @@ -244,20 +243,14 @@ MinDataRate IHttpMinResponseDataRateFeature.MinDataRate set => MinResponseDataRate = value; } - object IFeatureCollection.this[Type key] + protected void ResetIHttpUpgradeFeature() { - get => FastFeatureGet(key) ?? ConnectionFeatures[key]; - set => FastFeatureSet(key, value); + _currentIHttpUpgradeFeature = this; } - TFeature IFeatureCollection.Get() + protected void ResetIHttp2StreamIdFeature() { - return (TFeature)(FastFeatureGet(typeof(TFeature)) ?? ConnectionFeatures[typeof(TFeature)]); - } - - void IFeatureCollection.Set(TFeature instance) - { - FastFeatureSet(typeof(TFeature), instance); + _currentIHttp2StreamIdFeature = this; } void IHttpResponseFeature.OnStarting(Func callback, object state) diff --git a/src/Kestrel.Core/Internal/Http/HttpProtocol.Generated.cs b/src/Kestrel.Core/Internal/Http/HttpProtocol.Generated.cs index 5c57080c1..23a6bd9eb 100644 --- a/src/Kestrel.Core/Internal/Http/HttpProtocol.Generated.cs +++ b/src/Kestrel.Core/Internal/Http/HttpProtocol.Generated.cs @@ -3,32 +3,36 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Http.Features.Authentication; +using Microsoft.AspNetCore.Server.Kestrel.Core.Features; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { public partial class HttpProtocol { - private static readonly Type IHttpRequestFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpRequestFeature); - private static readonly Type IHttpResponseFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpResponseFeature); - private static readonly Type IHttpRequestIdentifierFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpRequestIdentifierFeature); - private static readonly Type IServiceProvidersFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IServiceProvidersFeature); - private static readonly Type IHttpRequestLifetimeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature); - private static readonly Type IHttpConnectionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature); - private static readonly Type IHttpAuthenticationFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature); - private static readonly Type IQueryFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IQueryFeature); - private static readonly Type IFormFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IFormFeature); - private static readonly Type IHttpUpgradeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpUpgradeFeature); - private static readonly Type IHttp2StreamIdFeatureType = typeof(global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttp2StreamIdFeature); - private static readonly Type IResponseCookiesFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature); - private static readonly Type IItemsFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IItemsFeature); - private static readonly Type ITlsConnectionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature); - private static readonly Type IHttpWebSocketFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpWebSocketFeature); - private static readonly Type ISessionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ISessionFeature); - private static readonly Type IHttpMaxRequestBodySizeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature); - private static readonly Type IHttpMinRequestBodyDataRateFeatureType = typeof(global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature); - private static readonly Type IHttpMinResponseDataRateFeatureType = typeof(global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinResponseDataRateFeature); - private static readonly Type IHttpBodyControlFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature); - private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature); + private static readonly Type IHttpRequestFeatureType = typeof(IHttpRequestFeature); + private static readonly Type IHttpResponseFeatureType = typeof(IHttpResponseFeature); + private static readonly Type IHttpRequestIdentifierFeatureType = typeof(IHttpRequestIdentifierFeature); + private static readonly Type IServiceProvidersFeatureType = typeof(IServiceProvidersFeature); + private static readonly Type IHttpRequestLifetimeFeatureType = typeof(IHttpRequestLifetimeFeature); + private static readonly Type IHttpConnectionFeatureType = typeof(IHttpConnectionFeature); + private static readonly Type IHttpAuthenticationFeatureType = typeof(IHttpAuthenticationFeature); + private static readonly Type IQueryFeatureType = typeof(IQueryFeature); + private static readonly Type IFormFeatureType = typeof(IFormFeature); + private static readonly Type IHttpUpgradeFeatureType = typeof(IHttpUpgradeFeature); + private static readonly Type IHttp2StreamIdFeatureType = typeof(IHttp2StreamIdFeature); + private static readonly Type IResponseCookiesFeatureType = typeof(IResponseCookiesFeature); + private static readonly Type IItemsFeatureType = typeof(IItemsFeature); + private static readonly Type ITlsConnectionFeatureType = typeof(ITlsConnectionFeature); + private static readonly Type IHttpWebSocketFeatureType = typeof(IHttpWebSocketFeature); + private static readonly Type ISessionFeatureType = typeof(ISessionFeature); + private static readonly Type IHttpMaxRequestBodySizeFeatureType = typeof(IHttpMaxRequestBodySizeFeature); + private static readonly Type IHttpMinRequestBodyDataRateFeatureType = typeof(IHttpMinRequestBodyDataRateFeature); + private static readonly Type IHttpMinResponseDataRateFeatureType = typeof(IHttpMinResponseDataRateFeature); + private static readonly Type IHttpBodyControlFeatureType = typeof(IHttpBodyControlFeature); + private static readonly Type IHttpSendFileFeatureType = typeof(IHttpSendFileFeature); private object _currentIHttpRequestFeature; private object _currentIHttpResponseFeature; @@ -78,292 +82,475 @@ private void FastReset() _currentIHttpSendFileFeature = null; } - internal object FastFeatureGet(Type key) + object IFeatureCollection.this[Type key] { - if (key == IHttpRequestFeatureType) + get { - return _currentIHttpRequestFeature; + object feature; + if (key == IHttpRequestFeatureType) + { + feature = _currentIHttpRequestFeature; + } + else if (key == IHttpResponseFeatureType) + { + feature = _currentIHttpResponseFeature; + } + else if (key == IHttpRequestIdentifierFeatureType) + { + feature = _currentIHttpRequestIdentifierFeature; + } + else if (key == IServiceProvidersFeatureType) + { + feature = _currentIServiceProvidersFeature; + } + else if (key == IHttpRequestLifetimeFeatureType) + { + feature = _currentIHttpRequestLifetimeFeature; + } + else if (key == IHttpConnectionFeatureType) + { + feature = _currentIHttpConnectionFeature; + } + else if (key == IHttpAuthenticationFeatureType) + { + feature = _currentIHttpAuthenticationFeature; + } + else if (key == IQueryFeatureType) + { + feature = _currentIQueryFeature; + } + else if (key == IFormFeatureType) + { + feature = _currentIFormFeature; + } + else if (key == IHttpUpgradeFeatureType) + { + feature = _currentIHttpUpgradeFeature; + } + else if (key == IHttp2StreamIdFeatureType) + { + feature = _currentIHttp2StreamIdFeature; + } + else if (key == IResponseCookiesFeatureType) + { + feature = _currentIResponseCookiesFeature; + } + else if (key == IItemsFeatureType) + { + feature = _currentIItemsFeature; + } + else if (key == ITlsConnectionFeatureType) + { + feature = _currentITlsConnectionFeature; + } + else if (key == IHttpWebSocketFeatureType) + { + feature = _currentIHttpWebSocketFeature; + } + else if (key == ISessionFeatureType) + { + feature = _currentISessionFeature; + } + else if (key == IHttpMaxRequestBodySizeFeatureType) + { + feature = _currentIHttpMaxRequestBodySizeFeature; + } + else if (key == IHttpMinRequestBodyDataRateFeatureType) + { + feature = _currentIHttpMinRequestBodyDataRateFeature; + } + else if (key == IHttpMinResponseDataRateFeatureType) + { + feature = _currentIHttpMinResponseDataRateFeature; + } + else if (key == IHttpBodyControlFeatureType) + { + feature = _currentIHttpBodyControlFeature; + } + else if (key == IHttpSendFileFeatureType) + { + feature = _currentIHttpSendFileFeature; + } + else + { + feature = ExtraFeatureGet(key); + } + + return feature ?? ConnectionFeatures[key]; } - if (key == IHttpResponseFeatureType) + + set { - return _currentIHttpResponseFeature; + _featureRevision++; + + if (key == IHttpRequestFeatureType) + { + _currentIHttpRequestFeature = value; + } + else if (key == IHttpResponseFeatureType) + { + _currentIHttpResponseFeature = value; + } + else if (key == IHttpRequestIdentifierFeatureType) + { + _currentIHttpRequestIdentifierFeature = value; + } + else if (key == IServiceProvidersFeatureType) + { + _currentIServiceProvidersFeature = value; + } + else if (key == IHttpRequestLifetimeFeatureType) + { + _currentIHttpRequestLifetimeFeature = value; + } + else if (key == IHttpConnectionFeatureType) + { + _currentIHttpConnectionFeature = value; + } + else if (key == IHttpAuthenticationFeatureType) + { + _currentIHttpAuthenticationFeature = value; + } + else if (key == IQueryFeatureType) + { + _currentIQueryFeature = value; + } + else if (key == IFormFeatureType) + { + _currentIFormFeature = value; + } + else if (key == IHttpUpgradeFeatureType) + { + _currentIHttpUpgradeFeature = value; + } + else if (key == IHttp2StreamIdFeatureType) + { + _currentIHttp2StreamIdFeature = value; + } + else if (key == IResponseCookiesFeatureType) + { + _currentIResponseCookiesFeature = value; + } + else if (key == IItemsFeatureType) + { + _currentIItemsFeature = value; + } + else if (key == ITlsConnectionFeatureType) + { + _currentITlsConnectionFeature = value; + } + else if (key == IHttpWebSocketFeatureType) + { + _currentIHttpWebSocketFeature = value; + } + else if (key == ISessionFeatureType) + { + _currentISessionFeature = value; + } + else if (key == IHttpMaxRequestBodySizeFeatureType) + { + _currentIHttpMaxRequestBodySizeFeature = value; + } + else if (key == IHttpMinRequestBodyDataRateFeatureType) + { + _currentIHttpMinRequestBodyDataRateFeature = value; + } + else if (key == IHttpMinResponseDataRateFeatureType) + { + _currentIHttpMinResponseDataRateFeature = value; + } + else if (key == IHttpBodyControlFeatureType) + { + _currentIHttpBodyControlFeature = value; + } + else if (key == IHttpSendFileFeatureType) + { + _currentIHttpSendFileFeature = value; + } + else + { + ExtraFeatureSet(key, value); + } } - if (key == IHttpRequestIdentifierFeatureType) + } + + void IFeatureCollection.Set(TFeature feature) + { + if (typeof(TFeature) == typeof(IHttpRequestFeature)) { - return _currentIHttpRequestIdentifierFeature; + _currentIHttpRequestFeature = feature; } - if (key == IServiceProvidersFeatureType) + else if (typeof(TFeature) == typeof(IHttpResponseFeature)) { - return _currentIServiceProvidersFeature; + _currentIHttpResponseFeature = feature; + } + else if (typeof(TFeature) == typeof(IHttpRequestIdentifierFeature)) + { + _currentIHttpRequestIdentifierFeature = feature; + } + else if (typeof(TFeature) == typeof(IServiceProvidersFeature)) + { + _currentIServiceProvidersFeature = feature; + } + else if (typeof(TFeature) == typeof(IHttpRequestLifetimeFeature)) + { + _currentIHttpRequestLifetimeFeature = feature; } - if (key == IHttpRequestLifetimeFeatureType) + else if (typeof(TFeature) == typeof(IHttpConnectionFeature)) { - return _currentIHttpRequestLifetimeFeature; + _currentIHttpConnectionFeature = feature; } - if (key == IHttpConnectionFeatureType) + else if (typeof(TFeature) == typeof(IHttpAuthenticationFeature)) { - return _currentIHttpConnectionFeature; + _currentIHttpAuthenticationFeature = feature; } - if (key == IHttpAuthenticationFeatureType) + else if (typeof(TFeature) == typeof(IQueryFeature)) { - return _currentIHttpAuthenticationFeature; + _currentIQueryFeature = feature; } - if (key == IQueryFeatureType) + else if (typeof(TFeature) == typeof(IFormFeature)) { - return _currentIQueryFeature; + _currentIFormFeature = feature; } - if (key == IFormFeatureType) + else if (typeof(TFeature) == typeof(IHttpUpgradeFeature)) { - return _currentIFormFeature; + _currentIHttpUpgradeFeature = feature; } - if (key == IHttpUpgradeFeatureType) + else if (typeof(TFeature) == typeof(IHttp2StreamIdFeature)) { - return _currentIHttpUpgradeFeature; + _currentIHttp2StreamIdFeature = feature; } - if (key == IHttp2StreamIdFeatureType) + else if (typeof(TFeature) == typeof(IResponseCookiesFeature)) { - return _currentIHttp2StreamIdFeature; + _currentIResponseCookiesFeature = feature; } - if (key == IResponseCookiesFeatureType) + else if (typeof(TFeature) == typeof(IItemsFeature)) { - return _currentIResponseCookiesFeature; + _currentIItemsFeature = feature; } - if (key == IItemsFeatureType) + else if (typeof(TFeature) == typeof(ITlsConnectionFeature)) { - return _currentIItemsFeature; + _currentITlsConnectionFeature = feature; } - if (key == ITlsConnectionFeatureType) + else if (typeof(TFeature) == typeof(IHttpWebSocketFeature)) { - return _currentITlsConnectionFeature; + _currentIHttpWebSocketFeature = feature; } - if (key == IHttpWebSocketFeatureType) + else if (typeof(TFeature) == typeof(ISessionFeature)) { - return _currentIHttpWebSocketFeature; + _currentISessionFeature = feature; } - if (key == ISessionFeatureType) + else if (typeof(TFeature) == typeof(IHttpMaxRequestBodySizeFeature)) { - return _currentISessionFeature; + _currentIHttpMaxRequestBodySizeFeature = feature; } - if (key == IHttpMaxRequestBodySizeFeatureType) + else if (typeof(TFeature) == typeof(IHttpMinRequestBodyDataRateFeature)) { - return _currentIHttpMaxRequestBodySizeFeature; + _currentIHttpMinRequestBodyDataRateFeature = feature; } - if (key == IHttpMinRequestBodyDataRateFeatureType) + else if (typeof(TFeature) == typeof(IHttpMinResponseDataRateFeature)) { - return _currentIHttpMinRequestBodyDataRateFeature; + _currentIHttpMinResponseDataRateFeature = feature; } - if (key == IHttpMinResponseDataRateFeatureType) + else if (typeof(TFeature) == typeof(IHttpBodyControlFeature)) { - return _currentIHttpMinResponseDataRateFeature; + _currentIHttpBodyControlFeature = feature; } - if (key == IHttpBodyControlFeatureType) + else if (typeof(TFeature) == typeof(IHttpSendFileFeature)) { - return _currentIHttpBodyControlFeature; + _currentIHttpSendFileFeature = feature; } - if (key == IHttpSendFileFeatureType) + else { - return _currentIHttpSendFileFeature; + ExtraFeatureSet(typeof(TFeature), feature); } - return ExtraFeatureGet(key); } - protected void FastFeatureSet(Type key, object feature) + TFeature IFeatureCollection.Get() { - _featureRevision++; - - if (key == IHttpRequestFeatureType) + TFeature feature; + if (typeof(TFeature) == typeof(IHttpRequestFeature)) { - _currentIHttpRequestFeature = feature; - return; + feature = (TFeature)_currentIHttpRequestFeature; } - if (key == IHttpResponseFeatureType) + else if (typeof(TFeature) == typeof(IHttpResponseFeature)) { - _currentIHttpResponseFeature = feature; - return; + feature = (TFeature)_currentIHttpResponseFeature; } - if (key == IHttpRequestIdentifierFeatureType) + else if (typeof(TFeature) == typeof(IHttpRequestIdentifierFeature)) { - _currentIHttpRequestIdentifierFeature = feature; - return; + feature = (TFeature)_currentIHttpRequestIdentifierFeature; } - if (key == IServiceProvidersFeatureType) + else if (typeof(TFeature) == typeof(IServiceProvidersFeature)) { - _currentIServiceProvidersFeature = feature; - return; + feature = (TFeature)_currentIServiceProvidersFeature; } - if (key == IHttpRequestLifetimeFeatureType) + else if (typeof(TFeature) == typeof(IHttpRequestLifetimeFeature)) { - _currentIHttpRequestLifetimeFeature = feature; - return; + feature = (TFeature)_currentIHttpRequestLifetimeFeature; } - if (key == IHttpConnectionFeatureType) + else if (typeof(TFeature) == typeof(IHttpConnectionFeature)) { - _currentIHttpConnectionFeature = feature; - return; + feature = (TFeature)_currentIHttpConnectionFeature; } - if (key == IHttpAuthenticationFeatureType) + else if (typeof(TFeature) == typeof(IHttpAuthenticationFeature)) { - _currentIHttpAuthenticationFeature = feature; - return; + feature = (TFeature)_currentIHttpAuthenticationFeature; } - if (key == IQueryFeatureType) + else if (typeof(TFeature) == typeof(IQueryFeature)) { - _currentIQueryFeature = feature; - return; + feature = (TFeature)_currentIQueryFeature; } - if (key == IFormFeatureType) + else if (typeof(TFeature) == typeof(IFormFeature)) { - _currentIFormFeature = feature; - return; + feature = (TFeature)_currentIFormFeature; } - if (key == IHttpUpgradeFeatureType) + else if (typeof(TFeature) == typeof(IHttpUpgradeFeature)) { - _currentIHttpUpgradeFeature = feature; - return; + feature = (TFeature)_currentIHttpUpgradeFeature; } - if (key == IHttp2StreamIdFeatureType) + else if (typeof(TFeature) == typeof(IHttp2StreamIdFeature)) { - _currentIHttp2StreamIdFeature = feature; - return; + feature = (TFeature)_currentIHttp2StreamIdFeature; } - if (key == IResponseCookiesFeatureType) + else if (typeof(TFeature) == typeof(IResponseCookiesFeature)) { - _currentIResponseCookiesFeature = feature; - return; + feature = (TFeature)_currentIResponseCookiesFeature; } - if (key == IItemsFeatureType) + else if (typeof(TFeature) == typeof(IItemsFeature)) { - _currentIItemsFeature = feature; - return; + feature = (TFeature)_currentIItemsFeature; } - if (key == ITlsConnectionFeatureType) + else if (typeof(TFeature) == typeof(ITlsConnectionFeature)) { - _currentITlsConnectionFeature = feature; - return; + feature = (TFeature)_currentITlsConnectionFeature; } - if (key == IHttpWebSocketFeatureType) + else if (typeof(TFeature) == typeof(IHttpWebSocketFeature)) { - _currentIHttpWebSocketFeature = feature; - return; + feature = (TFeature)_currentIHttpWebSocketFeature; } - if (key == ISessionFeatureType) + else if (typeof(TFeature) == typeof(ISessionFeature)) { - _currentISessionFeature = feature; - return; + feature = (TFeature)_currentISessionFeature; } - if (key == IHttpMaxRequestBodySizeFeatureType) + else if (typeof(TFeature) == typeof(IHttpMaxRequestBodySizeFeature)) { - _currentIHttpMaxRequestBodySizeFeature = feature; - return; + feature = (TFeature)_currentIHttpMaxRequestBodySizeFeature; } - if (key == IHttpMinRequestBodyDataRateFeatureType) + else if (typeof(TFeature) == typeof(IHttpMinRequestBodyDataRateFeature)) { - _currentIHttpMinRequestBodyDataRateFeature = feature; - return; + feature = (TFeature)_currentIHttpMinRequestBodyDataRateFeature; } - if (key == IHttpMinResponseDataRateFeatureType) + else if (typeof(TFeature) == typeof(IHttpMinResponseDataRateFeature)) { - _currentIHttpMinResponseDataRateFeature = feature; - return; + feature = (TFeature)_currentIHttpMinResponseDataRateFeature; } - if (key == IHttpBodyControlFeatureType) + else if (typeof(TFeature) == typeof(IHttpBodyControlFeature)) { - _currentIHttpBodyControlFeature = feature; - return; + feature = (TFeature)_currentIHttpBodyControlFeature; } - if (key == IHttpSendFileFeatureType) + else if (typeof(TFeature) == typeof(IHttpSendFileFeature)) { - _currentIHttpSendFileFeature = feature; - return; - }; - ExtraFeatureSet(key, feature); + feature = (TFeature)_currentIHttpSendFileFeature; + } + else + { + feature = (TFeature)(ExtraFeatureGet(typeof(TFeature))); + } + + if (feature != null) + { + return feature; + } + + return (TFeature)ConnectionFeatures[typeof(TFeature)]; } private IEnumerable> FastEnumerable() { if (_currentIHttpRequestFeature != null) { - yield return new KeyValuePair(IHttpRequestFeatureType, _currentIHttpRequestFeature as global::Microsoft.AspNetCore.Http.Features.IHttpRequestFeature); + yield return new KeyValuePair(IHttpRequestFeatureType, _currentIHttpRequestFeature as IHttpRequestFeature); } if (_currentIHttpResponseFeature != null) { - yield return new KeyValuePair(IHttpResponseFeatureType, _currentIHttpResponseFeature as global::Microsoft.AspNetCore.Http.Features.IHttpResponseFeature); + yield return new KeyValuePair(IHttpResponseFeatureType, _currentIHttpResponseFeature as IHttpResponseFeature); } if (_currentIHttpRequestIdentifierFeature != null) { - yield return new KeyValuePair(IHttpRequestIdentifierFeatureType, _currentIHttpRequestIdentifierFeature as global::Microsoft.AspNetCore.Http.Features.IHttpRequestIdentifierFeature); + yield return new KeyValuePair(IHttpRequestIdentifierFeatureType, _currentIHttpRequestIdentifierFeature as IHttpRequestIdentifierFeature); } if (_currentIServiceProvidersFeature != null) { - yield return new KeyValuePair(IServiceProvidersFeatureType, _currentIServiceProvidersFeature as global::Microsoft.AspNetCore.Http.Features.IServiceProvidersFeature); + yield return new KeyValuePair(IServiceProvidersFeatureType, _currentIServiceProvidersFeature as IServiceProvidersFeature); } if (_currentIHttpRequestLifetimeFeature != null) { - yield return new KeyValuePair(IHttpRequestLifetimeFeatureType, _currentIHttpRequestLifetimeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature); + yield return new KeyValuePair(IHttpRequestLifetimeFeatureType, _currentIHttpRequestLifetimeFeature as IHttpRequestLifetimeFeature); } if (_currentIHttpConnectionFeature != null) { - yield return new KeyValuePair(IHttpConnectionFeatureType, _currentIHttpConnectionFeature as global::Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature); + yield return new KeyValuePair(IHttpConnectionFeatureType, _currentIHttpConnectionFeature as IHttpConnectionFeature); } if (_currentIHttpAuthenticationFeature != null) { - yield return new KeyValuePair(IHttpAuthenticationFeatureType, _currentIHttpAuthenticationFeature as global::Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature); + yield return new KeyValuePair(IHttpAuthenticationFeatureType, _currentIHttpAuthenticationFeature as IHttpAuthenticationFeature); } if (_currentIQueryFeature != null) { - yield return new KeyValuePair(IQueryFeatureType, _currentIQueryFeature as global::Microsoft.AspNetCore.Http.Features.IQueryFeature); + yield return new KeyValuePair(IQueryFeatureType, _currentIQueryFeature as IQueryFeature); } if (_currentIFormFeature != null) { - yield return new KeyValuePair(IFormFeatureType, _currentIFormFeature as global::Microsoft.AspNetCore.Http.Features.IFormFeature); + yield return new KeyValuePair(IFormFeatureType, _currentIFormFeature as IFormFeature); } if (_currentIHttpUpgradeFeature != null) { - yield return new KeyValuePair(IHttpUpgradeFeatureType, _currentIHttpUpgradeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpUpgradeFeature); + yield return new KeyValuePair(IHttpUpgradeFeatureType, _currentIHttpUpgradeFeature as IHttpUpgradeFeature); } if (_currentIHttp2StreamIdFeature != null) { - yield return new KeyValuePair(IHttp2StreamIdFeatureType, _currentIHttp2StreamIdFeature as global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttp2StreamIdFeature); + yield return new KeyValuePair(IHttp2StreamIdFeatureType, _currentIHttp2StreamIdFeature as IHttp2StreamIdFeature); } if (_currentIResponseCookiesFeature != null) { - yield return new KeyValuePair(IResponseCookiesFeatureType, _currentIResponseCookiesFeature as global::Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature); + yield return new KeyValuePair(IResponseCookiesFeatureType, _currentIResponseCookiesFeature as IResponseCookiesFeature); } if (_currentIItemsFeature != null) { - yield return new KeyValuePair(IItemsFeatureType, _currentIItemsFeature as global::Microsoft.AspNetCore.Http.Features.IItemsFeature); + yield return new KeyValuePair(IItemsFeatureType, _currentIItemsFeature as IItemsFeature); } if (_currentITlsConnectionFeature != null) { - yield return new KeyValuePair(ITlsConnectionFeatureType, _currentITlsConnectionFeature as global::Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature); + yield return new KeyValuePair(ITlsConnectionFeatureType, _currentITlsConnectionFeature as ITlsConnectionFeature); } if (_currentIHttpWebSocketFeature != null) { - yield return new KeyValuePair(IHttpWebSocketFeatureType, _currentIHttpWebSocketFeature as global::Microsoft.AspNetCore.Http.Features.IHttpWebSocketFeature); + yield return new KeyValuePair(IHttpWebSocketFeatureType, _currentIHttpWebSocketFeature as IHttpWebSocketFeature); } if (_currentISessionFeature != null) { - yield return new KeyValuePair(ISessionFeatureType, _currentISessionFeature as global::Microsoft.AspNetCore.Http.Features.ISessionFeature); + yield return new KeyValuePair(ISessionFeatureType, _currentISessionFeature as ISessionFeature); } if (_currentIHttpMaxRequestBodySizeFeature != null) { - yield return new KeyValuePair(IHttpMaxRequestBodySizeFeatureType, _currentIHttpMaxRequestBodySizeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature); + yield return new KeyValuePair(IHttpMaxRequestBodySizeFeatureType, _currentIHttpMaxRequestBodySizeFeature as IHttpMaxRequestBodySizeFeature); } if (_currentIHttpMinRequestBodyDataRateFeature != null) { - yield return new KeyValuePair(IHttpMinRequestBodyDataRateFeatureType, _currentIHttpMinRequestBodyDataRateFeature as global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinRequestBodyDataRateFeature); + yield return new KeyValuePair(IHttpMinRequestBodyDataRateFeatureType, _currentIHttpMinRequestBodyDataRateFeature as IHttpMinRequestBodyDataRateFeature); } if (_currentIHttpMinResponseDataRateFeature != null) { - yield return new KeyValuePair(IHttpMinResponseDataRateFeatureType, _currentIHttpMinResponseDataRateFeature as global::Microsoft.AspNetCore.Server.Kestrel.Core.Features.IHttpMinResponseDataRateFeature); + yield return new KeyValuePair(IHttpMinResponseDataRateFeatureType, _currentIHttpMinResponseDataRateFeature as IHttpMinResponseDataRateFeature); } if (_currentIHttpBodyControlFeature != null) { - yield return new KeyValuePair(IHttpBodyControlFeatureType, _currentIHttpBodyControlFeature as global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature); + yield return new KeyValuePair(IHttpBodyControlFeatureType, _currentIHttpBodyControlFeature as IHttpBodyControlFeature); } if (_currentIHttpSendFileFeature != null) { - yield return new KeyValuePair(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature); + yield return new KeyValuePair(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as IHttpSendFileFeature); } if (MaybeExtra != null) diff --git a/src/Kestrel.Core/Internal/Http2/Http2Stream.cs b/src/Kestrel.Core/Internal/Http2/Http2Stream.cs index 71457fae4..f74909d34 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Stream.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Stream.cs @@ -34,7 +34,7 @@ public Http2Stream(Http2StreamContext context) protected override void OnReset() { - FastFeatureSet(typeof(IHttp2StreamIdFeature), this); + ResetIHttp2StreamIdFeature(); } protected override void OnRequestProcessingEnded() diff --git a/test/Kestrel.Core.Tests/HttpProtocolFeatureCollectionTests.cs b/test/Kestrel.Core.Tests/HttpProtocolFeatureCollectionTests.cs new file mode 100644 index 000000000..3cc5a348f --- /dev/null +++ b/test/Kestrel.Core.Tests/HttpProtocolFeatureCollectionTests.cs @@ -0,0 +1,209 @@ +// 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. + +using System; +using System.Linq; +using System.IO.Pipelines; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Xunit; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests +{ + public class HttpProtocolFeatureCollectionTests : IDisposable + { + private readonly PipeFactory _pipeFactory; + private readonly Http1ConnectionContext _http1ConnectionContext; + private readonly Http1Connection _http1Connection; + private readonly IFeatureCollection _collection; + + public HttpProtocolFeatureCollectionTests() + { + _pipeFactory = new PipeFactory(); + var pair = _pipeFactory.CreateConnectionPair(); + + var serviceContext = new ServiceContext + { + HttpParserFactory = _ => new HttpParser(), + ServerOptions = new KestrelServerOptions() + }; + + _http1ConnectionContext = new Http1ConnectionContext + { + ServiceContext = serviceContext, + ConnectionFeatures = new FeatureCollection(), + PipeFactory = _pipeFactory, + Application = pair.Application, + Transport = pair.Transport + }; + + _http1Connection = new Http1Connection(application: null, context: _http1ConnectionContext); + _http1Connection.Reset(); + _collection = _http1Connection; + } + + public void Dispose() + { + _pipeFactory.Dispose(); + } + + [Fact] + public int FeaturesStartAsSelf() + { + var featureCount = 0; + foreach (var featureIter in _collection) + { + Type type = featureIter.Key; + if (type.IsAssignableFrom(typeof(HttpProtocol))) + { + var featureLookup = _collection[type]; + Assert.Same(featureLookup, featureIter.Value); + Assert.Same(featureLookup, _collection); + featureCount++; + } + } + + Assert.NotEqual(0, featureCount); + + return featureCount; + } + + [Fact] + public int FeaturesCanBeAssignedTo() + { + var featureCount = SetFeaturesToNonDefault(); + Assert.NotEqual(0, featureCount); + + featureCount = 0; + foreach (var feature in _collection) + { + Type type = feature.Key; + if (type.IsAssignableFrom(typeof(HttpProtocol))) + { + Assert.Same(_collection[type], feature.Value); + Assert.NotSame(_collection[type], _collection); + featureCount++; + } + } + + Assert.NotEqual(0, featureCount); + + return featureCount; + } + + [Fact] + public void FeaturesResetToSelf() + { + var featuresAssigned = SetFeaturesToNonDefault(); + _http1Connection.ResetFeatureCollection(); + var featuresReset = FeaturesStartAsSelf(); + + Assert.Equal(featuresAssigned, featuresReset); + } + + [Fact] + public void FeaturesByGenericSameAsByType() + { + var featuresAssigned = SetFeaturesToNonDefault(); + + CompareGenericGetterToIndexer(); + + _http1Connection.ResetFeatureCollection(); + var featuresReset = FeaturesStartAsSelf(); + + Assert.Equal(featuresAssigned, featuresReset); + } + + [Fact] + public void FeaturesSetByTypeSameAsGeneric() + { + _collection[typeof(IHttpRequestFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpResponseFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpRequestIdentifierFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpRequestLifetimeFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpConnectionFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpMaxRequestBodySizeFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpMinRequestBodyDataRateFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpMinResponseDataRateFeature)] = CreateHttp1Connection(); + _collection[typeof(IHttpBodyControlFeature)] = CreateHttp1Connection(); + + CompareGenericGetterToIndexer(); + + EachHttpProtocolFeatureSetAndUnique(); + } + + [Fact] + public void FeaturesSetByGenericSameAsByType() + { + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + _collection.Set(CreateHttp1Connection()); + + CompareGenericGetterToIndexer(); + + EachHttpProtocolFeatureSetAndUnique(); + } + + private void CompareGenericGetterToIndexer() + { + Assert.Same(_collection.Get(), _collection[typeof(IHttpRequestFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpResponseFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpRequestIdentifierFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpRequestLifetimeFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpConnectionFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpMaxRequestBodySizeFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpMinRequestBodyDataRateFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpMinResponseDataRateFeature)]); + Assert.Same(_collection.Get(), _collection[typeof(IHttpBodyControlFeature)]); + } + + private int EachHttpProtocolFeatureSetAndUnique() + { + int featureCount = 0; + foreach (var item in _collection) + { + Type type = item.Key; + if (type.IsAssignableFrom(typeof(HttpProtocol))) + { + Assert.Equal(1, _collection.Count(kv => ReferenceEquals(kv.Value, item.Value))); + + featureCount++; + } + } + + Assert.NotEqual(0, featureCount); + + return featureCount; + } + + private int SetFeaturesToNonDefault() + { + int featureCount = 0; + foreach (var feature in _collection) + { + Type type = feature.Key; + if (type.IsAssignableFrom(typeof(HttpProtocol))) + { + _collection[type] = CreateHttp1Connection(); + featureCount++; + } + } + + var protocolFeaturesCount = EachHttpProtocolFeatureSetAndUnique(); + + Assert.Equal(protocolFeaturesCount, featureCount); + + return featureCount; + } + + private HttpProtocol CreateHttp1Connection() => new Http1Connection(application: null, context: _http1ConnectionContext); + } +} diff --git a/tools/CodeGenerator/HttpProtocolFeatureCollection.cs b/tools/CodeGenerator/HttpProtocolFeatureCollection.cs index 6582f445e..9d42e7f9a 100644 --- a/tools/CodeGenerator/HttpProtocolFeatureCollection.cs +++ b/tools/CodeGenerator/HttpProtocolFeatureCollection.cs @@ -19,6 +19,12 @@ static string Each(IEnumerable values, Func formatter) return values.Select(formatter).Aggregate((a, b) => a + b); } + public class KnownFeature + { + public Type Type; + public int Index; + } + public static string GeneratedFile(string className) { var alwaysFeatures = new[] @@ -58,7 +64,12 @@ public static string GeneratedFile(string className) typeof(IHttpSendFileFeature), }; - var allFeatures = alwaysFeatures.Concat(commonFeatures).Concat(sometimesFeatures).Concat(rareFeatures); + var allFeatures = alwaysFeatures.Concat(commonFeatures).Concat(sometimesFeatures).Concat(rareFeatures) + .Select((type, index) => new KnownFeature + { + Type = type, + Index = index + }); // NOTE: This list MUST always match the set of feature interfaces implemented by HttpProtocol. // See also: src/Kestrel/Http/HttpProtocol.FeatureCollection.cs @@ -80,48 +91,95 @@ public static string GeneratedFile(string className) using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Http.Features.Authentication; +using Microsoft.AspNetCore.Server.Kestrel.Core.Features; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http {{ public partial class {className} {{{Each(allFeatures, feature => $@" - private static readonly Type {feature.Name}Type = typeof(global::{feature.FullName});")} + private static readonly Type {feature.Type.Name}Type = typeof({feature.Type.Name});")} {Each(allFeatures, feature => $@" - private object _current{feature.Name};")} + private object _current{feature.Type.Name};")} private void FastReset() {{{Each(implementedFeatures, feature => $@" _current{feature.Name} = this;")} - {Each(allFeatures.Where(f => !implementedFeatures.Contains(f)), feature => $@" - _current{feature.Name} = null;")} + {Each(allFeatures.Where(f => !implementedFeatures.Contains(f.Type)), feature => $@" + _current{feature.Type.Name} = null;")} + }} + + object IFeatureCollection.this[Type key] + {{ + get + {{ + object feature;{Each(allFeatures, feature => $@" + {(feature.Index != 0 ? "else " : "")}if (key == {feature.Type.Name}Type) + {{ + feature = _current{feature.Type.Name}; + }}")} + else + {{ + feature = ExtraFeatureGet(key); + }} + + return feature ?? ConnectionFeatures[key]; + }} + + set + {{ + _featureRevision++; + {Each(allFeatures, feature => $@" + {(feature.Index != 0 ? "else " : "")}if (key == {feature.Type.Name}Type) + {{ + _current{feature.Type.Name} = value; + }}")} + else + {{ + ExtraFeatureSet(key, value); + }} + }} }} - internal object FastFeatureGet(Type key) + void IFeatureCollection.Set(TFeature feature) {{{Each(allFeatures, feature => $@" - if (key == {feature.Name}Type) + {(feature.Index != 0 ? "else " : "")}if (typeof(TFeature) == typeof({feature.Type.Name})) {{ - return _current{feature.Name}; + _current{feature.Type.Name} = feature; }}")} - return ExtraFeatureGet(key); + else + {{ + ExtraFeatureSet(typeof(TFeature), feature); + }} }} - protected void FastFeatureSet(Type key, object feature) + TFeature IFeatureCollection.Get() {{ - _featureRevision++; - {Each(allFeatures, feature => $@" - if (key == {feature.Name}Type) + TFeature feature;{Each(allFeatures, feature => $@" + {(feature.Index != 0 ? "else " : "")}if (typeof(TFeature) == typeof({feature.Type.Name})) {{ - _current{feature.Name} = feature; - return; - }}")}; - ExtraFeatureSet(key, feature); + feature = (TFeature)_current{feature.Type.Name}; + }}")} + else + {{ + feature = (TFeature)(ExtraFeatureGet(typeof(TFeature))); + }} + + if (feature != null) + {{ + return feature; + }} + + return (TFeature)ConnectionFeatures[typeof(TFeature)]; }} private IEnumerable> FastEnumerable() {{{Each(allFeatures, feature => $@" - if (_current{feature.Name} != null) + if (_current{feature.Type.Name} != null) {{ - yield return new KeyValuePair({feature.Name}Type, _current{feature.Name} as global::{feature.FullName}); + yield return new KeyValuePair({feature.Type.Name}Type, _current{feature.Type.Name} as {feature.Type.Name}); }}")} if (MaybeExtra != null)