This commit is contained in:
parent
d99644ef9c
commit
f05827484e
|
|
@ -4,7 +4,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Microsoft.AspNetCore.Components.RenderTree;
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Rendering
|
namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
@ -707,18 +706,16 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we've found the last attribute, we can iterate backwards and process duplicates.
|
// Now that we've found the last attribute, we can iterate backwards and process duplicates.
|
||||||
var seenAttributeNames = (_seenAttributeNames ??= new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase));
|
var seenAttributeNames = (_seenAttributeNames ??= new Dictionary<string, int>(SimplifiedStringHashComparer.Instance));
|
||||||
for (var i = last; i >= first; i--)
|
for (var i = last; i >= first; i--)
|
||||||
{
|
{
|
||||||
ref var frame = ref buffer[i];
|
ref var frame = ref buffer[i];
|
||||||
Debug.Assert(frame.FrameTypeField == RenderTreeFrameType.Attribute, $"Frame type is {frame.FrameTypeField} at {i}");
|
Debug.Assert(frame.FrameTypeField == RenderTreeFrameType.Attribute, $"Frame type is {frame.FrameTypeField} at {i}");
|
||||||
|
|
||||||
if (!seenAttributeNames.TryGetValue(frame.AttributeNameField, out var index))
|
if (!seenAttributeNames.TryAdd(frame.AttributeNameField, i))
|
||||||
{
|
{
|
||||||
// This is the first time seeing this attribute name. Add to the dictionary and move on.
|
var index = seenAttributeNames[frame.AttributeNameField];
|
||||||
seenAttributeNames.Add(frame.AttributeNameField, i);
|
if (index < i)
|
||||||
}
|
|
||||||
else if (index < i)
|
|
||||||
{
|
{
|
||||||
// This attribute is overriding a "silent frame" where we didn't create a frame for an AddAttribute call.
|
// This attribute is overriding a "silent frame" where we didn't create a frame for an AddAttribute call.
|
||||||
// This is the case for a null event handler, or bool false value.
|
// This is the case for a null event handler, or bool false value.
|
||||||
|
|
@ -744,6 +741,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
// That gets you here, and there's no action to take.
|
// That gets you here, and there's no action to take.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This is the pass where we cleanup attributes that have been wiped out.
|
// This is the pass where we cleanup attributes that have been wiped out.
|
||||||
//
|
//
|
||||||
|
|
@ -780,7 +778,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var seenAttributeNames = (_seenAttributeNames ??= new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase));
|
var seenAttributeNames = (_seenAttributeNames ??= new Dictionary<string, int>(SimplifiedStringHashComparer.Instance));
|
||||||
seenAttributeNames[name] = _entries.Count; // See comment in ProcessAttributes for why this is OK.
|
seenAttributeNames[name] = _entries.Count; // See comment in ProcessAttributes for why this is OK.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This comparer is optimized for use with dictionaries where the great majority of insertions/lookups
|
||||||
|
/// don't match existing entries. For example, when building a dictionary of almost entirely unique keys.
|
||||||
|
/// It's faster than the normal string comparer in this case because it doesn't use string.GetHashCode,
|
||||||
|
/// and hence doesn't have to consider every character in the string.
|
||||||
|
///
|
||||||
|
/// This primary scenario is <see cref="RenderTreeBuilder.ProcessDuplicateAttributes(int)"/>, which needs
|
||||||
|
/// to detect when one attribute is overriding another, but in the vast majority of cases attributes don't
|
||||||
|
/// actually override each other.
|
||||||
|
/// </summary>
|
||||||
|
internal class SimplifiedStringHashComparer : IEqualityComparer<string>
|
||||||
|
{
|
||||||
|
public readonly static SimplifiedStringHashComparer Instance = new SimplifiedStringHashComparer();
|
||||||
|
|
||||||
|
public bool Equals(string? x, string? y)
|
||||||
|
{
|
||||||
|
return string.Equals(x, y, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode(string key)
|
||||||
|
{
|
||||||
|
var keyLength = key.Length;
|
||||||
|
if (keyLength > 0)
|
||||||
|
{
|
||||||
|
// Consider just the length and middle and last characters.
|
||||||
|
// This will produce a distinct result for a sufficiently large
|
||||||
|
// proportion of attribute names.
|
||||||
|
return unchecked(
|
||||||
|
char.ToLowerInvariant(key[keyLength - 1])
|
||||||
|
+ 31 * char.ToLowerInvariant(key[keyLength / 2])
|
||||||
|
+ 961 * keyLength);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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 Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
{
|
||||||
|
public class SimplifiedStringHashComparerTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void EqualityIsCaseInsensitive()
|
||||||
|
{
|
||||||
|
Assert.True(SimplifiedStringHashComparer.Instance.Equals("abc", "ABC"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HashCodesAreCaseInsensitive()
|
||||||
|
{
|
||||||
|
var hash1 = SimplifiedStringHashComparer.Instance.GetHashCode("abc");
|
||||||
|
var hash2 = SimplifiedStringHashComparer.Instance.GetHashCode("ABC");
|
||||||
|
Assert.Equal(hash1, hash2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue