Port v7@2aa0dfb2c5 - WIP
This commit is contained in:
@@ -6,10 +6,10 @@ using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Xml;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Collections;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
@@ -18,8 +18,15 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public static class ObjectExtensions
|
||||
{
|
||||
private static readonly ConcurrentDictionary<Type, Dictionary<string, object>> ToObjectTypes
|
||||
= new ConcurrentDictionary<Type, Dictionary<string, object>>();
|
||||
private static readonly ConcurrentDictionary<Type, Dictionary<string, object>> ToObjectTypes = new ConcurrentDictionary<Type, Dictionary<string, object>>();
|
||||
private static readonly ConcurrentDictionary<Type, Type> NullableGenericCache = new ConcurrentDictionary<Type, Type>();
|
||||
private static readonly ConcurrentDictionary<CompositeTypeTypeKey, TypeConverter> InputTypeConverterCache = new ConcurrentDictionary<CompositeTypeTypeKey, TypeConverter>();
|
||||
private static readonly ConcurrentDictionary<CompositeTypeTypeKey, TypeConverter> DestinationTypeConverterCache = new ConcurrentDictionary<CompositeTypeTypeKey, TypeConverter>();
|
||||
private static readonly ConcurrentDictionary<CompositeTypeTypeKey, bool> AssignableTypeCache = new ConcurrentDictionary<CompositeTypeTypeKey, bool>();
|
||||
private static readonly ConcurrentDictionary<Type, bool> BoolConvertCache = new ConcurrentDictionary<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>>();
|
||||
|
||||
@@ -40,8 +47,8 @@ namespace Umbraco.Core
|
||||
/// <param name="input"></param>
|
||||
public static void DisposeIfDisposable(this object input)
|
||||
{
|
||||
var disposable = input as IDisposable;
|
||||
if (disposable != null) disposable.Dispose();
|
||||
if (input is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -53,347 +60,335 @@ namespace Umbraco.Core
|
||||
/// <returns></returns>
|
||||
internal static T SafeCast<T>(this object input)
|
||||
{
|
||||
if (ReferenceEquals(null, input) || ReferenceEquals(default(T), input)) return default(T);
|
||||
if (input is T) return (T)input;
|
||||
return default(T);
|
||||
if (ReferenceEquals(null, input) || ReferenceEquals(default(T), input)) return default;
|
||||
if (input is T variable) return variable;
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to convert the input object to the output type using TypeConverters
|
||||
/// Attempts to convert the input object to the output type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>This code is an optimized version of the original Umbraco method</remarks>
|
||||
/// <typeparam name="T">The type to convert to</typeparam>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <returns>The <see cref="Attempt{T}"/></returns>
|
||||
public static Attempt<T> TryConvertTo<T>(this object input)
|
||||
{
|
||||
var result = TryConvertTo(input, typeof(T));
|
||||
if (result.Success == false)
|
||||
|
||||
if (result.Success)
|
||||
return Attempt<T>.Succeed((T)result.Result);
|
||||
|
||||
// just try to cast
|
||||
try
|
||||
{
|
||||
//just try a straight up conversion
|
||||
try
|
||||
{
|
||||
var converted = (T) input;
|
||||
return Attempt<T>.Succeed(converted);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Attempt<T>.Fail(e);
|
||||
}
|
||||
return Attempt<T>.Succeed((T)input);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Attempt<T>.Fail(e);
|
||||
}
|
||||
return result.Success == false ? Attempt<T>.Fail() : Attempt<T>.Succeed((T)result.Result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to convert the input object to the output type using TypeConverters. If the destination
|
||||
/// type is a superclass of the input type, if will use <see cref="Convert.ChangeType(object,System.Type)"/>.
|
||||
/// 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">Type of the destination.</param>
|
||||
/// <returns></returns>
|
||||
public static Attempt<object> TryConvertTo(this object input, Type destinationType)
|
||||
/// <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 target)
|
||||
{
|
||||
// if null...
|
||||
if (input == null)
|
||||
if (target == null)
|
||||
{
|
||||
// nullable is ok
|
||||
if (destinationType.IsGenericType && destinationType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
return Attempt<object>.Succeed(null);
|
||||
|
||||
// value type is nok, else can be null, so is ok
|
||||
return Attempt<object>.If(destinationType.IsValueType == false, null);
|
||||
return Attempt<object>.Fail();
|
||||
}
|
||||
|
||||
// easy
|
||||
if (destinationType == typeof(object)) return Attempt.Succeed(input);
|
||||
if (input.GetType() == destinationType) return Attempt.Succeed(input);
|
||||
|
||||
// check for string so that overloaders of ToString() can take advantage of the conversion.
|
||||
if (destinationType == typeof(string)) return Attempt<object>.Succeed(input.ToString());
|
||||
|
||||
// if we've got a nullable of something, we try to convert directly to that thing.
|
||||
if (destinationType.IsGenericType && destinationType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
try
|
||||
{
|
||||
var underlyingType = Nullable.GetUnderlyingType(destinationType);
|
||||
|
||||
//special case for empty strings for bools/dates which should return null if an empty string
|
||||
var asString = input as string;
|
||||
if (asString != null && string.IsNullOrEmpty(asString) && (underlyingType == typeof(DateTime) || underlyingType == typeof(bool)))
|
||||
if (input == null)
|
||||
{
|
||||
return Attempt<object>.Succeed(null);
|
||||
// Nullable is ok
|
||||
if (target.IsGenericType && GetCachedGenericNullableType(target) != null)
|
||||
{
|
||||
return Attempt<object>.Succeed(null);
|
||||
}
|
||||
|
||||
// Reference types are ok
|
||||
return Attempt<object>.If(target.IsValueType == false, null);
|
||||
}
|
||||
|
||||
// recursively call into myself with the inner (not-nullable) type and handle the outcome
|
||||
var nonNullable = input.TryConvertTo(underlyingType);
|
||||
var inputType = input.GetType();
|
||||
|
||||
// and if sucessful, fall on through to rewrap in a nullable; if failed, pass on the exception
|
||||
if (nonNullable.Success)
|
||||
input = nonNullable.Result; // now fall on through...
|
||||
// Easy
|
||||
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 (target == typeof(string))
|
||||
{
|
||||
return Attempt<object>.Succeed(input.ToString());
|
||||
}
|
||||
|
||||
// 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 (target.IsGenericType)
|
||||
{
|
||||
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 inputString)
|
||||
{
|
||||
//TODO: Why the check against only bool/date when a string is null/empty? In what scenario can we convert to another type when the string is null or empty other than just being null?
|
||||
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
|
||||
var inner = input.TryConvertTo(underlying);
|
||||
|
||||
// And if sucessful, fall on through to rewrap in a nullable; if failed, pass on the exception
|
||||
if (inner.Success)
|
||||
{
|
||||
input = inner.Result; // Now fall on through...
|
||||
}
|
||||
else
|
||||
{
|
||||
return Attempt<object>.Fail(inner.Exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
return Attempt<object>.Fail(nonNullable.Exception);
|
||||
}
|
||||
|
||||
// we've already dealed with nullables, so any other generic types need to fall through
|
||||
if (destinationType.IsGenericType == false)
|
||||
{
|
||||
if (input is string)
|
||||
{
|
||||
// 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
|
||||
var result = TryConvertToFromString(input as string, destinationType);
|
||||
if (result.HasValue) return result.Value;
|
||||
}
|
||||
// target is not a generic type
|
||||
|
||||
//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.
|
||||
|
||||
if (TypeHelper.IsTypeAssignableFrom(destinationType, input.GetType())
|
||||
&& TypeHelper.IsTypeAssignableFrom<IConvertible>(input))
|
||||
{
|
||||
try
|
||||
if (input is string inputString)
|
||||
{
|
||||
var casted = Convert.ChangeType(input, destinationType);
|
||||
return Attempt.Succeed(casted);
|
||||
// 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
|
||||
var result = TryConvertToFromString(inputString, target);
|
||||
if (result.HasValue)
|
||||
{
|
||||
return result.Value;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
// 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.
|
||||
if (GetCachedCanAssign(input, inputType, target))
|
||||
{
|
||||
return Attempt<object>.Fail(e);
|
||||
return Attempt.Succeed(Convert.ChangeType(input, target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var inputConverter = TypeDescriptor.GetConverter(input);
|
||||
if (inputConverter.CanConvertTo(destinationType))
|
||||
{
|
||||
try
|
||||
if (target == typeof(bool))
|
||||
{
|
||||
var converted = inputConverter.ConvertTo(input, destinationType);
|
||||
return Attempt.Succeed(converted);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Attempt<object>.Fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (destinationType == typeof(bool))
|
||||
{
|
||||
var boolConverter = new CustomBooleanTypeConverter();
|
||||
if (boolConverter.CanConvertFrom(input.GetType()))
|
||||
{
|
||||
try
|
||||
if (GetCachedCanConvertToBoolean(inputType))
|
||||
{
|
||||
var converted = boolConverter.ConvertFrom(input);
|
||||
return Attempt.Succeed(converted);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Attempt<object>.Fail(e);
|
||||
return Attempt.Succeed(CustomBooleanTypeConverter.ConvertFrom(input));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var outputConverter = TypeDescriptor.GetConverter(destinationType);
|
||||
if (outputConverter.CanConvertFrom(input.GetType()))
|
||||
{
|
||||
try
|
||||
var inputConverter = GetCachedSourceTypeConverter(inputType, target);
|
||||
if (inputConverter != null)
|
||||
{
|
||||
var converted = outputConverter.ConvertFrom(input);
|
||||
return Attempt.Succeed(converted);
|
||||
return Attempt.Succeed(inputConverter.ConvertTo(input, target));
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
var outputConverter = GetCachedTargetTypeConverter(inputType, target);
|
||||
if (outputConverter != null)
|
||||
{
|
||||
return Attempt<object>.Fail(e);
|
||||
return Attempt.Succeed(outputConverter.ConvertFrom(input));
|
||||
}
|
||||
|
||||
if (target.IsGenericType && GetCachedGenericNullableType(target) != null)
|
||||
{
|
||||
// cannot Convert.ChangeType as that does not work with nullable
|
||||
// input has already been converted to the underlying type - just
|
||||
// return input, there's an implicit conversion from T to T? anyways
|
||||
return Attempt.Succeed(input);
|
||||
}
|
||||
|
||||
// Re-check convertables since we altered the input through recursion
|
||||
if (input is IConvertible convertible2)
|
||||
{
|
||||
return Attempt.Succeed(Convert.ChangeType(convertible2, target));
|
||||
}
|
||||
}
|
||||
|
||||
if (TypeHelper.IsTypeAssignableFrom<IConvertible>(input))
|
||||
catch (Exception e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var casted = Convert.ChangeType(input, destinationType);
|
||||
return Attempt.Succeed(casted);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Attempt<object>.Fail(e);
|
||||
}
|
||||
return Attempt<object>.Fail(e);
|
||||
}
|
||||
|
||||
return Attempt<object>.Fail();
|
||||
}
|
||||
|
||||
// returns an attempt if the string has been processed (either succeeded or failed)
|
||||
// returns null if we need to try other methods
|
||||
private static Attempt<object>? TryConvertToFromString(this string input, Type destinationType)
|
||||
/// <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="target">The type to convert to</param>
|
||||
/// <returns>The <see cref="Nullable{Attempt}"/></returns>
|
||||
private static Attempt<object>? TryConvertToFromString(this string input, Type target)
|
||||
{
|
||||
// easy
|
||||
if (destinationType == typeof(string))
|
||||
return Attempt<object>.Succeed(input);
|
||||
|
||||
// null, empty, whitespaces
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
// Easy
|
||||
if (target == typeof(string))
|
||||
{
|
||||
if (destinationType == typeof(bool)) // null/empty = bool false
|
||||
return Attempt<object>.Succeed(false);
|
||||
if (destinationType == typeof(DateTime)) // null/empty = min DateTime value
|
||||
return Attempt<object>.Succeed(DateTime.MinValue);
|
||||
|
||||
// cannot decide here,
|
||||
// any of the types below will fail parsing and will return a failed attempt
|
||||
// but anything else will not be processed and will return null
|
||||
// so even though the string is null/empty we have to proceed
|
||||
return Attempt<object>.Succeed(input);
|
||||
}
|
||||
|
||||
// look for type conversions in the expected order of frequency of use...
|
||||
if (destinationType.IsPrimitive)
|
||||
// Null, empty, whitespaces
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
if (destinationType == typeof(int)) // aka Int32
|
||||
if (target == typeof(bool))
|
||||
{
|
||||
int value;
|
||||
if (int.TryParse(input, out value)) return Attempt<object>.Succeed(value);
|
||||
// null/empty = bool false
|
||||
return Attempt<object>.Succeed(false);
|
||||
}
|
||||
|
||||
// because decimal 100.01m will happily convert to integer 100, it
|
||||
if (target == typeof(DateTime))
|
||||
{
|
||||
// null/empty = min DateTime value
|
||||
return Attempt<object>.Succeed(DateTime.MinValue);
|
||||
}
|
||||
|
||||
// Cannot decide here,
|
||||
// Any of the types below will fail parsing and will return a failed attempt
|
||||
// but anything else will not be processed and will return null
|
||||
// so even though the string is null/empty we have to proceed.
|
||||
}
|
||||
|
||||
// Look for type conversions in the expected order of frequency of use.
|
||||
//
|
||||
// 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 (target.IsPrimitive)
|
||||
{
|
||||
if (target == typeof(int))
|
||||
{
|
||||
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.
|
||||
decimal value2;
|
||||
var input2 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.If(decimal.TryParse(input2, out value2), Convert.ToInt32(value2));
|
||||
return Attempt<object>.If(decimal.TryParse(input2, out var value2), Convert.ToInt32(value2));
|
||||
}
|
||||
|
||||
if (destinationType == typeof(long)) // aka Int64
|
||||
if (target == typeof(long))
|
||||
{
|
||||
long value;
|
||||
if (long.TryParse(input, out value)) return Attempt<object>.Succeed(value);
|
||||
if (long.TryParse(input, out var value))
|
||||
{
|
||||
return Attempt<object>.Succeed(value);
|
||||
}
|
||||
|
||||
// same as int
|
||||
decimal value2;
|
||||
// Same as int
|
||||
var input2 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.If(decimal.TryParse(input2, out value2), Convert.ToInt64(value2));
|
||||
return Attempt<object>.If(decimal.TryParse(input2, out var value2), Convert.ToInt64(value2));
|
||||
}
|
||||
|
||||
// fixme - should we do the decimal trick for short, byte, unsigned?
|
||||
// TODO: Should we do the decimal trick for short, byte, unsigned?
|
||||
|
||||
if (destinationType == typeof(bool)) // aka Boolean
|
||||
if (target == typeof(bool))
|
||||
{
|
||||
bool value;
|
||||
if (bool.TryParse(input, out value)) return Attempt<object>.Succeed(value);
|
||||
// don't declare failure so the CustomBooleanTypeConverter can try
|
||||
if (bool.TryParse(input, out var value))
|
||||
{
|
||||
return Attempt<object>.Succeed(value);
|
||||
}
|
||||
|
||||
// Don't declare failure so the CustomBooleanTypeConverter can try
|
||||
return null;
|
||||
}
|
||||
|
||||
if (destinationType == typeof(short)) // aka Int16
|
||||
// Calling this method directly is faster than any attempt to cache it.
|
||||
switch (Type.GetTypeCode(target))
|
||||
{
|
||||
short value;
|
||||
return Attempt<object>.If(short.TryParse(input, out value), value);
|
||||
}
|
||||
case TypeCode.Int16:
|
||||
return Attempt<object>.If(short.TryParse(input, out var value), value);
|
||||
|
||||
if (destinationType == typeof(double)) // aka Double
|
||||
{
|
||||
double value;
|
||||
var input2 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.If(double.TryParse(input2, out value), value);
|
||||
}
|
||||
case TypeCode.Double:
|
||||
var input2 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.If(double.TryParse(input2, out var valueD), valueD);
|
||||
|
||||
if (destinationType == typeof(float)) // aka Single
|
||||
{
|
||||
float value;
|
||||
var input2 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.If(float.TryParse(input2, out value), value);
|
||||
}
|
||||
case TypeCode.Single:
|
||||
var input3 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.If(float.TryParse(input3, out var valueF), valueF);
|
||||
|
||||
if (destinationType == typeof(char)) // aka Char
|
||||
{
|
||||
char value;
|
||||
return Attempt<object>.If(char.TryParse(input, out value), value);
|
||||
}
|
||||
case TypeCode.Char:
|
||||
return Attempt<object>.If(char.TryParse(input, out var valueC), valueC);
|
||||
|
||||
if (destinationType == typeof(byte)) // aka Byte
|
||||
{
|
||||
byte value;
|
||||
return Attempt<object>.If(byte.TryParse(input, out value), value);
|
||||
}
|
||||
case TypeCode.Byte:
|
||||
return Attempt<object>.If(byte.TryParse(input, out var valueB), valueB);
|
||||
|
||||
if (destinationType == typeof(sbyte)) // aka SByte
|
||||
{
|
||||
sbyte value;
|
||||
return Attempt<object>.If(sbyte.TryParse(input, out value), value);
|
||||
}
|
||||
case TypeCode.SByte:
|
||||
return Attempt<object>.If(sbyte.TryParse(input, out var valueSb), valueSb);
|
||||
|
||||
if (destinationType == typeof(uint)) // aka UInt32
|
||||
{
|
||||
uint value;
|
||||
return Attempt<object>.If(uint.TryParse(input, out value), value);
|
||||
}
|
||||
case TypeCode.UInt32:
|
||||
return Attempt<object>.If(uint.TryParse(input, out var valueU), valueU);
|
||||
|
||||
if (destinationType == typeof(ushort)) // aka UInt16
|
||||
{
|
||||
ushort value;
|
||||
return Attempt<object>.If(ushort.TryParse(input, out value), value);
|
||||
}
|
||||
case TypeCode.UInt16:
|
||||
return Attempt<object>.If(ushort.TryParse(input, out var valueUs), valueUs);
|
||||
|
||||
if (destinationType == typeof(ulong)) // aka UInt64
|
||||
{
|
||||
ulong value;
|
||||
return Attempt<object>.If(ulong.TryParse(input, out value), value);
|
||||
case TypeCode.UInt64:
|
||||
return Attempt<object>.If(ulong.TryParse(input, out var valueUl), valueUl);
|
||||
}
|
||||
}
|
||||
else if (destinationType == typeof(Guid))
|
||||
else if (target == typeof(Guid))
|
||||
{
|
||||
Guid value;
|
||||
return Attempt<object>.If(Guid.TryParse(input, out value), value);
|
||||
return Attempt<object>.If(Guid.TryParse(input, out var value), value);
|
||||
}
|
||||
else if (destinationType == typeof(DateTime))
|
||||
else if (target == typeof(DateTime))
|
||||
{
|
||||
DateTime value;
|
||||
if (DateTime.TryParse(input, out value))
|
||||
if (DateTime.TryParse(input, out var value))
|
||||
{
|
||||
switch (value.Kind)
|
||||
{
|
||||
case DateTimeKind.Unspecified:
|
||||
case DateTimeKind.Utc:
|
||||
return Attempt<object>.Succeed(value);
|
||||
|
||||
case DateTimeKind.Local:
|
||||
return Attempt<object>.Succeed(value.ToUniversalTime());
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
return Attempt<object>.Fail();
|
||||
}
|
||||
else if (destinationType == typeof(DateTimeOffset))
|
||||
else if (target == typeof(DateTimeOffset))
|
||||
{
|
||||
DateTimeOffset value;
|
||||
return Attempt<object>.If(DateTimeOffset.TryParse(input, out value), value);
|
||||
return Attempt<object>.If(DateTimeOffset.TryParse(input, out var value), value);
|
||||
}
|
||||
else if (destinationType == typeof(TimeSpan))
|
||||
else if (target == typeof(TimeSpan))
|
||||
{
|
||||
TimeSpan value;
|
||||
return Attempt<object>.If(TimeSpan.TryParse(input, out value), value);
|
||||
return Attempt<object>.If(TimeSpan.TryParse(input, out var value), value);
|
||||
}
|
||||
else if (destinationType == typeof(decimal)) // aka Decimal
|
||||
else if (target == typeof(decimal))
|
||||
{
|
||||
decimal value;
|
||||
var input2 = NormalizeNumberDecimalSeparator(input);
|
||||
return Attempt<object>.If(decimal.TryParse(input2, out value), value);
|
||||
return Attempt<object>.If(decimal.TryParse(input2, out var value), value);
|
||||
}
|
||||
else if (destinationType == typeof(Version))
|
||||
else if (input != null && target == typeof(Version))
|
||||
{
|
||||
Version value;
|
||||
return Attempt<object>.If(Version.TryParse(input, out value), value);
|
||||
return Attempt<object>.If(Version.TryParse(input, out var value), value);
|
||||
}
|
||||
|
||||
// E_NOTIMPL IPAddress, BigInteger
|
||||
|
||||
return null; // we can't decide...
|
||||
return null; // we can't decide...
|
||||
}
|
||||
|
||||
private static readonly char[] NumberDecimalSeparatorsToNormalize = {'.', ','};
|
||||
|
||||
private static string NormalizeNumberDecimalSeparator(string s)
|
||||
{
|
||||
var normalized = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator[0];
|
||||
return s.ReplaceMany(NumberDecimalSeparatorsToNormalize, normalized);
|
||||
}
|
||||
|
||||
internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname)
|
||||
{
|
||||
//TODO: Localise this exception
|
||||
@@ -475,8 +470,7 @@ namespace Umbraco.Core
|
||||
/// <param name="o"></param>
|
||||
/// <param name="ignoreProperties"></param>
|
||||
/// <returns></returns>
|
||||
public static IDictionary<string, TVal> ToDictionary<T, TProperty, TVal>(this T o,
|
||||
params Expression<Func<T, TProperty>>[] ignoreProperties)
|
||||
public static IDictionary<string, TVal> ToDictionary<T, TProperty, TVal>(this T o, params Expression<Func<T, TProperty>>[] ignoreProperties)
|
||||
{
|
||||
return o.ToDictionary<TVal>(ignoreProperties.Select(e => o.GetPropertyInfo(e)).Select(propInfo => propInfo.Name).ToArray());
|
||||
}
|
||||
@@ -563,7 +557,7 @@ namespace Umbraco.Core
|
||||
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))
|
||||
? "{{ {0} }}".InvariantFormat(string.Join(", ", items))
|
||||
: null;
|
||||
}
|
||||
|
||||
@@ -585,9 +579,9 @@ namespace Umbraco.Core
|
||||
{
|
||||
var items =
|
||||
(from propertyInfo in props
|
||||
let value = GetPropertyDebugString(propertyInfo, obj, levels)
|
||||
where value != null
|
||||
select "{0}={1}".InvariantFormat(propertyInfo.Name, value)).ToArray();
|
||||
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))
|
||||
@@ -690,7 +684,109 @@ namespace Umbraco.Core
|
||||
|
||||
internal static Guid AsGuid(this object value)
|
||||
{
|
||||
return value is Guid ? (Guid) value : Guid.Empty;
|
||||
return value is Guid guid ? guid : Guid.Empty;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static string NormalizeNumberDecimalSeparator(string s)
|
||||
{
|
||||
var normalized = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator[0];
|
||||
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 GetCachedSourceTypeConverter(Type source, Type target)
|
||||
{
|
||||
var key = new CompositeTypeTypeKey(source, target);
|
||||
|
||||
if (InputTypeConverterCache.TryGetValue(key, out var typeConverter))
|
||||
{
|
||||
return typeConverter;
|
||||
}
|
||||
|
||||
var 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)
|
||||
{
|
||||
var key = new CompositeTypeTypeKey(source, target);
|
||||
|
||||
if (DestinationTypeConverterCache.TryGetValue(key, out var 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)
|
||||
{
|
||||
if (NullableGenericCache.TryGetValue(type, out var 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(object input, Type source, Type target)
|
||||
{
|
||||
var key = new CompositeTypeTypeKey(source, target);
|
||||
if (AssignableTypeCache.TryGetValue(key, out var canConvert))
|
||||
{
|
||||
return canConvert;
|
||||
}
|
||||
|
||||
// "object is" is faster than "Type.IsAssignableFrom.
|
||||
// We can use it to very quickly determine whether true/false
|
||||
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)
|
||||
{
|
||||
if (BoolConvertCache.TryGetValue(type, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (CustomBooleanTypeConverter.CanConvertFrom(type))
|
||||
{
|
||||
return BoolConvertCache[type] = true;
|
||||
}
|
||||
|
||||
return BoolConvertCache[type] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user