diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs
index 928de44d72..e4c9d94eba 100644
--- a/src/Umbraco.Core/ObjectExtensions.cs
+++ b/src/Umbraco.Core/ObjectExtensions.cs
@@ -12,11 +12,11 @@ using Umbraco.Core.Collections;
namespace Umbraco.Core
{
- ///
- /// Provides object extension methods.
- ///
- public static class ObjectExtensions
- {
+ ///
+ /// Provides object extension methods.
+ ///
+ public static class ObjectExtensions
+ {
// Cache the various type lookups
private static readonly ConcurrentDictionary NullableGenericCache = new ConcurrentDictionary();
private static readonly ConcurrentDictionary InputTypeConverterCache = new ConcurrentDictionary();
@@ -36,33 +36,33 @@ namespace Umbraco.Core
///
///
public static IEnumerable AsEnumerableOfOne(this T input)
- {
- return Enumerable.Repeat(input, 1);
- }
+ {
+ return Enumerable.Repeat(input, 1);
+ }
- ///
- ///
- ///
- ///
- public static void DisposeIfDisposable(this object input)
- {
- var disposable = input as IDisposable;
- if (disposable != null) disposable.Dispose();
- }
+ ///
+ ///
+ ///
+ ///
+ public static void DisposeIfDisposable(this object input)
+ {
+ var disposable = input as IDisposable;
+ if (disposable != null) disposable.Dispose();
+ }
- ///
- /// Provides a shortcut way of safely casting an input when you cannot guarantee the is
- /// an instance type (i.e., when the C# AS keyword is not applicable).
- ///
- ///
- /// The input.
- ///
- internal static T SafeCast(this object input)
- {
- if (ReferenceEquals(null, input) || ReferenceEquals(default(T), input)) return default(T);
- if (input is T) return (T)input;
- return default(T);
- }
+ ///
+ /// Provides a shortcut way of safely casting an input when you cannot guarantee the is
+ /// an instance type (i.e., when the C# AS keyword is not applicable).
+ ///
+ ///
+ /// The input.
+ ///
+ internal static T SafeCast(this object input)
+ {
+ if (ReferenceEquals(null, input) || ReferenceEquals(default(T), input)) return default(T);
+ if (input is T) return (T)input;
+ return default(T);
+ }
///
/// Attempts to convert the input object to the output type.
@@ -76,12 +76,12 @@ namespace Umbraco.Core
var result = TryConvertTo(input, typeof(T));
if (result.Success)
- return Attempt.Succeed((T) result.Result);
+ return Attempt.Succeed((T)result.Result);
// just try to cast
try
{
- return Attempt.Succeed((T) input);
+ return Attempt.Succeed((T)input);
}
catch (Exception e)
{
@@ -180,7 +180,7 @@ namespace Umbraco.Core
// TODO: Do a check for destination type being IEnumerable and source type implementing IEnumerable with
// the same 'T', then we'd have to find the extension method for the type AsEnumerable() and execute it.
- if (GetCachedCanAssign(inputType, target))
+ if (GetCachedCanAssign(input, inputType, target))
{
return Attempt.Succeed(Convert.ChangeType(input, target));
}
@@ -379,92 +379,92 @@ namespace Umbraco.Core
// 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
- if (isDisposed)
- throw new ObjectDisposedException(objectname);
- }
+ internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname)
+ {
+ //TODO: Localise this exception
+ if (isDisposed)
+ throw new ObjectDisposedException(objectname);
+ }
- //public enum PropertyNamesCaseType
- //{
- // CamelCase,
- // CaseInsensitive
- //}
+ //public enum PropertyNamesCaseType
+ //{
+ // CamelCase,
+ // CaseInsensitive
+ //}
- /////
- ///// Convert an object to a JSON string with camelCase formatting
- /////
- /////
- /////
- //public static string ToJsonString(this object obj)
- //{
- // return obj.ToJsonString(PropertyNamesCaseType.CamelCase);
- //}
+ /////
+ ///// Convert an object to a JSON string with camelCase formatting
+ /////
+ /////
+ /////
+ //public static string ToJsonString(this object obj)
+ //{
+ // return obj.ToJsonString(PropertyNamesCaseType.CamelCase);
+ //}
- /////
- ///// Convert an object to a JSON string with the specified formatting
- /////
- ///// The obj.
- ///// Type of the property names case.
- /////
- //public static string ToJsonString(this object obj, PropertyNamesCaseType propertyNamesCaseType)
- //{
- // var type = obj.GetType();
- // var dateTimeStyle = "yyyy-MM-dd HH:mm:ss";
+ /////
+ ///// Convert an object to a JSON string with the specified formatting
+ /////
+ ///// The obj.
+ ///// Type of the property names case.
+ /////
+ //public static string ToJsonString(this object obj, PropertyNamesCaseType propertyNamesCaseType)
+ //{
+ // var type = obj.GetType();
+ // var dateTimeStyle = "yyyy-MM-dd HH:mm:ss";
- // if (type.IsPrimitive || typeof(string).IsAssignableFrom(type))
- // {
- // return obj.ToString();
- // }
+ // if (type.IsPrimitive || typeof(string).IsAssignableFrom(type))
+ // {
+ // return obj.ToString();
+ // }
- // if (typeof(DateTime).IsAssignableFrom(type) || typeof(DateTimeOffset).IsAssignableFrom(type))
- // {
- // return Convert.ToDateTime(obj).ToString(dateTimeStyle);
- // }
+ // if (typeof(DateTime).IsAssignableFrom(type) || typeof(DateTimeOffset).IsAssignableFrom(type))
+ // {
+ // return Convert.ToDateTime(obj).ToString(dateTimeStyle);
+ // }
- // var serializer = new JsonSerializer();
+ // var serializer = new JsonSerializer();
- // switch (propertyNamesCaseType)
- // {
- // case PropertyNamesCaseType.CamelCase:
- // serializer.ContractResolver = new CamelCasePropertyNamesContractResolver();
- // break;
- // }
+ // switch (propertyNamesCaseType)
+ // {
+ // case PropertyNamesCaseType.CamelCase:
+ // serializer.ContractResolver = new CamelCasePropertyNamesContractResolver();
+ // break;
+ // }
- // var dateTimeConverter = new IsoDateTimeConverter
- // {
- // DateTimeStyles = System.Globalization.DateTimeStyles.None,
- // DateTimeFormat = dateTimeStyle
- // };
+ // var dateTimeConverter = new IsoDateTimeConverter
+ // {
+ // DateTimeStyles = System.Globalization.DateTimeStyles.None,
+ // DateTimeFormat = dateTimeStyle
+ // };
- // if (typeof(IDictionary).IsAssignableFrom(type))
- // {
- // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter);
- // }
+ // if (typeof(IDictionary).IsAssignableFrom(type))
+ // {
+ // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter);
+ // }
- // if (type.IsArray || (typeof(IEnumerable).IsAssignableFrom(type)))
- // {
- // return JArray.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter);
- // }
+ // if (type.IsArray || (typeof(IEnumerable).IsAssignableFrom(type)))
+ // {
+ // return JArray.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter);
+ // }
- // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter);
- //}
+ // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter);
+ //}
- ///
- /// Converts an object into a dictionary
- ///
- ///
- ///
- ///
- ///
- ///
- ///
+ ///
+ /// Converts an object into a dictionary
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public static IDictionary ToDictionary(this T o,
- params Expression>[] ignoreProperties)
- {
- return o.ToDictionary(ignoreProperties.Select(e => o.GetPropertyInfo(e)).Select(propInfo => propInfo.Name).ToArray());
- }
+ params Expression>[] ignoreProperties)
+ {
+ return o.ToDictionary(ignoreProperties.Select(e => o.GetPropertyInfo(e)).Select(propInfo => propInfo.Name).ToArray());
+ }
///
/// Turns object into dictionary
@@ -473,137 +473,137 @@ namespace Umbraco.Core
/// Properties to ignore
///
public static IDictionary ToDictionary(this object o, params string[] ignoreProperties)
- {
- if (o != null)
- {
- var props = TypeDescriptor.GetProperties(o);
- var d = new Dictionary();
- foreach (var prop in props.Cast().Where(x => ignoreProperties.Contains(x.Name) == false))
- {
- var val = prop.GetValue(o);
- if (val != null)
- {
- d.Add(prop.Name, (TVal)val);
- }
- }
- return d;
- }
- return new Dictionary();
- }
+ {
+ if (o != null)
+ {
+ var props = TypeDescriptor.GetProperties(o);
+ var d = new Dictionary();
+ foreach (var prop in props.Cast().Where(x => ignoreProperties.Contains(x.Name) == false))
+ {
+ var val = prop.GetValue(o);
+ if (val != null)
+ {
+ d.Add(prop.Name, (TVal)val);
+ }
+ }
+ return d;
+ }
+ return new Dictionary();
+ }
- internal static string ToDebugString(this object obj, int levels = 0)
- {
- if (obj == null) return "{null}";
- try
- {
- if (obj is string)
- {
- return "\"{0}\"".InvariantFormat(obj);
- }
+ internal static string ToDebugString(this object obj, int levels = 0)
+ {
+ if (obj == null) return "{null}";
+ try
+ {
+ if (obj is string)
+ {
+ return "\"{0}\"".InvariantFormat(obj);
+ }
if (obj is int || obj is Int16 || obj is Int64 || obj is float || obj is double || obj is bool || obj is int? || obj is Int16? || obj is Int64? || obj is float? || obj is double? || obj is bool?)
- {
- return "{0}".InvariantFormat(obj);
- }
- if (obj is Enum)
- {
- return "[{0}]".InvariantFormat(obj);
- }
- if (obj is IEnumerable)
- {
- var enumerable = (obj as IEnumerable);
+ {
+ return "{0}".InvariantFormat(obj);
+ }
+ if (obj is Enum)
+ {
+ return "[{0}]".InvariantFormat(obj);
+ }
+ if (obj is IEnumerable)
+ {
+ var enumerable = (obj as IEnumerable);
- var items = (from object enumItem in enumerable let value = GetEnumPropertyDebugString(enumItem, levels) where value != null select value).Take(10).ToList();
+ var items = (from object enumItem in enumerable let value = GetEnumPropertyDebugString(enumItem, levels) where value != null select value).Take(10).ToList();
- return items.Any()
- ? "{{ {0} }}".InvariantFormat(String.Join(", ", items))
- : null;
- }
+ return items.Any()
+ ? "{{ {0} }}".InvariantFormat(String.Join(", ", items))
+ : null;
+ }
- var props = obj.GetType().GetProperties();
- if ((props.Length == 2) && props[0].Name == "Key" && props[1].Name == "Value" && levels > -2)
- {
- try
- {
- var key = props[0].GetValue(obj, null) as string;
- var value = props[1].GetValue(obj, null).ToDebugString(levels - 1);
- return "{0}={1}".InvariantFormat(key, value);
- }
- catch (Exception)
- {
- return "[KeyValuePropertyException]";
- }
- }
- if (levels > -1)
- {
- var items =
- (from propertyInfo in props
- let value = GetPropertyDebugString(propertyInfo, obj, levels)
- where value != null
- select "{0}={1}".InvariantFormat(propertyInfo.Name, value)).ToArray();
+ var props = obj.GetType().GetProperties();
+ if ((props.Length == 2) && props[0].Name == "Key" && props[1].Name == "Value" && levels > -2)
+ {
+ try
+ {
+ var key = props[0].GetValue(obj, null) as string;
+ var value = props[1].GetValue(obj, null).ToDebugString(levels - 1);
+ return "{0}={1}".InvariantFormat(key, value);
+ }
+ catch (Exception)
+ {
+ return "[KeyValuePropertyException]";
+ }
+ }
+ if (levels > -1)
+ {
+ var items =
+ (from propertyInfo in props
+ let value = GetPropertyDebugString(propertyInfo, obj, levels)
+ where value != null
+ select "{0}={1}".InvariantFormat(propertyInfo.Name, value)).ToArray();
- return items.Any()
- ? "[{0}]:{{ {1} }}".InvariantFormat(obj.GetType().Name, String.Join(", ", items))
- : null;
- }
- }
- catch (Exception ex)
- {
- return "[Exception:{0}]".InvariantFormat(ex.Message);
- }
- return null;
- }
+ return items.Any()
+ ? "[{0}]:{{ {1} }}".InvariantFormat(obj.GetType().Name, String.Join(", ", items))
+ : null;
+ }
+ }
+ catch (Exception ex)
+ {
+ return "[Exception:{0}]".InvariantFormat(ex.Message);
+ }
+ return null;
+ }
- ///
- /// Attempts to serialize the value to an XmlString using ToXmlString
- ///
- ///
- ///
- ///
- internal static Attempt TryConvertToXmlString(this object value, Type type)
- {
- try
- {
- var output = value.ToXmlString(type);
- return Attempt.Succeed(output);
- }
- catch (NotSupportedException ex)
- {
- return Attempt.Fail(ex);
- }
- }
+ ///
+ /// Attempts to serialize the value to an XmlString using ToXmlString
+ ///
+ ///
+ ///
+ ///
+ internal static Attempt TryConvertToXmlString(this object value, Type type)
+ {
+ try
+ {
+ var output = value.ToXmlString(type);
+ return Attempt.Succeed(output);
+ }
+ catch (NotSupportedException ex)
+ {
+ return Attempt.Fail(ex);
+ }
+ }
- ///
- /// Returns an XmlSerialized safe string representation for the value
- ///
- ///
- /// The Type can only be a primitive type or Guid and byte[] otherwise an exception is thrown
- ///
- internal static string ToXmlString(this object value, Type type)
- {
- if (value == null) return string.Empty;
- if (type == typeof(string)) return (value.ToString().IsNullOrWhiteSpace() ? "" : value.ToString());
- if (type == typeof(bool)) return XmlConvert.ToString((bool)value);
- if (type == typeof(byte)) return XmlConvert.ToString((byte)value);
- if (type == typeof(char)) return XmlConvert.ToString((char)value);
+ ///
+ /// Returns an XmlSerialized safe string representation for the value
+ ///
+ ///
+ /// The Type can only be a primitive type or Guid and byte[] otherwise an exception is thrown
+ ///
+ internal static string ToXmlString(this object value, Type type)
+ {
+ if (value == null) return string.Empty;
+ if (type == typeof(string)) return (value.ToString().IsNullOrWhiteSpace() ? "" : value.ToString());
+ if (type == typeof(bool)) return XmlConvert.ToString((bool)value);
+ if (type == typeof(byte)) return XmlConvert.ToString((byte)value);
+ if (type == typeof(char)) return XmlConvert.ToString((char)value);
if (type == typeof(DateTime)) return XmlConvert.ToString((DateTime)value, XmlDateTimeSerializationMode.Unspecified);
- if (type == typeof(DateTimeOffset)) return XmlConvert.ToString((DateTimeOffset)value);
- if (type == typeof(decimal)) return XmlConvert.ToString((decimal)value);
- if (type == typeof(double)) return XmlConvert.ToString((double)value);
- if (type == typeof(float)) return XmlConvert.ToString((float)value);
- if (type == typeof(Guid)) return XmlConvert.ToString((Guid)value);
- if (type == typeof(int)) return XmlConvert.ToString((int)value);
- if (type == typeof(long)) return XmlConvert.ToString((long)value);
- if (type == typeof(sbyte)) return XmlConvert.ToString((sbyte)value);
- if (type == typeof(short)) return XmlConvert.ToString((short)value);
- if (type == typeof(TimeSpan)) return XmlConvert.ToString((TimeSpan)value);
- if (type == typeof(bool)) return XmlConvert.ToString((bool)value);
- if (type == typeof(uint)) return XmlConvert.ToString((uint)value);
- if (type == typeof(ulong)) return XmlConvert.ToString((ulong)value);
- if (type == typeof(ushort)) return XmlConvert.ToString((ushort)value);
+ if (type == typeof(DateTimeOffset)) return XmlConvert.ToString((DateTimeOffset)value);
+ if (type == typeof(decimal)) return XmlConvert.ToString((decimal)value);
+ if (type == typeof(double)) return XmlConvert.ToString((double)value);
+ if (type == typeof(float)) return XmlConvert.ToString((float)value);
+ if (type == typeof(Guid)) return XmlConvert.ToString((Guid)value);
+ if (type == typeof(int)) return XmlConvert.ToString((int)value);
+ if (type == typeof(long)) return XmlConvert.ToString((long)value);
+ if (type == typeof(sbyte)) return XmlConvert.ToString((sbyte)value);
+ if (type == typeof(short)) return XmlConvert.ToString((short)value);
+ if (type == typeof(TimeSpan)) return XmlConvert.ToString((TimeSpan)value);
+ if (type == typeof(bool)) return XmlConvert.ToString((bool)value);
+ if (type == typeof(uint)) return XmlConvert.ToString((uint)value);
+ if (type == typeof(ulong)) return XmlConvert.ToString((ulong)value);
+ if (type == typeof(ushort)) return XmlConvert.ToString((ushort)value);
- throw new NotSupportedException("Cannot convert type " + type.FullName + " to a string using ToXmlString as it is not supported by XmlConvert");
- }
+ throw new NotSupportedException("Cannot convert type " + type.FullName + " to a string using ToXmlString as it is not supported by XmlConvert");
+ }
///
/// Returns an XmlSerialized safe string representation for the value and type
@@ -612,38 +612,38 @@ namespace Umbraco.Core
///
///
internal static string ToXmlString(this object value)
- {
- return value.ToXmlString(typeof (T));
- }
+ {
+ return value.ToXmlString(typeof(T));
+ }
- private static string GetEnumPropertyDebugString(object enumItem, int levels)
- {
- try
- {
- return enumItem.ToDebugString(levels - 1);
- }
- catch (Exception)
- {
- return "[GetEnumPartException]";
- }
- }
+ private static string GetEnumPropertyDebugString(object enumItem, int levels)
+ {
+ try
+ {
+ return enumItem.ToDebugString(levels - 1);
+ }
+ catch (Exception)
+ {
+ return "[GetEnumPartException]";
+ }
+ }
- private static string GetPropertyDebugString(PropertyInfo propertyInfo, object obj, int levels)
- {
- try
- {
- return propertyInfo.GetValue(obj, null).ToDebugString(levels - 1);
- }
- catch (Exception)
- {
- return "[GetPropertyValueException]";
- }
- }
+ private static string GetPropertyDebugString(PropertyInfo propertyInfo, object obj, int levels)
+ {
+ try
+ {
+ return propertyInfo.GetValue(obj, null).ToDebugString(levels - 1);
+ }
+ catch (Exception)
+ {
+ return "[GetPropertyValueException]";
+ }
+ }
- internal static Guid AsGuid(this object value)
- {
- return value is Guid ? (Guid) value : Guid.Empty;
- }
+ internal static Guid AsGuid(this object value)
+ {
+ return value is Guid ? (Guid)value : Guid.Empty;
+ }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string NormalizeNumberDecimalSeparator(string s)
@@ -656,57 +656,93 @@ namespace Umbraco.Core
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TypeConverter GetCachedSourceTypeConverter(Type source, Type target)
{
- return InputTypeConverterCache.GetOrAdd(new CompositeTypeTypeKey(source, target), k =>
- {
- var ksource = k.Type1;
- var ktarget = k.Type2;
+ var key = new CompositeTypeTypeKey(source, target);
- var converter = TypeDescriptor.GetConverter(ksource);
- return converter.CanConvertTo(ktarget) ? converter : null;
- });
+ if (InputTypeConverterCache.TryGetValue(key, out TypeConverter typeConverter))
+ {
+ return typeConverter;
+ }
+
+ TypeConverter converter = TypeDescriptor.GetConverter(source);
+ if (converter.CanConvertTo(target))
+ {
+ return InputTypeConverterCache[key] = converter;
+ }
+
+ return InputTypeConverterCache[key] = null;
}
// gets a converter for target, that can convert from source, or null if none exists
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TypeConverter GetCachedTargetTypeConverter(Type source, Type target)
{
- return DestinationTypeConverterCache.GetOrAdd(new CompositeTypeTypeKey(source, target), k =>
- {
- var ksource = k.Type1;
- var ktarget = k.Type2;
+ var key = new CompositeTypeTypeKey(source, target);
- var converter = TypeDescriptor.GetConverter(ktarget);
- return converter.CanConvertFrom(ksource) ? converter : null;
- });
+ if (DestinationTypeConverterCache.TryGetValue(key, out TypeConverter typeConverter))
+ {
+ return typeConverter;
+ }
+
+ TypeConverter converter = TypeDescriptor.GetConverter(target);
+ if (converter.CanConvertFrom(source))
+ {
+ return DestinationTypeConverterCache[key] = converter;
+ }
+
+ return DestinationTypeConverterCache[key] = 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 type)
{
- return NullableGenericCache.GetOrAdd(type, t
- => t.GetGenericTypeDefinition() == typeof(Nullable<>) ? Nullable.GetUnderlyingType(type) : null;
+ if (NullableGenericCache.TryGetValue(type, out Type underlyingType))
+ {
+ return underlyingType;
+ }
+
+ if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
+ {
+ Type underlying = Nullable.GetUnderlyingType(type);
+ return NullableGenericCache[type] = underlying;
+ }
+
+ return NullableGenericCache[type] = null;
}
// gets an IConvertible from source to target type, or null if none exists
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool GetCachedCanAssign(Type source, Type target)
+ private static bool GetCachedCanAssign(object input, Type source, Type target)
{
- return AssignableTypeCache.GetOrAdd(new CompositeTypeTypeKey(source, target), k =>
+ var key = new CompositeTypeTypeKey(source, target);
+ if (AssignableTypeCache.TryGetValue(key, out bool canConvert))
{
- var ksource = k.Type1;
- var ktarget = k.Type2;
+ return canConvert;
+ }
- return ktarget.IsAssignableFrom(ksource) && typeof(IConvertible).IsAssignableFrom(ksource);
- });
+ if (input is IConvertible && target.IsAssignableFrom(source))
+ {
+ return AssignableTypeCache[key] = true;
+ }
+
+ return AssignableTypeCache[key] = false;
}
// determines whether a type can be converted to boolean
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool GetCachedCanConvertToBoolean(Type type)
{
- return BoolConvertCache.GetOrAdd(type, t
- => CustomBooleanTypeConverter.CanConvertFrom(t));
+ if (BoolConvertCache.TryGetValue(type, out bool result))
+ {
+ return result;
+ }
+
+ if (CustomBooleanTypeConverter.CanConvertFrom(type))
+ {
+ return BoolConvertCache[type] = true;
+ }
+
+ return BoolConvertCache[type] = false;
}
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Tests.Benchmarks/ConcurrentDictionaryBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/ConcurrentDictionaryBenchmarks.cs
new file mode 100644
index 0000000000..6cb39b7235
--- /dev/null
+++ b/src/Umbraco.Tests.Benchmarks/ConcurrentDictionaryBenchmarks.cs
@@ -0,0 +1,152 @@
+using BenchmarkDotNet.Attributes;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Umbraco.Core.Collections;
+
+namespace Umbraco.Tests.Benchmarks
+{
+ [MemoryDiagnoser]
+ public class ConcurrentDictionaryBenchmarks
+ {
+ private static readonly ConcurrentDictionary AssignableTypeCache = new ConcurrentDictionary();
+
+ private static object input = new Bar();
+
+ private static Type source = typeof(Bar);
+
+ private static Type target = typeof(Foo);
+
+ [Benchmark(Baseline = true)]
+ public bool GetCachedCanAssignFactory()
+ {
+ return AssignableTypeCache.GetOrAdd(new CompositeTypeTypeKey(source, target), k =>
+ {
+ var ksource = k.Type1;
+ var ktarget = k.Type2;
+
+ return ktarget.IsAssignableFrom(ksource) && typeof(IConvertible).IsAssignableFrom(ksource);
+ });
+ }
+
+ [Benchmark]
+ public bool GetCachedCanAssignNoFactory()
+ {
+ // This method is 10% faster
+ var key = new CompositeTypeTypeKey(source, target);
+ if (AssignableTypeCache.TryGetValue(key, out bool canConvert))
+ {
+ return canConvert;
+ }
+
+ // "is" is faster than "IsAssignableFrom"
+ if (input is IConvertible && target.IsAssignableFrom(source))
+ {
+ return AssignableTypeCache[key] = true;
+ }
+
+ return AssignableTypeCache[key] = false;
+ }
+
+ private class Foo : IConvertible
+ {
+ public TypeCode GetTypeCode()
+ {
+ return TypeCode.Object;
+ }
+
+ public bool ToBoolean(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public byte ToByte(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public char ToChar(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public DateTime ToDateTime(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public decimal ToDecimal(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public double ToDouble(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public short ToInt16(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public int ToInt32(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public long ToInt64(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public sbyte ToSByte(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public float ToSingle(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public string ToString(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public object ToType(Type conversionType, IFormatProvider provider)
+ {
+ if (conversionType == typeof(Foo))
+ {
+ return new Foo();
+ }
+
+ throw new NotImplementedException();
+ }
+
+ public ushort ToUInt16(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint ToUInt32(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public ulong ToUInt64(IFormatProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class Bar : Foo
+ {
+
+ }
+ }
+}
diff --git a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj
index 2eb4f5f3d3..67e35a3621 100644
--- a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj
+++ b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj
@@ -162,6 +162,7 @@
+
@@ -175,7 +176,9 @@
-
+
+ Designer
+