4
4
using System ;
5
5
using System . Collections . Generic ;
6
6
using System . Diagnostics ;
7
+ using System . Diagnostics . CodeAnalysis ;
7
8
using System . Runtime . CompilerServices ;
8
9
using Microsoft . AspNetCore . Components . Profiling ;
9
10
using Microsoft . AspNetCore . Components . RenderTree ;
@@ -28,7 +29,7 @@ public sealed class RenderTreeBuilder : IDisposable
28
29
private readonly Stack < int > _openElementIndices = new Stack < int > ( ) ;
29
30
private RenderTreeFrameType ? _lastNonAttributeFrameType ;
30
31
private bool _hasSeenAddMultipleAttributes ;
31
- private Dictionary < string , int > ? _seenAttributeNames ;
32
+ private SimpleStringIntDict ? _seenAttributeNames ;
32
33
33
34
/// <summary>
34
35
/// The reserved parameter name used for supplying child content.
@@ -782,41 +783,39 @@ internal void ProcessDuplicateAttributes(int first)
782
783
}
783
784
784
785
// Now that we've found the last attribute, we can iterate backwards and process duplicates.
785
- var seenAttributeNames = ( _seenAttributeNames ??= new Dictionary < string , int > ( StringComparer . OrdinalIgnoreCase ) ) ;
786
+ var seenAttributeNames = ( _seenAttributeNames ??= new SimpleStringIntDict ( ) ) ;
786
787
for ( var i = last ; i >= first ; i -- )
787
788
{
788
789
ref var frame = ref buffer [ i ] ;
789
790
Debug . Assert ( frame . FrameType == RenderTreeFrameType . Attribute , $ "Frame type is { frame . FrameType } at { i } ") ;
790
791
791
- if ( ! seenAttributeNames . TryGetValue ( frame . AttributeName , out var index ) )
792
+ if ( ! seenAttributeNames . TryAdd ( frame . AttributeName , i , out var index ) )
792
793
{
793
- // This is the first time seeing this attribute name. Add to the dictionary and move on.
794
- seenAttributeNames . Add ( frame . AttributeName , i ) ;
795
- }
796
- else if ( index < i )
797
- {
798
- // This attribute is overriding a "silent frame" where we didn't create a frame for an AddAttribute call.
799
- // This is the case for a null event handler, or bool false value.
800
- //
801
- // We need to update our tracking, in case the attribute appeared 3 or more times.
802
- seenAttributeNames [ frame . AttributeName ] = i ;
803
- }
804
- else if ( index > i )
805
- {
806
- // This attribute has been overridden. For now, blank out its name to *mark* it. We'll do a pass
807
- // later to wipe it out.
808
- frame = default ;
809
- }
810
- else
811
- {
812
- // OK so index == i. How is that possible? Well it's possible for a "silent frame" immediately
813
- // followed by setting the same attribute. Think of it this way, when we create a "silent frame"
814
- // we have to track that attribute name with *some* index.
815
- //
816
- // The only index value we can safely use is _entries.Count (next available). This is fine because
817
- // we never use these indexes to look stuff up, only for comparison.
818
- //
819
- // That gets you here, and there's no action to take.
794
+ if ( index < i )
795
+ {
796
+ // This attribute is overriding a "silent frame" where we didn't create a frame for an AddAttribute call.
797
+ // This is the case for a null event handler, or bool false value.
798
+ //
799
+ // We need to update our tracking, in case the attribute appeared 3 or more times.
800
+ seenAttributeNames . Replace ( frame . AttributeName , index ) ;
801
+ }
802
+ else if ( index > i )
803
+ {
804
+ // This attribute has been overridden. For now, blank out its name to *mark* it. We'll do a pass
805
+ // later to wipe it out.
806
+ frame = default ;
807
+ }
808
+ else
809
+ {
810
+ // OK so index == i. How is that possible? Well it's possible for a "silent frame" immediately
811
+ // followed by setting the same attribute. Think of it this way, when we create a "silent frame"
812
+ // we have to track that attribute name with *some* index.
813
+ //
814
+ // The only index value we can safely use is _entries.Count (next available). This is fine because
815
+ // we never use these indexes to look stuff up, only for comparison.
816
+ //
817
+ // That gets you here, and there's no action to take.
818
+ }
820
819
}
821
820
}
822
821
@@ -856,8 +855,8 @@ internal void TrackAttributeName(string name)
856
855
return ;
857
856
}
858
857
859
- var seenAttributeNames = ( _seenAttributeNames ??= new Dictionary < string , int > ( StringComparer . OrdinalIgnoreCase ) ) ;
860
- seenAttributeNames [ name ] = _entries . Count ; // See comment in ProcessAttributes for why this is OK.
858
+ var seenAttributeNames = ( _seenAttributeNames ??= new SimpleStringIntDict ( ) ) ;
859
+ seenAttributeNames . TryAdd ( name , _entries . Count , out _ ) ; // See comment in ProcessAttributes for why this is OK.
861
860
}
862
861
863
862
void IDisposable . Dispose ( )
@@ -879,5 +878,83 @@ private static void ProfilingStart([CallerMemberName] string? name = null)
879
878
[ Conditional ( "Profile_RenderTreeBuilder" ) ]
880
879
private static void ProfilingEnd ( [ CallerMemberName ] string ? name = null )
881
880
=> ComponentsProfiling . Instance . End ( name ) ;
881
+
882
+ private class SimpleStringIntDict
883
+ {
884
+ private string [ ] _keys = new string [ 100 ] ;
885
+ private int [ ] _values = new int [ 100 ] ;
886
+
887
+ public bool TryAdd ( string key , int value , out int existingValue )
888
+ {
889
+ if ( TryFindIndex ( key , out var index ) )
890
+ {
891
+ existingValue = _values [ index ] ;
892
+ return false ;
893
+ }
894
+ else if ( index >= 0 )
895
+ {
896
+ _keys [ index ] = key ;
897
+ _values [ index ] = value ;
898
+ existingValue = default ;
899
+ return true ;
900
+ }
901
+ else
902
+ {
903
+ throw new InvalidOperationException ( "Storage is full" ) ;
904
+ }
905
+ }
906
+
907
+ public void Replace ( string key , int value )
908
+ {
909
+ if ( TryFindIndex ( key , out var index ) )
910
+ {
911
+ _values [ index ] = value ;
912
+ }
913
+ else
914
+ {
915
+ throw new InvalidOperationException ( "Key not found" ) ;
916
+ }
917
+ }
918
+
919
+ public void Clear ( )
920
+ {
921
+ Array . Clear ( _keys , 0 , _keys . Length ) ;
922
+ Array . Clear ( _values , 0 , _values . Length ) ;
923
+ }
924
+
925
+ private bool TryFindIndex ( string key , out int existingIndexOrInsertionPosition )
926
+ {
927
+ var numBuckets = _keys . Length ;
928
+ var startIndex = key . Length == 0 ? 0 : char . ToLowerInvariant ( key [ key . Length / 2 ] ) ;
929
+ startIndex = startIndex * 31 + char . ToLowerInvariant ( key [ 0 ] ) ;
930
+ startIndex = startIndex % numBuckets ;
931
+ var candidateIndex = startIndex ;
932
+
933
+ do
934
+ {
935
+ var candidateKey = _keys [ candidateIndex ] ;
936
+ if ( candidateKey == null )
937
+ {
938
+ existingIndexOrInsertionPosition = candidateIndex ;
939
+ return false ;
940
+ }
941
+
942
+ if ( string . Equals ( candidateKey , key , StringComparison . OrdinalIgnoreCase ) )
943
+ {
944
+ existingIndexOrInsertionPosition = candidateIndex ;
945
+ return true ;
946
+ }
947
+
948
+ if ( ++ candidateIndex >= numBuckets )
949
+ {
950
+ candidateIndex = 0 ;
951
+ }
952
+ }
953
+ while ( candidateIndex != startIndex ) ;
954
+
955
+ existingIndexOrInsertionPosition = - 1 ;
956
+ return false ;
957
+ }
958
+ }
882
959
}
883
960
}
0 commit comments