// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TypeHelper.cs" company="Catel development team">
// Copyright (c) 2008 - 2011 Catel development team. All rights reserved.
// </copyright>
// <summary>
// <see cref="Type" /> helper class.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Catel.Properties;
using log4net;
#if SILVERLIGHT
using Catel.Reflection;
#endif
namespace Catel
{
/// <summary>
/// <see cref="Type"/> helper class.
/// </summary>
public static class TypeHelper
{
#region Variables
/// <summary>
/// The <see cref="ILog">log</see> object.
/// </summary>
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
#endregion
#region Methods
/// <summary>
/// Determines whether the subclass is of a raw generic type.
/// </summary>
/// <param name="generic">The generic.</param>
/// <param name="toCheck">The type to check.</param>
/// <returns>
/// <c>true</c> if the subclass is of a raw generic type; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">when <paramref name="generic"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException">when <paramref name="toCheck"/> is <c>null</c>.</exception>
/// <remarks>
/// This implementation is based on this forum thread:
/// http://stackoverflow.com/questions/457676/c-reflection-check-if-a-class-is-derived-from-a-generic-class
/// </remarks>
public static bool IsSubclassOfRawGeneric(Type generic, Type toCheck)
{
if (generic == null)
{
throw new ArgumentNullException("generic");
}
if (toCheck == null)
{
throw new ArgumentNullException("toCheck");
}
while ((toCheck != null) && (toCheck != typeof(object)))
{
var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
if (generic == cur)
{
return true;
}
toCheck = toCheck.BaseType;
}
return false;
}
/// <summary>
/// Formats a type in the official type description like [typename], [assemblyname].
/// </summary>
/// <param name="assembly">Assembly name to format.</param>
/// <param name="type">Type name to format.</param>
/// <returns>Type name like [typename], [assemblyname].</returns>
public static string FormatType(string assembly, string type)
{
return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", type, assembly);
}
/// <summary>
/// Formats multiple inner types into one string.
/// </summary>
/// <param name="innerTypes">The inner types.</param>
/// <returns>
/// string representing a combination of all inner types.
/// </returns>
public static string FormatInnerTypes(string[] innerTypes)
{
string result = string.Empty;
for (int i = 0; i < innerTypes.Length; i++)
{
result += string.Format(CultureInfo.InvariantCulture, "[{0}]", innerTypes[i]);
// Postfix a comma if this is not the last
if (i < innerTypes.Length - 1)
{
result += ",";
}
}
return result;
}
/// <summary>
/// Returns whether a type is nullable or not.
/// </summary>
/// <param name="type">Type to check.</param>
/// <returns>
/// True if the type is nullable, otherwise false.
/// </returns>
public static bool IsTypeNullable(Type type)
{
if (type == null)
{
return false;
}
if (!type.IsValueType)
{
return true;
}
if (Nullable.GetUnderlyingType(type) != null)
{
return true;
}
return false;
}
/// <summary>
/// Checks whether the 2 specified objects are equal. This method is better, simple because it also checks boxing so
/// 2 integers with the same values that are boxed are equal.
/// </summary>
/// <param name="object1">The first object.</param>
/// <param name="object2">The second object.</param>
/// <returns><c>true</c> if the objects are equal; otherwise <c>false</c>.</returns>
public static bool AreObjectsEqual(object object1, object object2)
{
if ((object1 == null) && (object2 == null))
{
return true;
}
if ((object1 == null) || (object2 == null))
{
return false;
}
return object1.Equals(object2);
}
/// <summary>
/// Gets the <see cref="PropertyInfo"/> for the specified property.
/// </summary>
/// <param name="type">The type that contains the properties.</param>
/// <param name="property">The property name.</param>
/// <returns>
/// <see cref="PropertyInfo"/> or <c>null</c> if no property info is found.
/// </returns>
public static PropertyInfo GetPropertyInfo(Type type, string property)
{
PropertyInfo propertyInfo = type.GetProperty(property, BindingFlags.Public | BindingFlags.Instance);
if (propertyInfo == null)
{
propertyInfo = type.GetProperty(property, BindingFlags.NonPublic | BindingFlags.Instance);
}
if (propertyInfo == null)
{
Log.Warn(TraceMessages.CannotFindProperty, property);
}
return propertyInfo;
}
#endregion
#region [ object size ]
/// <summary>
/// Cache of type sizes.
/// </summary>
private static readonly IDictionary<Type, int> TypeSizes = InitializeSizeHelper();
/// <summary>
/// Initializes the size helper.
/// </summary>
/// <returns>Dictionary containing the type and its size.</returns>
private static IDictionary<Type, int> InitializeSizeHelper()
{
IDictionary<Type, int> rv = new Dictionary<Type, int>();
rv.Add(typeof(DateTime), 8);
rv.Add(typeof(Pointer), 8); // Prevent endless loops.
rv.Add(typeof(long), 8);
rv.Add(typeof(int), 4);
rv.Add(typeof(short), 2);
rv.Add(typeof(ulong), 8);
rv.Add(typeof(uint), 4);
rv.Add(typeof(ushort), 2);
return rv;
}
/// <summary>
/// Determine the size of any object.
/// </summary>
/// <param name="obj">The object you want to know the size of.</param>
/// <returns>A size in bytes, or -1 if the request failed.</returns>
public static int SizeOf(object obj)
{
int sizeValue;
try
{
sizeValue = ReflectiveSizeOf(obj);
}
catch
{
sizeValue = -1;
}
return sizeValue;
}
/// <summary>
/// Determines the reflective size of any object.
/// </summary>
/// <param name="obj">The object you want to know the size of.</param>
/// <returns>A size in bytes, or -1 if the request failed.</returns>
private static int ReflectiveSizeOf(object obj)
{
int size = 0;
if (obj != null)
{
Queue<object> objects = new Queue<object>();
Queue<object> done = new Queue<object>();
objects.Enqueue(obj);
while (objects.Count > 0)
{
object target = objects.Dequeue();
int targetSize = -1;
done.Enqueue(target);
if (target == null)
{
targetSize = 0;
}
else
{
try
{
Type targetType = target.GetType();
if (targetType.IsEnum)
{
targetType = Enum.GetUnderlyingType(targetType);
}
if (TypeSizes.ContainsKey(targetType))
{
if (targetSize == -1)
{
targetSize = 0;
}
targetSize += TypeSizes[targetType];
}
else
{
if (targetType.IsValueType)
{
// int ts2 = System.Runtime.InteropServices.Marshal.SizeOf(targetType);
targetSize = System.Runtime.InteropServices.Marshal.SizeOf(target);
}
else
{
if (target is Delegate)
{
targetSize += TypeSizes[typeof(Pointer)];
}
else
{
targetSize = -2;
}
}
}
}
catch (Exception)
{
targetSize = -2;
}
}
if (target != null && targetSize < 0)
{
Type targetType = target.GetType();
FieldInfo[] fields = targetType.GetFields(BindingFlags.Instance | BindingFlags.GetField | BindingFlags.Public | BindingFlags.NonPublic);
foreach (FieldInfo f in fields)
{
object fieldValue = f.GetValue(target);
if (fieldValue != null && !objects.Contains(fieldValue) && !done.Contains(fieldValue))
{
objects.Enqueue(fieldValue);
}
}
if (target is IEnumerable)
{
if (target is string)
{
string targetAsString = target as string;
size += targetAsString.Length;
}
else
{
IEnumerable col = target as IEnumerable;
foreach (object c in col)
{
if (c != null && !objects.Contains(c) && !done.Contains(c))
{
objects.Enqueue(c);
}
}
}
}
}
else
{
size += targetSize;
}
}
objects.Clear(); // Should already be empty.
done.Clear();
}
return size;
}
#endregion
#region TryParseEx
/// <summary>
/// Helper class for converting Strings to GUIDs.
/// </summary>
public static class Parser
{
/// <summary>
/// Pattern guid tester.
/// </summary>
private const string PatternGuidTester = "^[0-9A-F]{8}(-?)[0-9A-F]{4}\\1[0-9A-F]{4}\\1[0-9A-F]{4}\\1[0-9A-F]{12}$";
/// <summary>
/// The <see cref="Regex"/> containing the guid tester.
/// </summary>
private static readonly Regex RxGuidTester = new Regex(PatternGuidTester, RegexOptions.IgnoreCase | RegexOptions.Singleline);
/// <summary>
/// Tries to parse a string to a guid.
/// </summary>
/// <param name="input">The string version of the guid. Currently supported layouts dddddddd-dddd-dddd-dddd-dddddddddddd and dddddddddddddddddddddddddddddddd where d is a hexadecimal digit.</param>
/// <param name="output">When the parse succeeds this is the guid else it is set to Guid.Empty.</param>
/// <returns>False when the parse failed else true.</returns>
public static bool TryParseGuid(string input, out Guid output)
{
bool hasFailed = string.IsNullOrEmpty(input) || !RxGuidTester.IsMatch(input);
output = !hasFailed ? new Guid(input) : Guid.Empty;
return !hasFailed;
}
}
#endregion TryParseEx
#region Powercast
#region Typed input and output
/// <summary>
/// Tries to Generic cast of a value.
/// </summary>
/// <typeparam name="TOutput">Requested return type.</typeparam>
/// <typeparam name="TInput">The input type.</typeparam>
/// <param name="value">The value to cast.</param>
/// <param name="output">The casted value.</param>
/// <returns>When a cast is succeded true else false.</returns>
public static bool TryCast<TOutput, TInput>(TInput value, out TOutput output)
{
bool success = true;
Type outputType = typeof(TOutput);
Type innerType = Nullable.GetUnderlyingType(outputType);
// Database support...
if (value == null || Convert.IsDBNull(value))
{
output = default(TOutput);
if (outputType.IsValueType && innerType == null)
{
success = false;
}
else
{
// Non-valuetype can contain nill.
// (Nullable<T> also)
success = true;
}
}
else
{
Type inputType = value.GetType();
if (inputType.IsAssignableFrom(outputType))
{
// Direct assignable
success = true;
output = (TOutput)(object)value;
}
else
{
output = (TOutput)Convert.ChangeType(value, innerType ?? outputType, CultureInfo.InvariantCulture);
success = true;
}
}
return success;
}
/// <summary>
/// Generic cast of a value.
/// </summary>
/// <typeparam name="TOutput">Requested return type.</typeparam>
/// <typeparam name="TInput">The input type.</typeparam>
/// <param name="value">The value to cast.</param>
/// <returns>The casted value.</returns>
public static TOutput Cast<TOutput, TInput>(TInput value)
{
TOutput output = default(TOutput);
if (!TryCast(value, out output))
{
string tI = typeof(TInput).FullName;
string tO = typeof(TOutput).FullName;
string vl = string.Concat(value);
string msg = "Failed to cast from '{0}' to '{1}'";
if (!tI.Equals(vl))
{
if (value == null)
{
vl = "nill-value";
}
msg = string.Concat(msg, " for value '{2}'");
}
msg = string.Format(msg, tI, tO, vl);
throw new InvalidCastException(msg);
}
return output;
}
/// <summary>
/// Generic cast of a value.
/// </summary>
/// <typeparam name="TOutput">Requested return type.</typeparam>
/// <typeparam name="TInput">The input type.</typeparam>
/// <param name="value">The value to cast.</param>
/// <param name="whenNullValue">When unable to cast the incoming value, this value is returned instead.</param>
/// <returns>The casted value or when uncastable the <paramref name="whenNullValue" /> is returned.</returns>
public static TOutput Cast<TOutput, TInput>(TInput value, TOutput whenNullValue)
{
TOutput output;
if (!TryCast(value, out output) || output == null)
{
output = whenNullValue;
}
return output;
}
#endregion
#region Unknown input type / common object input
/// <summary>
/// Generic cast of a value.
/// </summary>
/// <typeparam name="TOutput">Requested return type.</typeparam>
/// <param name="input">The object to cast.</param>
/// <returns>The casted value.</returns>
public static TOutput Cast<TOutput>(object input)
{
return Cast<TOutput, object>(input);
}
/// <summary>
/// Generic cast of a value.
/// </summary>
/// <typeparam name="TOutput">Requested return type.</typeparam>
/// <param name="input">The input.</param>
/// <param name="whenNullValue">When unable to cast the incoming value, this value is returned instead.</param>
/// <returns>
/// The casted value or when uncastable the <paramref name="whenNullValue"/> is returned.
/// </returns>
public static TOutput Cast<TOutput>(object input, TOutput whenNullValue)
{
return Cast<TOutput, object>(input, whenNullValue);
}
#endregion
#endregion
#region Reflection helpers
/// <summary>
/// Gets a specific property from an object.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <param name="property">The name of the property to get.</param>
/// <param name="sourceObject">The source object.</param>
/// <returns>The property.</returns>
public static TProperty PropertyGet<TProperty, TObject>(string property, TObject sourceObject)
{
// Use cache?
if (string.IsNullOrEmpty(property))
{
throw new ArgumentNullException("property", "Parameter is Null or Empty.");
}
if (sourceObject == null)
{
throw new ArgumentNullException("sourceObject");
}
Type objType = typeof(TObject);
PropertyInfo pi = objType.GetProperty(property, typeof(TProperty), Type.EmptyTypes);
if (pi == null || !pi.CanRead)
{
string msg;
if (pi == null)
{
msg = "No public {2}-property found with the name '{0}' in type '{1}'";
}
else
{
msg = "{2}-property '{0}' in type '{1}' cannot be read.";
}
msg = string.Format(msg, property, objType.FullName, typeof(TProperty).Name);
throw new Exception(msg);
}
MethodInfo mi = pi.GetGetMethod();
object value = mi.Invoke(sourceObject, new object[0]);
return Cast<TProperty>(value);
}
/// <summary>
/// Gets the property value.
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="propertyPath">The property path.</param>
/// <returns>The property value.</returns>
public static object GetPropertyValue(object instance, string propertyPath)
{
if (instance == null)
{
throw new ArgumentNullException("instance");
}
if (string.IsNullOrEmpty(propertyPath))
{
throw new ArgumentNullException("propertyPath");
}
return GetMemberValue(instance.GetType(), instance, propertyPath);
}
/// <summary>
/// This method gives you the ability to read values of private fields of an object.
/// </summary>
/// <param name="instance">The instance of the object of which you want to know the private value.</param>
/// <param name="fieldName">The name of the field you want to know... when you want to know a field of an private object of an object you can use the common OO-notation.</param>
/// <returns>
/// The value of the object.
/// <para />
/// NOTE: When the Field is not found the object, an exception is thrown.
/// </returns>
public static object GetField(object instance, string fieldName)
{
Type instanceType = instance.GetType();
return GetField(instanceType, instance, fieldName);
}
/// <summary>
/// This method gives you the ability to read values of private fields of an object.
/// </summary>
/// <param name="typeInterface">The interface use want to use when interpeting the class</param>
/// <param name="instance">The instance of the object of which you want to know the private value.</param>
/// <param name="fieldName">The name of the field you want to know... when you want to know a field of an private object of an object you can use the common OO-notation.</param>
/// <returns>
/// The value of the object.
/// <para />
/// NOTE: When the Field is not found the object, an exception is thrown.
/// </returns>
public static object GetField(Type typeInterface, object instance, string fieldName)
{
// TODO: Use GetMemberInfo
int firstIndexOfPeriod = fieldName.IndexOf('.');
string firstPart = (firstIndexOfPeriod == -1) ? fieldName : fieldName.Substring(0, firstIndexOfPeriod);
string restPart = (firstIndexOfPeriod == -1) ? null : fieldName.Substring(firstIndexOfPeriod + 1);
object fieldValue;
FieldInfo fieldDesc = typeInterface.GetField(firstPart, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField);
if (fieldDesc == null)
{
if (typeInterface.BaseType == null)
{
// TODO: Use an dedicated exception-class
throw new Exception("Field not found in object-type (or any base-type).");
}
fieldValue = GetField(typeInterface.BaseType, instance, fieldName);
}
else
{
fieldValue = fieldDesc.GetValue(instance);
if (restPart != null)
{
fieldValue = GetField(fieldValue, restPart);
}
}
return fieldValue;
}
/// <summary>
/// Gets the member info.
/// </summary>
/// <param name="typeInterface">The type interface.</param>
/// <param name="instance">The instance.</param>
/// <param name="fieldName">Name of the field.</param>
/// <returns>The value of the object.</returns>
private static object GetMemberValue(Type typeInterface, object instance, string fieldName)
{
// TODO: Helaas klopt er nu geen r(u)(ee)(k)(t) van....
// TODO: Validate typeFlag
int firstIndexOfPeriod = fieldName.IndexOf('.');
string firstPart = (firstIndexOfPeriod == -1) ? fieldName : fieldName.Substring(0, firstIndexOfPeriod);
string restPart = (firstIndexOfPeriod == -1) ? null : fieldName.Substring(firstIndexOfPeriod + 1);
MemberInfo[] memberDesc = typeInterface.GetMember(firstPart, MemberTypes.Field | MemberTypes.Property, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
object memberValue;
if (memberDesc == null || memberDesc.Length == 0)
{
if (typeInterface.BaseType == null)
{
// TODO: Use an dedicated exception-class
throw new Exception("Field not found in object-type (or any base-type).");
}
memberValue = GetMemberValue(typeInterface.BaseType, instance, fieldName);
}
else
{
if (memberDesc.Length > 1)
{
throw new NotSupportedException("Too many results.");
}
MemberInfo mi = memberDesc[0];
if (mi is FieldInfo)
{
FieldInfo fi = (FieldInfo)mi;
memberValue = fi.GetValue(instance);
}
else if (mi is PropertyInfo)
{
PropertyInfo pi = (PropertyInfo)mi;
memberValue = pi.GetValue(instance, null);
}
else
{
// Currently not supported.
throw new NotSupportedException();
}
if (restPart != null && memberValue != null)
{
memberValue = GetMemberValue(memberValue.GetType(), memberValue, restPart);
}
}
return memberValue;
}
/// <summary>
/// This method gives you the ability to change values of private fields of an object.
/// </summary>
/// <param name="instance">The instance of the object of which you want to change a field-value.</param>
/// <param name="fieldName">The field-name... when you want to know a field of an private object of an object you can use the common OO-notation.</param>
/// <param name="value">The value you want to put in the field, WARNING this method does NOT check the types of the objects.</param>
public static void SetField(object instance, string fieldName, object value)
{
Type instanceType = instance.GetType();
SetField(instanceType, instance, fieldName, value);
}
/// <summary>
/// This method gives you the ability to change values of private fields of an object.
/// </summary>
/// <param name="typeInterface">The interface use want to use when interpeting the class</param>
/// <param name="instance">The instance of the object of which you want to change a field-value.</param>
/// <param name="fieldName">The field-name... when you want to know a field of an private object of an object you can use the common OO-notation.</param>
/// <param name="value">The value you want to put in the field, WARNING this method does NOT check the types of the objects.</param>
public static void SetField(Type typeInterface, object instance, string fieldName, object value)
{
int firstIndexOfPeriod = fieldName.IndexOf('.');
string firstPart = (firstIndexOfPeriod == -1) ? fieldName : fieldName.Substring(0, firstIndexOfPeriod);
string restPart = (firstIndexOfPeriod == -1) ? null : fieldName.Substring(firstIndexOfPeriod + 1);
FieldInfo fieldDesc = typeInterface.GetField(firstPart, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField);
if (fieldDesc == null)
{
if (typeInterface.BaseType == null)
{
throw new Exception("Field not found in object-type (or any base-type).");
}
SetField(typeInterface.BaseType, instance, fieldName, value);
}
else
{
if (restPart != null)
{
object fieldValue = fieldDesc.GetValue(instance);
SetField(fieldValue, restPart, value);
if (fieldValue is ValueType)
{
fieldDesc.SetValue(instance, fieldValue);
}
}
else
{
fieldDesc.SetValue(instance, value);
}
}
}
#endregion
#region Hash-tool
/// <summary>
/// Combine multiple hashcodes in to one.
/// </summary>
/// <param name="hashes">An array of hashcodes.</param>
/// <returns>An 'unique' hashcode.</returns>
/// <remarks>
/// Based on System.Web.UI.HashCodeCombiner (use Reflector).
/// </remarks>
public static int CombineHash(params int[] hashes)
{
if (hashes == null || hashes.Length == 0)
{
throw new ArgumentException("Array can't be null or empty.", "hashes");
}
int hash = 5381; // 0x1505L
foreach (int inp in hashes)
{
hash = ((hash << 5) + hash) ^ inp;
}
// Make sure the hash is not negative
if (hash < 0)
{
hash = hash * -1;
}
// Return hash
return hash;
}
#endregion
#region Dispose helper function
/// <summary>
/// Clears all events.
/// </summary>
/// <param name="obj">The object.</param>
public static void ClearAllEvents(object obj)
{
// Geen object, geen events.
if (obj == null)
{
return;
}
Type objectType = obj.GetType();
EventInfo[] eis = objectType.GetEvents();
// Ga alle events van dit object af.
foreach (EventInfo ei in eis)
{
UnsubscribeAll(obj, ei);
}
}
/// <summary>
/// Unsubscribes all.
/// </summary>
/// <param name="obj">The object.</param>
/// <param name="ei">The event info.</param>
private static void UnsubscribeAll(object obj, EventInfo ei)
{
if (ei.EventHandlerType.BaseType != typeof(MulticastDelegate))
{
// Geen MulticastDelegate => geen event.
return;
}
MethodInfo invoke = ei.EventHandlerType.GetMethod("Invoke");
if (invoke == null)
{
// Geen Invoke-method => geen event.
return;
}
Type objectType = obj.GetType();
FieldInfo eventField = objectType.GetField(ei.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (eventField == null)
{
return;
}
// Value van dit Event-field, de feitelijke storage van de delegates.
object eventValue = eventField.GetValue(obj);
if (eventValue == null)
{
return;
}
// Geregistreerde delegates ophalen.
MethodInfo getInvocationList = ei.EventHandlerType.GetMethod("GetInvocationList");
Delegate[] delegates = (Delegate[])getInvocationList.Invoke(eventValue, null);
// Elke delegate uit het event verwijderen.
foreach (Delegate handler in delegates)
{
ei.RemoveEventHandler(obj, handler);
}
}
/// <summary>
/// Clears the event.
/// </summary>
/// <param name="instance">The object instance.</param>
/// <param name="eventHandler">The event handler.</param>
public static void ClearEvent(object instance, object eventHandler)
{
if (instance == null)
{
return;
}
if (eventHandler == null)
{
return;
}
Type etype = eventHandler.GetType();
if (etype.BaseType != typeof(MulticastDelegate))
{
// Geen MulticastDelegate => geen event.
return;
}
MethodInfo invoke = etype.GetMethod("Invoke");
if (invoke == null)
{
// Geen Invoke-method => geen event.
return;
}
const BindingFlags AllInstance = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
Type instanceType = instance.GetType();
// Event info zoeken bij de geleverde event-storage
EventInfo eventInfo = (from fi in instanceType.GetFields(AllInstance)
let fieldvalue = fi.GetValue(instance)
where fieldvalue == eventHandler
select instanceType.GetEvent(fi.Name, AllInstance)).FirstOrDefault();
if (eventInfo == null)
{
return;
}
// Geregistreerde delegates ophalen.
MethodInfo getInvocationList = etype.GetMethod("GetInvocationList");
Delegate[] delegates = (Delegate[])getInvocationList.Invoke(eventHandler, null);
// Elke delegate uit het event verwijderen.
foreach (Delegate handler in delegates)
{
eventInfo.RemoveEventHandler(instance, handler);
}
}
#endregion
}
}