Cleanup
This commit is contained in:
52
src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs
Normal file
52
src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a composite key of (Type, Type) for fast dictionaries.
|
||||
/// </summary>
|
||||
internal struct CompositeTypeTypeKey : IEquatable<CompositeTypeTypeKey>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CompositeTypeTypeKey"/> struct.
|
||||
/// </summary>
|
||||
public CompositeTypeTypeKey(Type type1, Type type2)
|
||||
{
|
||||
Type1 = type1;
|
||||
Type2 = type2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first type.
|
||||
/// </summary>
|
||||
public Type Type1 { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the second type.
|
||||
/// </summary>
|
||||
public Type Type2 { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(CompositeTypeTypeKey other)
|
||||
=> Type1 == other.Type1 && Type2 == other.Type2;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
=> obj is CompositeTypeTypeKey other && Type1 == other.Type1 && Type2 == other.Type2;
|
||||
|
||||
public static bool operator ==(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2)
|
||||
=> key1.Type1 == key2.Type1 && key1.Type2 == key2.Type2;
|
||||
|
||||
public static bool operator !=(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2)
|
||||
=> key1.Type1 != key2.Type1 || key1.Type2 != key2.Type2;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (Type1.GetHashCode() * 397) ^ Type2.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,9 @@ using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Xml;
|
||||
using Umbraco.Core.Collections;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
@@ -14,19 +15,19 @@ namespace Umbraco.Core
|
||||
/// Provides object extension methods.
|
||||
/// </summary>
|
||||
public static class ObjectExtensions
|
||||
{
|
||||
// Used for caching the various type lookups
|
||||
{
|
||||
// Cache the various type lookups
|
||||
private static readonly Dictionary<Type, Type> NullableGenericCache = new Dictionary<Type, Type>();
|
||||
private static readonly Dictionary<TypePair, TypeConverter> InputTypeConverterCache = new Dictionary<TypePair, TypeConverter>();
|
||||
private static readonly Dictionary<TypePair, TypeConverter> DestinationTypeConverterCache = new Dictionary<TypePair, TypeConverter>();
|
||||
private static readonly Dictionary<TypePair, IConvertible> AssignableTypeCache = new Dictionary<TypePair, IConvertible>();
|
||||
private static readonly Dictionary<CompositeTypeTypeKey, TypeConverter> InputTypeConverterCache = new Dictionary<CompositeTypeTypeKey, TypeConverter>();
|
||||
private static readonly Dictionary<CompositeTypeTypeKey, TypeConverter> DestinationTypeConverterCache = new Dictionary<CompositeTypeTypeKey, TypeConverter>();
|
||||
private static readonly Dictionary<CompositeTypeTypeKey, IConvertible> AssignableTypeCache = new Dictionary<CompositeTypeTypeKey, IConvertible>();
|
||||
private static readonly Dictionary<Type, bool> BoolConvertCache = new Dictionary<Type, bool>();
|
||||
|
||||
private static readonly char[] NumberDecimalSeparatorsToNormalize = { '.', ',' };
|
||||
private static readonly CustomBooleanTypeConverter CustomBooleanTypeConverter = new CustomBooleanTypeConverter();
|
||||
|
||||
//private static readonly ConcurrentDictionary<Type, Func<object>> ObjectFactoryCache = new ConcurrentDictionary<Type, Func<object>>();
|
||||
|
||||
private static readonly CustomBooleanTypeConverter CustomBooleanTypeConverter = new CustomBooleanTypeConverter();
|
||||
|
||||
//private static readonly ConcurrentDictionary<Type, Func<object>> ObjectFactoryCache = new ConcurrentDictionary<Type, Func<object>>();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -71,62 +72,60 @@ namespace Umbraco.Core
|
||||
/// <returns>The <see cref="Attempt{T}"/></returns>
|
||||
public static Attempt<T> TryConvertTo<T>(this object input)
|
||||
{
|
||||
Attempt<object> result = TryConvertTo(input, typeof(T));
|
||||
if (!result.Success)
|
||||
{
|
||||
// Just try a straight up conversion
|
||||
try
|
||||
{
|
||||
var converted = (T)input;
|
||||
return Attempt<T>.Succeed(converted);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Attempt<T>.Fail(e);
|
||||
}
|
||||
}
|
||||
var result = TryConvertTo(input, typeof(T));
|
||||
|
||||
if (result.Success)
|
||||
return Attempt<T>.Succeed((T) result.Result);
|
||||
|
||||
// just try to cast
|
||||
try
|
||||
{
|
||||
return Attempt<T>.Succeed((T) input);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Attempt<T>.Fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
return Attempt<T>.Succeed((T)result.Result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to convert the input object to the output type.
|
||||
/// </summary>
|
||||
/// <remarks>This code is an optimized version of the original Umbraco method</remarks>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="destinationType">The type to convert to</param>
|
||||
/// <param name="target">The type to convert to</param>
|
||||
/// <returns>The <see cref="Attempt{Object}"/></returns>
|
||||
public static Attempt<object> TryConvertTo(this object input, Type destinationType)
|
||||
public static Attempt<object> TryConvertTo(this object input, Type target)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return Attempt<object>.Fail();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (destinationType == null)
|
||||
{
|
||||
return Attempt<object>.Fail();
|
||||
}
|
||||
|
||||
if (input == null)
|
||||
{
|
||||
// Nullable is ok
|
||||
if (destinationType.IsGenericType && GetCachedGenericNullableType(destinationType) != null)
|
||||
if (target.IsGenericType && GetCachedGenericNullableType(target) != null)
|
||||
{
|
||||
return Attempt<object>.Succeed(null);
|
||||
}
|
||||
|
||||
// Reference types are ok
|
||||
return Attempt<object>.SucceedIf(!destinationType.IsValueType, null);
|
||||
return Attempt<object>.SucceedIf(target.IsValueType == false, null);
|
||||
}
|
||||
|
||||
Type inputType = input.GetType();
|
||||
var inputType = input.GetType();
|
||||
|
||||
// Easy
|
||||
if (destinationType == typeof(object) || inputType == destinationType)
|
||||
if (target == typeof(object) || inputType == target)
|
||||
{
|
||||
return Attempt.Succeed(input);
|
||||
}
|
||||
|
||||
// Check for string so that overloaders of ToString() can take advantage of the conversion.
|
||||
if (destinationType == typeof(string))
|
||||
if (target == typeof(string))
|
||||
{
|
||||
return Attempt<object>.Succeed(input.ToString());
|
||||
}
|
||||
@@ -134,42 +133,44 @@ namespace Umbraco.Core
|
||||
// If we've got a nullable of something, we try to convert directly to that thing.
|
||||
// We cache the destination type and underlying nullable types
|
||||
// Any other generic types need to fall through
|
||||
if (destinationType.IsGenericType)
|
||||
if (target.IsGenericType)
|
||||
{
|
||||
Type underlyingGenericType = GetCachedGenericNullableType(destinationType);
|
||||
if (underlyingGenericType != null)
|
||||
var underlying = GetCachedGenericNullableType(target);
|
||||
if (underlying != null)
|
||||
{
|
||||
// Special case for empty strings for bools/dates which should return null if an empty string.
|
||||
if (input is string asString)
|
||||
if (input is string inputString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(asString) && (underlyingGenericType == typeof(DateTime) || underlyingGenericType == typeof(bool)))
|
||||
if (string.IsNullOrEmpty(inputString) && (underlying == typeof(DateTime) || underlying == typeof(bool)))
|
||||
{
|
||||
return Attempt<object>.Succeed(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively call into this method with the inner (not-nullable) type and handle the outcome
|
||||
Attempt<object> nonNullable = input.TryConvertTo(underlyingGenericType);
|
||||
var inner = input.TryConvertTo(underlying);
|
||||
|
||||
// And if sucessful, fall on through to rewrap in a nullable; if failed, pass on the exception
|
||||
if (nonNullable.Success)
|
||||
if (inner.Success)
|
||||
{
|
||||
input = nonNullable.Result; // Now fall on through...
|
||||
input = inner.Result; // Now fall on through...
|
||||
}
|
||||
else
|
||||
{
|
||||
return Attempt<object>.Fail(nonNullable.Exception);
|
||||
return Attempt<object>.Fail(inner.Exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (input is string asString)
|
||||
{
|
||||
// target is not a generic type
|
||||
|
||||
if (input is string inputString)
|
||||
{
|
||||
// Try convert from string, returns an Attempt if the string could be
|
||||
// processed (either succeeded or failed), else null if we need to try
|
||||
// other methods
|
||||
Attempt<object>? result = TryConvertToFromString(asString, destinationType);
|
||||
var result = TryConvertToFromString(inputString, target);
|
||||
if (result.HasValue)
|
||||
{
|
||||
return result.Value;
|
||||
@@ -178,14 +179,14 @@ namespace Umbraco.Core
|
||||
|
||||
// TODO: Do a check for destination type being IEnumerable<T> and source type implementing IEnumerable<T> with
|
||||
// the same 'T', then we'd have to find the extension method for the type AsEnumerable() and execute it.
|
||||
IConvertible convertible = GetCachedAssignableConvertibleResult(input, inputType, destinationType);
|
||||
var convertible = GetCachedAssignableConvertibleResult(input, inputType, target);
|
||||
if (convertible != null)
|
||||
{
|
||||
return Attempt.Succeed(Convert.ChangeType(convertible, destinationType));
|
||||
return Attempt.Succeed(Convert.ChangeType(convertible, target));
|
||||
}
|
||||
}
|
||||
|
||||
if (destinationType == typeof(bool))
|
||||
if (target == typeof(bool))
|
||||
{
|
||||
if (GetCanConvertToBooleanResult(inputType))
|
||||
{
|
||||
@@ -193,13 +194,13 @@ namespace Umbraco.Core
|
||||
}
|
||||
}
|
||||
|
||||
TypeConverter inputConverter = GetCachedInputTypeConverter(inputType, destinationType);
|
||||
var inputConverter = GetCachedSourceTypeConverter(inputType, target);
|
||||
if (inputConverter != null)
|
||||
{
|
||||
return Attempt.Succeed(inputConverter.ConvertTo(input, destinationType));
|
||||
return Attempt.Succeed(inputConverter.ConvertTo(input, target));
|
||||
}
|
||||
|
||||
TypeConverter outputConverter = GetCachedDestinationTypeConverter(inputType, destinationType);
|
||||
var outputConverter = GetCachedTargetTypeConverter(inputType, target);
|
||||
if (outputConverter != null)
|
||||
{
|
||||
return Attempt.Succeed(outputConverter.ConvertFrom(input));
|
||||
@@ -208,7 +209,7 @@ namespace Umbraco.Core
|
||||
// Re-check convertables since we altered the input through recursion
|
||||
if (input is IConvertible convertible2)
|
||||
{
|
||||
return Attempt.Succeed(Convert.ChangeType(convertible2, destinationType));
|
||||
return Attempt.Succeed(Convert.ChangeType(convertible2, target));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -217,19 +218,19 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
return Attempt<object>.Fail();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to convert the input string to the output type.
|
||||
/// </summary>
|
||||
/// <remarks>This code is an optimized version of the original Umbraco method</remarks>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="destinationType">The type to convert to</param>
|
||||
/// <param name="target">The type to convert to</param>
|
||||
/// <returns>The <see cref="Nullable{Attempt}"/></returns>
|
||||
private static Attempt<object>? TryConvertToFromString(this string input, Type destinationType)
|
||||
private static Attempt<object>? TryConvertToFromString(this string input, Type target)
|
||||
{
|
||||
// Easy
|
||||
if (destinationType == typeof(string))
|
||||
if (target == typeof(string))
|
||||
{
|
||||
return Attempt<object>.Succeed(input);
|
||||
}
|
||||
@@ -237,13 +238,13 @@ namespace Umbraco.Core
|
||||
// Null, empty, whitespaces
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
if (destinationType == typeof(bool))
|
||||
if (target == typeof(bool))
|
||||
{
|
||||
// null/empty = bool false
|
||||
return Attempt<object>.Succeed(false);
|
||||
}
|
||||
|
||||
if (destinationType == typeof(DateTime))
|
||||
if (target == typeof(DateTime))
|
||||
{
|
||||
// null/empty = min DateTime value
|
||||
return Attempt<object>.Succeed(DateTime.MinValue);
|
||||
@@ -260,37 +261,38 @@ namespace Umbraco.Core
|
||||
// By using a mixture of ordered if statements and switches we can optimize both for
|
||||
// fast conditional checking for most frequently used types and the branching
|
||||
// that does not depend on previous values available to switch statements.
|
||||
if (destinationType.IsPrimitive)
|
||||
if (target.IsPrimitive)
|
||||
{
|
||||
if (destinationType == typeof(int))
|
||||
if (target == typeof(int))
|
||||
{
|
||||
if (int.TryParse(input, out int value))
|
||||
if (int.TryParse(input, out var value))
|
||||
{
|
||||
return Attempt<object>.Succeed(value);
|
||||
}
|
||||
|
||||
// Because decimal 100.01m will happily convert to integer 100, it
|
||||
// makes sense that string "100.01" *also* converts to integer 100.
|
||||
string input2 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out decimal value2), Convert.ToInt32(value2));
|
||||
var input2 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out var value2), Convert.ToInt32(value2));
|
||||
}
|
||||
|
||||
if (destinationType == typeof(long))
|
||||
if (target == typeof(long))
|
||||
{
|
||||
if (long.TryParse(input, out long value))
|
||||
if (long.TryParse(input, out var value))
|
||||
{
|
||||
return Attempt<object>.Succeed(value);
|
||||
}
|
||||
|
||||
// Same as int
|
||||
string input2 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out decimal value2), Convert.ToInt64(value2));
|
||||
var input2 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out var value2), Convert.ToInt64(value2));
|
||||
}
|
||||
|
||||
// TODO: Should we do the decimal trick for short, byte, unsigned?
|
||||
if (destinationType == typeof(bool))
|
||||
// TODO: Should we do the decimal trick for short, byte, unsigned?
|
||||
|
||||
if (target == typeof(bool))
|
||||
{
|
||||
if (bool.TryParse(input, out bool value))
|
||||
if (bool.TryParse(input, out var value))
|
||||
{
|
||||
return Attempt<object>.Succeed(value);
|
||||
}
|
||||
@@ -300,45 +302,45 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
// Calling this method directly is faster than any attempt to cache it.
|
||||
switch (Type.GetTypeCode(destinationType))
|
||||
switch (Type.GetTypeCode(target))
|
||||
{
|
||||
case TypeCode.Int16:
|
||||
return Attempt<object>.SucceedIf(short.TryParse(input, out short value), value);
|
||||
return Attempt<object>.SucceedIf(short.TryParse(input, out var value), value);
|
||||
|
||||
case TypeCode.Double:
|
||||
string input2 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.SucceedIf(double.TryParse(input2, out double valueD), valueD);
|
||||
var input2 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.SucceedIf(double.TryParse(input2, out var valueD), valueD);
|
||||
|
||||
case TypeCode.Single:
|
||||
string input3 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.SucceedIf(float.TryParse(input3, out float valueF), valueF);
|
||||
var input3 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.SucceedIf(float.TryParse(input3, out var valueF), valueF);
|
||||
|
||||
case TypeCode.Char:
|
||||
return Attempt<object>.SucceedIf(char.TryParse(input, out char valueC), valueC);
|
||||
return Attempt<object>.SucceedIf(char.TryParse(input, out var valueC), valueC);
|
||||
|
||||
case TypeCode.Byte:
|
||||
return Attempt<object>.SucceedIf(byte.TryParse(input, out byte valueB), valueB);
|
||||
return Attempt<object>.SucceedIf(byte.TryParse(input, out var valueB), valueB);
|
||||
|
||||
case TypeCode.SByte:
|
||||
return Attempt<object>.SucceedIf(sbyte.TryParse(input, out sbyte valueSb), valueSb);
|
||||
return Attempt<object>.SucceedIf(sbyte.TryParse(input, out var valueSb), valueSb);
|
||||
|
||||
case TypeCode.UInt32:
|
||||
return Attempt<object>.SucceedIf(uint.TryParse(input, out uint valueU), valueU);
|
||||
return Attempt<object>.SucceedIf(uint.TryParse(input, out var valueU), valueU);
|
||||
|
||||
case TypeCode.UInt16:
|
||||
return Attempt<object>.SucceedIf(ushort.TryParse(input, out ushort valueUs), valueUs);
|
||||
return Attempt<object>.SucceedIf(ushort.TryParse(input, out var valueUs), valueUs);
|
||||
|
||||
case TypeCode.UInt64:
|
||||
return Attempt<object>.SucceedIf(ulong.TryParse(input, out ulong valueUl), valueUl);
|
||||
return Attempt<object>.SucceedIf(ulong.TryParse(input, out var valueUl), valueUl);
|
||||
}
|
||||
}
|
||||
else if (destinationType == typeof(Guid))
|
||||
else if (target == typeof(Guid))
|
||||
{
|
||||
return Attempt<object>.SucceedIf(Guid.TryParse(input, out Guid value), value);
|
||||
return Attempt<object>.SucceedIf(Guid.TryParse(input, out var value), value);
|
||||
}
|
||||
else if (destinationType == typeof(DateTime))
|
||||
else if (target == typeof(DateTime))
|
||||
{
|
||||
if (DateTime.TryParse(input, out DateTime value))
|
||||
if (DateTime.TryParse(input, out var value))
|
||||
{
|
||||
switch (value.Kind)
|
||||
{
|
||||
@@ -356,27 +358,27 @@ namespace Umbraco.Core
|
||||
|
||||
return Attempt<object>.Fail();
|
||||
}
|
||||
else if (destinationType == typeof(DateTimeOffset))
|
||||
else if (target == typeof(DateTimeOffset))
|
||||
{
|
||||
return Attempt<object>.SucceedIf(DateTimeOffset.TryParse(input, out DateTimeOffset value), value);
|
||||
return Attempt<object>.SucceedIf(DateTimeOffset.TryParse(input, out var value), value);
|
||||
}
|
||||
else if (destinationType == typeof(TimeSpan))
|
||||
else if (target == typeof(TimeSpan))
|
||||
{
|
||||
return Attempt<object>.SucceedIf(TimeSpan.TryParse(input, out TimeSpan value), value);
|
||||
return Attempt<object>.SucceedIf(TimeSpan.TryParse(input, out var value), value);
|
||||
}
|
||||
else if (destinationType == typeof(decimal))
|
||||
else if (target == typeof(decimal))
|
||||
{
|
||||
string input2 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out decimal value), value);
|
||||
var input2 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.SucceedIf(decimal.TryParse(input2, out var value), value);
|
||||
}
|
||||
else if (input != null && destinationType == typeof(Version))
|
||||
else if (input != null && target == typeof(Version))
|
||||
{
|
||||
return Attempt<object>.SucceedIf(Version.TryParse(input, out Version value), value);
|
||||
return Attempt<object>.SucceedIf(Version.TryParse(input, out var value), value);
|
||||
}
|
||||
|
||||
// E_NOTIMPL IPAddress, BigInteger
|
||||
return null; // We can't decide...
|
||||
}
|
||||
}
|
||||
internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname)
|
||||
{
|
||||
//TODO: Localise this exception
|
||||
@@ -641,115 +643,100 @@ namespace Umbraco.Core
|
||||
internal static Guid AsGuid(this object value)
|
||||
{
|
||||
return value is Guid ? (Guid) value : Guid.Empty;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static string NormalizeNumberDecimalSeparator(string s)
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static string NormalizeNumberDecimalSeparator(string s)
|
||||
{
|
||||
var normalized = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator[0];
|
||||
return s.ReplaceMany(NumberDecimalSeparatorsToNormalize, normalized);
|
||||
}
|
||||
return s.ReplaceMany(NumberDecimalSeparatorsToNormalize, normalized);
|
||||
}
|
||||
|
||||
// gets a converter for source, that can convert to target, or null if none exists
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static TypeConverter GetCachedInputTypeConverter(Type inputType, Type destinationType)
|
||||
private static TypeConverter GetCachedSourceTypeConverter(Type source, Type target)
|
||||
{
|
||||
var key = new TypePair(inputType, destinationType);
|
||||
if (!InputTypeConverterCache.ContainsKey(key))
|
||||
var key = new CompositeTypeTypeKey(source, target);
|
||||
if (InputTypeConverterCache.TryGetValue(key, out var cached))
|
||||
return cached;
|
||||
|
||||
var converter = TypeDescriptor.GetConverter(source);
|
||||
if (converter.CanConvertTo(target))
|
||||
{
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(inputType);
|
||||
if (converter.CanConvertTo(destinationType))
|
||||
{
|
||||
InputTypeConverterCache[key] = converter;
|
||||
return converter;
|
||||
}
|
||||
else
|
||||
{
|
||||
InputTypeConverterCache[key] = null;
|
||||
return null;
|
||||
}
|
||||
InputTypeConverterCache[key] = converter;
|
||||
return converter;
|
||||
}
|
||||
|
||||
return InputTypeConverterCache[key];
|
||||
InputTypeConverterCache[key] = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// gets a converter for target, that can convert from source, or null if none exists
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static TypeConverter GetCachedDestinationTypeConverter(Type inputType, Type destinationType)
|
||||
private static TypeConverter GetCachedTargetTypeConverter(Type source, Type target)
|
||||
{
|
||||
var key = new TypePair(inputType, destinationType);
|
||||
if (!DestinationTypeConverterCache.ContainsKey(key))
|
||||
var key = new CompositeTypeTypeKey(source, target);
|
||||
if (DestinationTypeConverterCache.TryGetValue(key, out var cached))
|
||||
return cached;
|
||||
|
||||
var converter = TypeDescriptor.GetConverter(target);
|
||||
if (converter.CanConvertFrom(source))
|
||||
{
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(destinationType);
|
||||
if (converter.CanConvertFrom(inputType))
|
||||
{
|
||||
DestinationTypeConverterCache[key] = converter;
|
||||
return converter;
|
||||
}
|
||||
else
|
||||
{
|
||||
DestinationTypeConverterCache[key] = null;
|
||||
return null;
|
||||
}
|
||||
DestinationTypeConverterCache[key] = converter;
|
||||
return converter;
|
||||
}
|
||||
|
||||
return DestinationTypeConverterCache[key];
|
||||
DestinationTypeConverterCache[key] = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// gets the underlying type of a nullable type, or null if the type is not nullable
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Type GetCachedGenericNullableType(Type destinationType)
|
||||
private static Type GetCachedGenericNullableType(Type type)
|
||||
{
|
||||
if (!NullableGenericCache.ContainsKey(destinationType))
|
||||
if (NullableGenericCache.TryGetValue(type, out var cached))
|
||||
return cached;
|
||||
|
||||
if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
if (destinationType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
Type underlying = Nullable.GetUnderlyingType(destinationType);
|
||||
NullableGenericCache[destinationType] = underlying;
|
||||
return underlying;
|
||||
}
|
||||
else
|
||||
{
|
||||
NullableGenericCache[destinationType] = null;
|
||||
return null;
|
||||
}
|
||||
var underlying = Nullable.GetUnderlyingType(type);
|
||||
NullableGenericCache[type] = underlying;
|
||||
return underlying;
|
||||
}
|
||||
|
||||
return NullableGenericCache[destinationType];
|
||||
NullableGenericCache[type] = null;
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// gets an IConvertible from source to target type, or null if none exists
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static IConvertible GetCachedAssignableConvertibleResult(object input, Type inputType, Type destinationType)
|
||||
private static IConvertible GetCachedAssignableConvertibleResult(object input, Type source, Type target)
|
||||
{
|
||||
var key = new TypePair(inputType, destinationType);
|
||||
if (!AssignableTypeCache.ContainsKey(key))
|
||||
var key = new CompositeTypeTypeKey(source, target);
|
||||
if (AssignableTypeCache.TryGetValue(key, out var cached))
|
||||
return cached;
|
||||
|
||||
if (target.IsAssignableFrom(source) && input is IConvertible convertible)
|
||||
{
|
||||
if (destinationType.IsAssignableFrom(inputType))
|
||||
{
|
||||
if (input is IConvertible convertable)
|
||||
{
|
||||
AssignableTypeCache[key] = convertable;
|
||||
return convertable;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AssignableTypeCache[key] = null;
|
||||
return null;
|
||||
}
|
||||
AssignableTypeCache[key] = convertible;
|
||||
return convertible;
|
||||
}
|
||||
|
||||
return AssignableTypeCache[key];
|
||||
AssignableTypeCache[key] = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// determines whether a type can be converted to boolean
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool GetCanConvertToBooleanResult(Type inputType)
|
||||
private static bool GetCanConvertToBooleanResult(Type type)
|
||||
{
|
||||
if (!BoolConvertCache.ContainsKey(inputType))
|
||||
{
|
||||
bool canConvert = CustomBooleanTypeConverter.CanConvertFrom(inputType);
|
||||
BoolConvertCache[inputType] = canConvert;
|
||||
return canConvert;
|
||||
}
|
||||
if (BoolConvertCache.TryGetValue(type, out var cached))
|
||||
return cached;
|
||||
|
||||
return BoolConvertCache[inputType];
|
||||
}
|
||||
var canConvert = CustomBooleanTypeConverter.CanConvertFrom(type);
|
||||
BoolConvertCache[type] = canConvert;
|
||||
return canConvert;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A lightweight struct for storing a pair of types for caching.
|
||||
/// </summary>
|
||||
internal struct TypePair : IEquatable<TypePair>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TypePair"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="type1">The first type</param>
|
||||
/// <param name="type2">The second type</param>
|
||||
public TypePair(Type type1, Type type2)
|
||||
{
|
||||
this.Type1 = type1;
|
||||
this.Type2 = type2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first type
|
||||
/// </summary>
|
||||
public Type Type1 { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the second type
|
||||
/// </summary>
|
||||
public Type Type2 { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(TypePair other)
|
||||
{
|
||||
return this.Type1 == other.Type1 && this.Type2 == other.Type2;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return (obj is TypePair) && this.Equals((TypePair)obj);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hashCode = this.Type1.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ this.Type2.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1531,7 +1531,7 @@
|
||||
<Compile Include="ReadLock.cs" />
|
||||
<Compile Include="TypeFinder.cs" />
|
||||
<Compile Include="TypeHelper.cs" />
|
||||
<Compile Include="TypePair.cs" />
|
||||
<Compile Include="Collections\CompositeTypeTypeKey.cs" />
|
||||
<Compile Include="Udi.cs" />
|
||||
<Compile Include="UdiDefinitionAttribute.cs" />
|
||||
<Compile Include="UdiEntityType.cs" />
|
||||
|
||||
@@ -15,7 +15,7 @@ using ILogger = Umbraco.Core.Logging.ILogger;
|
||||
|
||||
namespace Umbraco.Tests.Benchmarks
|
||||
{
|
||||
[QuickRunWithMemoryDiagnoser]
|
||||
[QuickRunWithMemoryDiagnoserConfig]
|
||||
public class BulkInsertBenchmarks
|
||||
{
|
||||
private static byte[] _initDbBytes = null;
|
||||
|
||||
@@ -8,18 +8,26 @@ namespace Umbraco.Tests.Benchmarks.Config
|
||||
/// <summary>
|
||||
/// Configures the benchmark to run with less warmup and a shorter iteration time than the standard benchmark.
|
||||
/// </summary>
|
||||
public class QuickRunAttribute : Attribute, IConfigSource
|
||||
public class QuickRunConfigAttribute : Attribute, IConfigSource
|
||||
{
|
||||
public QuickRunAttribute()
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QuickRunConfigAttribute"/> class.
|
||||
/// </summary>
|
||||
public QuickRunConfigAttribute()
|
||||
{
|
||||
Config = ManualConfig.CreateEmpty()
|
||||
Config = (ManualConfig) ManualConfig.CreateEmpty()
|
||||
.With(Job.Default.WithLaunchCount(1) // benchmark process will be launched only once
|
||||
.WithIterationTime(new TimeInterval(100, TimeUnit.Millisecond)) // 100ms per iteration
|
||||
.WithWarmupCount(3) // 3 warmup iteration
|
||||
.WithTargetCount(3)); // 3 target iteration
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the manual configuration.
|
||||
/// </summary>
|
||||
protected ManualConfig Config { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IConfig Config { get; }
|
||||
IConfig IConfigSource.Config => Config;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
|
||||
namespace Umbraco.Tests.Benchmarks.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures the benchmark to run with less warmup and a shorter iteration time than the standard benchmark.
|
||||
/// Memory usage diagnosis is included in the benchmark
|
||||
/// </summary>
|
||||
public class QuickRunWithMemoryDiagnoserAttribute : QuickRunAttribute
|
||||
{
|
||||
public QuickRunWithMemoryDiagnoserAttribute()
|
||||
{
|
||||
((ManualConfig)this.Config).Add(new MemoryDiagnoser());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
|
||||
namespace Umbraco.Tests.Benchmarks.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures the benchmark to run with less warmup and a shorter iteration time than the standard benchmark,
|
||||
/// and include memory usage diagnosis.
|
||||
/// </summary>
|
||||
public class QuickRunWithMemoryDiagnoserConfigAttribute : QuickRunConfigAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QuickRunWithMemoryDiagnoserConfigAttribute"/> class.
|
||||
/// </summary>
|
||||
public QuickRunWithMemoryDiagnoserConfigAttribute()
|
||||
{
|
||||
Config.Add(new MemoryDiagnoser());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,97 +2,132 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Tests.Benchmarks.Config;
|
||||
|
||||
namespace Umbraco.Tests.Benchmarks
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
[QuickRunWithMemoryDiagnoserConfig]
|
||||
public class StringReplaceManyBenchmarks
|
||||
{
|
||||
private static string text = "1,2.3:4&5#6";
|
||||
/*
|
||||
|
||||
private static char[] chars = new[] { ',', '.', ':', '&', '#' };
|
||||
short text, short replacement:
|
||||
|
||||
private static char replacement = '*';
|
||||
Method | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated |
|
||||
--------------------------------------------- |---------:|----------:|----------:|-------:|---------:|-------:|----------:|
|
||||
'String.ReplaceMany w/chars - Aggregate' | 236.0 ns | 40.92 ns | 2.312 ns | 1.00 | 0.00 | 0.0461 | 200 B |
|
||||
'String.ReplaceMany w/chars - For Loop' | 166.7 ns | 70.51 ns | 3.984 ns | 0.71 | 0.01 | 0.0420 | 180 B |
|
||||
'String.ReplaceMany w/dictionary - Aggregate' | 606.5 ns | 342.94 ns | 19.377 ns | 2.57 | 0.07 | 0.0473 | 212 B |
|
||||
'String.ReplaceMany w/dictionary - For Each' | 571.8 ns | 232.33 ns | 13.127 ns | 2.42 | 0.05 | 0.0458 | 212 B |
|
||||
|
||||
private static Dictionary<string, string> replacements = new Dictionary<string, string>(5)
|
||||
long text, short replacement:
|
||||
|
||||
Method | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated |
|
||||
--------------------------------------------- |----------:|----------:|----------:|-------:|---------:|-------:|----------:|
|
||||
'String.ReplaceMany w/chars - Aggregate' | 5.771 us | 9.963 us | 0.5630 us | 1.00 | 0.00 | 1.6798 | 6.94 KB |
|
||||
'String.ReplaceMany w/chars - For Loop' | 4.962 us | 2.121 us | 0.1199 us | 0.87 | 0.08 | 1.6840 | 6.92 KB |
|
||||
'String.ReplaceMany w/dictionary - Aggregate' | 14.514 us | 8.189 us | 0.4627 us | 2.53 | 0.22 | 1.6447 | 6.96 KB |
|
||||
'String.ReplaceMany w/dictionary - For Each' | 15.445 us | 24.745 us | 1.3981 us | 2.69 | 0.30 | 1.5696 | 6.96 KB |
|
||||
|
||||
short text, long replacements dictionary:
|
||||
|
||||
Method | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated |
|
||||
--------------------------------------------- |-----------:|-----------:|----------:|-------:|---------:|-------:|----------:|
|
||||
'String.ReplaceMany w/chars - Aggregate' | 257.0 ns | 200.0 ns | 11.30 ns | 1.00 | 0.00 | 0.0452 | 200 B |
|
||||
'String.ReplaceMany w/chars - For Loop' | 182.4 ns | 221.0 ns | 12.49 ns | 0.71 | 0.05 | 0.0425 | 180 B |
|
||||
'String.ReplaceMany w/dictionary - Aggregate' | 7,273.8 ns | 2,747.1 ns | 155.22 ns | 28.34 | 1.12 | 0.0714 | 464 B |
|
||||
'String.ReplaceMany w/dictionary - For Each' | 6,981.0 ns | 5,500.7 ns | 310.80 ns | 27.20 | 1.38 | 0.0775 | 464 B |
|
||||
|
||||
long text, long replacements dictionary:
|
||||
|
||||
Method | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated |
|
||||
--------------------------------------------- |-----------:|-----------:|-----------:|-------:|---------:|-------:|----------:|
|
||||
'String.ReplaceMany w/chars - Aggregate' | 4.868 us | 3.420 us | 0.1932 us | 1.00 | 0.00 | 1.6816 | 6.94 KB |
|
||||
'String.ReplaceMany w/chars - For Loop' | 4.958 us | 2.633 us | 0.1487 us | 1.02 | 0.04 | 1.6791 | 6.92 KB |
|
||||
'String.ReplaceMany w/dictionary - Aggregate' | 181.309 us | 210.177 us | 11.8754 us | 37.29 | 2.32 | 5.3571 | 24.28 KB |
|
||||
'String.ReplaceMany w/dictionary - For Each' | 174.567 us | 113.733 us | 6.4262 us | 35.90 | 1.57 | 5.8594 | 24.28 KB |
|
||||
|
||||
*/
|
||||
|
||||
// don't use constants
|
||||
// ReSharper disable ConvertToConstant.Local
|
||||
|
||||
// input text for ReplaceMany
|
||||
private static readonly string Text;
|
||||
|
||||
// replaced chars for ReplaceMany with chars
|
||||
private static readonly char[] ReplacedChars = { ',', '.', ':', '&', '#' };
|
||||
|
||||
// replacement char for ReplaceMany with chars
|
||||
private static readonly char ReplacementChar = '*';
|
||||
|
||||
// replacements for ReplaceMany with dictionary
|
||||
private static readonly IDictionary<string, string> Replacements;
|
||||
|
||||
// ReSharper restore ConvertToConstant.Local
|
||||
|
||||
static StringReplaceManyBenchmarks()
|
||||
{
|
||||
{",", "*" },
|
||||
{".", "*" },
|
||||
{":", "*" },
|
||||
{"&", "*" },
|
||||
{"#", "*" },
|
||||
};
|
||||
// pick what you want to benchmark
|
||||
|
||||
[Benchmark(Description = "String.ReplaceMany with Aggregate", Baseline = true)]
|
||||
public string ReplaceManyAggregate()
|
||||
{
|
||||
if (text == null)
|
||||
// short
|
||||
Text = "1,2.3:4&5#6";
|
||||
|
||||
// long
|
||||
//Text = "Sed ut perspiciatis unde omnis iste natus &error sit voluptatem accusantium doloremque l:audantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et &quasi architecto beatae vitae ::dicta sunt explicabo. Nemo enim ipsam volupta:tem quia voluptas sit aspernatur aut o&dit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciun&t. Neque porro quisquam est, qui dolorem: ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut e:nim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi co&&nsequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse: quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?";
|
||||
|
||||
// short
|
||||
Replacements = new Dictionary<string, string>
|
||||
{
|
||||
throw new ArgumentNullException("text");
|
||||
}
|
||||
{ ",", "*" },
|
||||
{ ".", "*" },
|
||||
{ ":", "*" },
|
||||
{ "&", "*" },
|
||||
{ "#", "*" },
|
||||
};
|
||||
|
||||
if (chars == null)
|
||||
{
|
||||
throw new ArgumentNullException("chars");
|
||||
}
|
||||
|
||||
string result = text;
|
||||
return chars.Aggregate(result, (current, c) => current.Replace(c, replacement));
|
||||
// long
|
||||
//Replacements = new Dictionary<string, string>();
|
||||
//for (var i = 2; i < 100; i++)
|
||||
// Replacements[Convert.ToChar(i).ToString()] = "*";
|
||||
}
|
||||
|
||||
[Benchmark(Description = "String.ReplaceMany with For Loop")]
|
||||
// this is what v7 originally did
|
||||
[Benchmark(Description = "String.ReplaceMany w/chars - Aggregate", Baseline = true)]
|
||||
public string ReplaceManyAggregate()
|
||||
{
|
||||
var result = Text;
|
||||
return ReplacedChars.Aggregate(result, (current, c) => current.Replace(c, ReplacementChar));
|
||||
}
|
||||
|
||||
[Benchmark(Description = "String.ReplaceMany w/chars - For Loop")]
|
||||
public string ReplaceManyForLoop()
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException("text");
|
||||
}
|
||||
var result = Text;
|
||||
|
||||
if (chars == null)
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
// ReSharper disable once ForCanBeConvertedToForeach
|
||||
for (var i = 0; i < ReplacedChars.Length; i++)
|
||||
{
|
||||
throw new ArgumentNullException("chars");
|
||||
}
|
||||
|
||||
string result = text;
|
||||
for (int i = 0; i < chars.Length; i++)
|
||||
{
|
||||
result = result.Replace(chars[i], replacement);
|
||||
result = result.Replace(ReplacedChars[i], ReplacementChar);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Benchmark(Description = "String.ReplaceMany Dictionary with Aggregate")]
|
||||
// this is what v7 originally did
|
||||
[Benchmark(Description = "String.ReplaceMany w/dictionary - Aggregate")]
|
||||
public string ReplaceManyDictionaryAggregate()
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException("text");
|
||||
}
|
||||
|
||||
if (replacements == null)
|
||||
{
|
||||
throw new ArgumentNullException("replacements");
|
||||
}
|
||||
|
||||
return replacements.Aggregate(text, (current, kvp) => current.Replace(kvp.Key, kvp.Value));
|
||||
return Replacements.Aggregate(Text, (current, kvp) => current.Replace(kvp.Key, kvp.Value));
|
||||
}
|
||||
|
||||
[Benchmark(Description = "String.ReplaceMany Dictionary with For Each")]
|
||||
[Benchmark(Description = "String.ReplaceMany w/dictionary - For Each")]
|
||||
public string ReplaceManyDictionaryForEach()
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException("text");
|
||||
}
|
||||
|
||||
if (replacements == null)
|
||||
{
|
||||
throw new ArgumentNullException("replacements");
|
||||
}
|
||||
|
||||
string result = text;
|
||||
foreach (KeyValuePair<string, string> item in replacements)
|
||||
var result = Text;
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var item in Replacements)
|
||||
{
|
||||
result = result.Replace(item.Key, item.Value);
|
||||
}
|
||||
|
||||
@@ -162,8 +162,8 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Config\QuickRunAttribute.cs" />
|
||||
<Compile Include="Config\QuickRunWithMemoryDiagnoserAttribute.cs" />
|
||||
<Compile Include="Config\QuickRunConfigAttribute.cs" />
|
||||
<Compile Include="Config\QuickRunWithMemoryDiagnoserConfigAttribute.cs" />
|
||||
<Compile Include="LinqCastBenchmarks.cs" />
|
||||
<Compile Include="ModelToSqlExpressionHelperBenchmarks.cs" />
|
||||
<Compile Include="BulkInsertBenchmarks.cs" />
|
||||
|
||||
@@ -5,7 +5,7 @@ using Umbraco.Tests.Benchmarks.Config;
|
||||
|
||||
namespace Umbraco.Tests.Benchmarks
|
||||
{
|
||||
[QuickRunWithMemoryDiagnoser]
|
||||
[QuickRunWithMemoryDiagnoserConfig]
|
||||
public class XmlBenchmarks
|
||||
{
|
||||
[GlobalSetup]
|
||||
|
||||
@@ -11,7 +11,7 @@ using Umbraco.Web.PublishedCache.XmlPublishedCache;
|
||||
|
||||
namespace Umbraco.Tests.Benchmarks
|
||||
{
|
||||
[QuickRunWithMemoryDiagnoser]
|
||||
[QuickRunWithMemoryDiagnoserConfig]
|
||||
public class XmlPublishedContentInitBenchmarks
|
||||
{
|
||||
public XmlPublishedContentInitBenchmarks()
|
||||
|
||||
Reference in New Issue
Block a user