Set Nested Property Values using Reflections - c#

I've searched throughout but can't find the exact answer to my question. Take for instance the following code:
public class Company
{
private string m_strName;
private Customer m_objCustomer;
public Company()
{
m_strName = "";
m_objCustomer = new Customer();
}
public string Name
{
get { return m_strName; }
set { m_strName = value; }
}
public Customer CustomerInformaion
{
get { return m_objCustomer; }
set { m_objCustomer = value; }
}
}
public class Customer
{
private string m_strName;
private Details m_objDetails;
public Customer()
{
m_strName = "";
m_objDetails = new Details();
}
public string Name
{
get { return m_strName; }
set { m_strName = value; }
}
public Details CustomerDetails
{
get { return m_objDetails; }
set { m_objDetails = value; }
}
}
public class Details
{
private string m_strPhoneNumber;
private string m_strEmailAddress;
public Details()
{
m_strPhoneNumber = "";
m_strEmailAddress = "";
}
public string PhoneNumber
{
get { return m_strPhoneNumber; }
set { m_strPhoneNumber = value; }
}
public string EmailAddress
{
get { return m_strEmailAddress; }
set { m_strEmailAddress = value; }
}
}
Now, I have setup a Form that has many text fields where a user can enter information about a customer at a company. One of those fields is the Email Address text field that has a Tag property set to EmailAddress. I want to be able to look at the Tag of the TextBox and iterate through the entire Company object to find a property with a matching Name and Set its value to the Text property of the TextBox. I can locate the property but setting its value has turned out to be quite difficult. Here's what I have thus far:
foreach (PropertyInfo info in m_objCompany.GetType().GetProperties())
{
if (info.PropertyType != typeof(System.String))
{
foreach (PropertyInfo info2 in info.PropertyType.GetProperties())
{
if (objTextBox.Tag.Equals(info2.Name))
{
if (info2.CanWrite)
{
Object objValue = Convert.ChangeType(objTextBox.Text, info.PropertyType);
info2.SetValue(m_objCompany, objValue, null);
}
}
}
}
}
My issue is that when I run the code I receive an error at ChangeType and/or SetValue. The issue is that the Reflection is stopping at info2 and attempting to set the value to the Type of Details - since it is the parent of the Property EmailAddress.
Any help in determining how to point the SetValue to appropriate property would be helpful and appreciated. As I'm sure you well can guess my class is a GREAT deal larger than the example provided with near 100 properties. Most all are string values which will be entered in manually through TextBox objects. I'm attempting to create one routine that can then be called by all TextBox objects whereby the Tag property of the object could be used to indicate which Property of my class I'm trying to set. From there it is off to XML serialization land.

your innermost line
info2.SetValue(m_objCompany, objValue, null);
is trying to set value of the inner property (info2), on the outer object. The outer object doesn't have an inner object.
What you probably want, is something like this:
public void Bar(object m_objCompany)
{
foreach (PropertyInfo info in m_objCompany.GetType().GetProperties())
{
if (info.PropertyType != typeof(System.String))
{
// Somehow create the outer property
object outerPropertyValue = info.PropertyType.GetConstructor(new Type[] { }).Invoke(new object[] { });
foreach (PropertyInfo info2 in info.PropertyType.GetProperties())
{
if ("blah" == "blah")
{
if (info2.CanWrite)
{
Object innerPropertyValue = Convert.ChangeType("blah", info2.PropertyType);
info2.SetValue(outerPropertyValue, innerPropertyValue, null);
}
}
}
info.SetValue(m_objCompany, outerPropertyValue, null);
}
}
}
When you come across a property that you want to set, you need to create that property (outerPropertyValue), then setup the properties of that property (via innerPropertyValue), then set the outer property on the original object (m_objCompany).

Here is some code I use for Reflection. In this case you would want to call the SetValue method.
Reflector.SetValue(TARGET_OBJECT, "Customer.Details.PhoneNumber", "ValueToSet");
UPDATE: Added the missing ConversionResult struct. Sorry for the omission.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
namespace YourNamespace
{
public struct ConversionResult
{
public Boolean Success;
public object ConvertedValue;
}
public static class Reflector
{
private static BindingFlags DefaultBindings = BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic;
#region Public Methods
/// <summary>
/// Execute the "codeToExecute" string on the "source" object
/// </summary>
/// <param name="source">Object the code should be executed against</param>
/// <param name="codeToExecute">Code that should be executed ex. 'Person.Age'</param>
/// <returns>The result of execute codeToExecute on source</returns>
public static object GetValue(object source, String codeToExecute)
{
ReflectorResult reflectorResult = GetReflectorResult(source, codeToExecute, true, false);
if (reflectorResult != null)
{
return reflectorResult.Value;
}
return null;
}
/// <summary>
/// Sets the "source" object to the "value" specified in "codeToExecute"
/// </summary>
/// <param name="source">Object the code should be executed against</param>
/// <param name="codeToExecute">Code that should be executed ex. 'Person.Age'</param>
/// <param name="value">Value to set the source+codeToExecute to.</param>
public static Boolean SetValue(object source, String codeToExecute, object value)
{
return SetValue(source, codeToExecute, value, false);
}
/// <summary>
/// Sets the "source" object to the "value" specified in "codeToExecute"
/// </summary>
/// <param name="source">Object the code should be executed against</param>
/// <param name="codeToExecute">Code that should be executed ex. 'Person.Age'</param>
/// <param name="value">Value to set the source+codeToExecute to.</param>
/// <param name="createIfNotExists">Creates items it cannot find</param>
public static Boolean SetValue(object source, String codeToExecute, object value, Boolean createIfNotExists)
{
Boolean executed = true;
ReflectorResult reflectorResult = GetReflectorResult(source, codeToExecute, false, createIfNotExists);
if (reflectorResult != null)
{
TypeConverter typeConverter = null;
PropertyInfo propertyInfo = reflectorResult.MemberInfo as PropertyInfo;
if (propertyInfo != null)
{
if (propertyInfo.CanWrite)
{
typeConverter = GetTypeConverter(propertyInfo);
ConversionResult conversionResult = ConvertValue(value, propertyInfo.PropertyType, typeConverter);
if (conversionResult.Success)
{
propertyInfo.SetValue(reflectorResult.PreviousValue, conversionResult.ConvertedValue, reflectorResult.MemberInfoParameters);
}
else
{
executed = false;
PentaLogger.LogVerbose("Invalid value: " + value);
}
}
}
else
{
FieldInfo fieldInfo = reflectorResult.MemberInfo as FieldInfo;
if (fieldInfo != null)
{
typeConverter = GetTypeConverter(fieldInfo);
ConversionResult conversionResult = ConvertValue(value, fieldInfo.FieldType, typeConverter);
if (conversionResult.Success)
{
fieldInfo.SetValue(reflectorResult.PreviousValue, conversionResult.ConvertedValue);
}
else
{
executed = false;
PentaLogger.LogVerbose("Invalid value: " + value);
}
}
else
{
// both property and field are invalid
executed = false;
}
}
}
else
{
executed = false;
}
return executed;
}
/// <summary>
/// Sets the "source" object to the "value" specified in "codeToExecute"
/// </summary>
/// <param name="source">Object the code should be executed against</param>
/// <param name="codeToExecute">Code that should be executed ex. 'Person.Age'</param>
/// <param name="value">Value to set the source+codeToExecute to.</param>
public static void RunDynamicCode(object source, String codeToExecute)
{
GetReflectorResult(source, codeToExecute, true, false);
}
/// <summary>
/// Executes the method on the "source" object with the passed parameters
/// </summary>
/// <param name="source">Object the code should be executed against</param>
/// <param name="methodName">Method to call</param>
/// <param name="parameters">Method Parameters</param>
public static object ExecuteMethod(object source, String methodName, object[] parameters)
{
if (parameters == null)
{
parameters = new object[0];
}
MethodInfo[] methodInfos = GetMethods(source, methodName);
foreach (MethodInfo methodInfo in methodInfos)
{
object[] convertedParameters = GetParameters(methodInfo, parameters);
if (convertedParameters != null)
{
return methodInfo.Invoke(source, convertedParameters);
}
}
return null;
}
/// <summary>
/// Executes the method on the "source" object with the passed parameters
/// </summary>
/// <param name="source">Object the code should be executed against</param>
/// <param name="methodName">Method to call</param>
/// <param name="parameter">Method Parameter</param>
public static object ExecuteMethod(object source, String methodName, object parameter)
{
return ExecuteMethod(source, methodName, new object[] { parameter });
}
/// <summary>
/// Executes the method on the "source" object with no parameters
/// </summary>
/// <param name="source">Object the code should be executed against</param>
/// <param name="methodName">Method to call</param>
public static object ExecuteMethod(object source, String methodName)
{
return ExecuteMethod(source, methodName, null);
}
/// <summary>
/// Copies all public properties and fields from source to target
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
public static void CopyObject(object source, object target)
{
if (source != null && target != null)
{
Type targetType = target.GetType();
Type sourceType = source.GetType();
PropertyInfo[] properties = sourceType.GetProperties(DefaultBindings);
foreach (PropertyInfo sourceProperty in properties)
{
PropertyInfo targetProperty = targetType.GetProperty(sourceProperty.Name, sourceProperty.PropertyType);
if (targetProperty != null && targetProperty.CanRead && targetProperty.CanWrite)
{
object value = sourceProperty.GetValue(source, null);
targetProperty.SetValue(target, value, null);
}
}
FieldInfo[] fields = sourceType.GetFields(DefaultBindings);
foreach (FieldInfo sourceField in fields)
{
FieldInfo targetField = targetType.GetField(sourceField.Name);
if (targetField != null && targetField.IsPublic)
{
object value = sourceField.GetValue(source);
targetField.SetValue(target, value);
}
}
}
}
/// <summary>
/// Convert the object to the correct type
/// </summary>
/// <param name="value">Value to convert</param>
/// <param name="type">Type to convert to</param>
/// <returns>Converted value</returns>
public static ConversionResult ConvertValue(object value, Type type, TypeConverter typeConverter)
{
ConversionResult conversionResult = new ConversionResult();
conversionResult.Success = false;
if (value != null && type != null)
{
Type objectType = value.GetType();
if (objectType == type)
{
conversionResult.Success = true;
conversionResult.ConvertedValue = value;
}
else
{
// If there is an explicit type converter use it
if (typeConverter != null && typeConverter.CanConvertFrom(objectType))
{
try
{
conversionResult.ConvertedValue = typeConverter.ConvertFrom(value);
conversionResult.Success = true;
}
catch (FormatException) { }
catch (Exception e)
{
if (!(e.InnerException is FormatException))
{
throw;
}
}
}
else
{
try
{
conversionResult.ConvertedValue = Convert.ChangeType(value, type, CultureInfo.CurrentCulture);
conversionResult.Success = true;
}
catch (InvalidCastException) { }
}
}
}
return conversionResult;
}
public static Boolean CanCreateObect(String classPath, Assembly assembly, params object[] parameters)
{
Boolean canCreate = false;
Type type = Type.GetType(classPath);
if (type == null)
{
String pathWithAssembly = classPath + ", " + assembly.FullName;
type = Type.GetType(pathWithAssembly);
}
if (type != null)
{
foreach (ConstructorInfo ci in type.GetConstructors())
{
if (ci.IsPublic)
{
ParameterInfo[] constructorParameters = ci.GetParameters();
if (constructorParameters.Length == parameters.Length)
{
for(Int32 i=0; i<constructorParameters.Length; i++)
{
object parameter = parameters[i];
if(parameter == null)
{
continue;
}
ParameterInfo pi = constructorParameters[i];
if (!pi.ParameterType.IsAssignableFrom(parameter.GetType()))
{
break;
}
}
canCreate = true;
break;
}
}
}
}
return canCreate;
}
public static object CreateObject(String classPath, Assembly assembly, params object[] parameters)
{
Type type = Type.GetType(classPath);
if (type == null)
{
String pathWithAssembly = classPath + ", " + assembly.FullName;
type = Type.GetType(pathWithAssembly);
}
if (type == null)
{
return null;
}
return Activator.CreateInstance(type, parameters);
}
#endregion
#region Private Methods
private static ReflectorResult GetReflectorResult(object source, String codeToExecute, bool getLastValue, bool createIfNotExists)
{
ReflectorResult result = new ReflectorResult(source);
try
{
// Split the code into usable fragments
String[] codeFragments = SplitCodeArray(codeToExecute);
for (Int32 i = 0; i < codeFragments.Length; i++)
{
// if the value is null we cannot go any deeper so don't waste your time
if (result.Value == null)
{
return result;
}
String codeFragment = codeFragments[i];
result.PreviousValue = result.Value;
if (codeFragment.Contains("]"))
{
ProcessArray(result, codeFragment, createIfNotExists);
}
else if (codeFragment.Contains(")"))
{
ProcessMethod(result, codeFragment);
}
else
{
// For set properties we do not need the last value
bool retrieveValue = getLastValue;
if (!retrieveValue)
{
// If this is not the last one in the array, get it anyway
retrieveValue = i + 1 != codeFragments.Length;
}
ProcessProperty(result, codeFragment, retrieveValue);
}
}
}
catch (InvalidCodeFragmentException ex)
{
PentaLogger.LogVerbose("Invalid Property: '" + codeToExecute + "' Invalid Fragment: '" + ex.Message + "'");
}
return result;
}
private static String[] SplitCodeArray(String codeToExecute)
{
List<String> items = new List<String>();
Int32 parenAndbracketCount = 0;
String buffer = "";
foreach (Char c in codeToExecute.ToCharArray())
{
if (c == '.')
{
if (buffer.Length > 0)
{
items.Add(buffer);
buffer = "";
}
continue;
}
else if (c == '[')
{
parenAndbracketCount++;
if (buffer.Length > 0)
{
items.Add(buffer);
}
buffer = c.ToString();
}
else if (c == ']' || c == ')')
{
parenAndbracketCount--;
buffer += c;
if (buffer.Length > 0)
{
items.Add(buffer);
buffer = "";
}
}
else if (Char.IsWhiteSpace(c) || Char.IsControl(c))
{
if (parenAndbracketCount == 0)
{
// Skip it
continue;
}
else
{
buffer += c;
}
}
else if (c == '(')
{
parenAndbracketCount++;
buffer += c;
}
else
{
buffer += c;
}
}
if (buffer.Length > 0)
{
items.Add(buffer);
}
return items.ToArray();
}
private static object[] GetParameters(String codeFragment, MemberInfo memberInfo)
{
String parameters = SplitParametersFromMethod(codeFragment);
if (String.IsNullOrEmpty(parameters))
return new object[0];
object[] parameterArray = parameters.Split(',');
return GetParameters(memberInfo, parameterArray);
}
private static object[] GetParameters(MemberInfo memberInfo, object[] parameterArray)
{
ParameterInfo[] parameterInfo = null;
TypeConverter typeConverter = null;
PropertyInfo propertyInfo = memberInfo as PropertyInfo;
if (propertyInfo != null)
{
parameterInfo = propertyInfo.GetIndexParameters();
typeConverter = GetTypeConverter(parameterInfo[0]);
}
else
{
MethodInfo methodInfo = memberInfo as MethodInfo;
if (methodInfo != null)
{
parameterInfo = methodInfo.GetParameters();
}
}
if (parameterInfo == null)
{
return null;
}
object[] returnParameters = new object[parameterInfo.Length];
for (Int32 i = 0; i < parameterArray.Length; i++)
{
ConversionResult converstionResult = ConvertValue(parameterArray[i], parameterInfo[i].ParameterType, typeConverter);
if (converstionResult.Success)
{
returnParameters[i] = converstionResult.ConvertedValue;
}
else
{
return null;
}
}
return returnParameters;
}
private static TypeConverter GetTypeConverter(MemberInfo memberInfo, Type targetType)
{
object[] typeConverters = memberInfo.GetCustomAttributes(typeof(TypeConverterAttribute), true);
if (typeConverters.Length > 0)
{
TypeConverterAttribute typeConverterAttribute = (TypeConverterAttribute)typeConverters[0];
Type typeFromName = Type.GetType(typeConverterAttribute.ConverterTypeName);
if ((typeFromName != null) && typeof(TypeConverter).IsAssignableFrom(typeFromName))
{
return (TypeConverter)Activator.CreateInstance(typeFromName);
}
}
return TypeDescriptor.GetConverter(targetType);
}
private static TypeConverter GetTypeConverter(PropertyInfo propertyInfo)
{
return GetTypeConverter(propertyInfo, propertyInfo.PropertyType);
}
private static TypeConverter GetTypeConverter(FieldInfo fieldInfo)
{
return GetTypeConverter(fieldInfo, fieldInfo.FieldType);
}
private static TypeConverter GetTypeConverter(ParameterInfo parameterInfo)
{
return GetTypeConverter(parameterInfo.Member, parameterInfo.ParameterType);
}
private static ArrayDefinition GetArrayDefinition(object value, String codeToExecute)
{
// All IList classes have an Item property except for System.Array.
List<MemberInfo> retrieveMemberInfos = new List<MemberInfo>();
foreach (PropertyInfo propertyInfo in value.GetType().GetProperties(DefaultBindings))
{
if (propertyInfo.Name == "Item")
{
retrieveMemberInfos.Add(propertyInfo);
}
}
if (retrieveMemberInfos.Count == 0)
{
// We didn't find any Item properties so this is probably an Array. Use the GetValue method
foreach (MethodInfo methodInfo in value.GetType().GetMethods(DefaultBindings))
{
if (methodInfo.Name == "GetValue")
{
retrieveMemberInfos.Add(methodInfo);
}
}
}
// Some members have overloaded this[] methods. Find the correct method.
foreach (MemberInfo memberInfo in retrieveMemberInfos)
{
object[] parameters = GetParameters(codeToExecute, memberInfo);
if (parameters != null)
{
ArrayDefinition arrayDefinition = new ArrayDefinition();
arrayDefinition.Parameters = parameters;
arrayDefinition.RetrieveMemberInfo = memberInfo;
return arrayDefinition;
}
}
return null;
}
private static void ProcessArray(ReflectorResult result, String codeFragment, Boolean createIfNotExists)
{
Int32 failCount = 0;
ArrayDefinition arrayDefinition = GetArrayDefinition(result.Value, codeFragment);
if (arrayDefinition != null)
{
// If this is anything but System.Array we need to call a Property
PropertyInfo propertyInfo = arrayDefinition.RetrieveMemberInfo as PropertyInfo;
if (propertyInfo != null)
{
SetPropertyInfoValue:
try
{
object value = propertyInfo.GetValue(result.Value, arrayDefinition.Parameters);
result.SetResult(value, propertyInfo, arrayDefinition.Parameters);
}
catch (TargetInvocationException ex)
{
failCount++;
if (ex.InnerException is ArgumentOutOfRangeException && failCount == 1 && createIfNotExists)
{
if (CreateArrayItem(result, arrayDefinition))
{
goto SetPropertyInfoValue;
}
}
// Tried to fix it but failed. Blow up
result.Clear();
throw new InvalidCodeFragmentException(codeFragment);
}
}
else
{
// System.Array has a Method to call
MethodInfo methodInfo = arrayDefinition.RetrieveMemberInfo as MethodInfo;
if (methodInfo != null)
{
try
{
// We can't support dynamically creating array items
object value = methodInfo.Invoke(result.Value, arrayDefinition.Parameters);
result.SetResult(value, methodInfo, arrayDefinition.Parameters);
}
catch (TargetInvocationException)
{
result.Clear();
throw new InvalidCodeFragmentException(codeFragment);
}
}
}
}
else
{
result.Clear();
throw new InvalidCodeFragmentException(codeFragment);
}
}
private static Boolean CreateArrayItem(ReflectorResult result, ArrayDefinition arrayDefinition)
{
Type resultType = result.Value.GetType();
Type containedType = null;
if (resultType.IsArray)
{
containedType = resultType.GetElementType();
}
else
{
containedType = resultType.GetGenericArguments()[0];
}
object newInstance = Activator.CreateInstance(containedType);
if (!resultType.IsArray)
{
MethodInfo[] methods = GetMethods(result.Value, "Insert");
foreach (MethodInfo methodInfo in methods)
{
object[] temp = new object[arrayDefinition.Parameters.Length + 1];
arrayDefinition.Parameters.CopyTo(temp, 0);
temp[arrayDefinition.Parameters.Length] = newInstance;
object[] parameters = GetParameters(methodInfo, temp);
if (parameters != null)
{
methodInfo.Invoke(result.Value, parameters);
return true;
}
}
}
return false;
}
private static void ProcessProperty(ReflectorResult result, String codeFragment, bool retrieveValue)
{
// This is just a regular property
PropertyInfo propertyInfo = result.Value.GetType().GetProperty(codeFragment, DefaultBindings);
if (propertyInfo != null)
{
object value = result.Value;
if (retrieveValue)
{
value = propertyInfo.GetValue(result.Value, null);
result.SetResult(value, propertyInfo, null);
}
result.SetResult(value, propertyInfo, null);
}
else
{
// Maybe it is a field
FieldInfo fieldInfo = result.Value.GetType().GetField(codeFragment, DefaultBindings);
if (fieldInfo != null)
{
object value = result.Value;
if (retrieveValue)
{
value = fieldInfo.GetValue(result.Value);
}
result.SetResult(value, fieldInfo, null);
}
else
{
// This item is missing, log it and set the value to null
result.Clear();
throw new InvalidCodeFragmentException(codeFragment);
}
}
}
private static void ProcessMethod(ReflectorResult result, String codeFragment)
{
// This is just a regular property
String methodName = codeFragment.Substring(0, codeFragment.IndexOf('('));
MethodInfo[] methodInfos = GetMethods(result.Value, methodName);
foreach (MethodInfo methodInfo in methodInfos)
{
object[] parameters = GetParameters(codeFragment, methodInfo);
if (parameters != null)
{
object value = methodInfo.Invoke(result.Value, parameters);
result.SetResult(value, null, null);
break;
}
}
}
private static String SplitParametersFromMethod(String codeFragment)
{
char startCharacter = '[';
char endCharacter = ']';
if (codeFragment.EndsWith(")", StringComparison.CurrentCulture))
{
// This is a function
startCharacter = '(';
endCharacter = ')';
}
Int32 startParam = codeFragment.IndexOf(startCharacter) + 1;
if (startParam < 1)
return null;
Int32 endParam = codeFragment.IndexOf(endCharacter);
if (endParam < 0)
return null;
return codeFragment.Substring(startParam, endParam - startParam).Trim();
}
private static MethodInfo[] GetMethods(object value, String methodName)
{
if (String.IsNullOrEmpty(methodName))
{
throw new ArgumentNullException("methodName");
}
if (value == null)
{
return new MethodInfo[0];
}
List<MethodInfo> methodInfos = new List<MethodInfo>();
foreach (MethodInfo methodInfo in value.GetType().GetMethods(DefaultBindings))
{
if (methodInfo.Name == methodName)
{
methodInfos.Add(methodInfo);
}
}
return methodInfos.ToArray();
}
#endregion
#region Helper Classes
private class ArrayDefinition
{
public MemberInfo RetrieveMemberInfo { get; set; }
public object[] Parameters { get; set; }
}
private class ReflectorResult
{
public ReflectorResult(object startValue)
{
SetResult(startValue, null, null);
}
public MemberInfo MemberInfo { get; private set; }
public object[] MemberInfoParameters { get; private set; }
public object PreviousValue { get; set; }
public object Value { get; private set; }
public void SetResult(object value, MemberInfo memberInfo, object[] memberInfoParameters)
{
Value = value;
MemberInfo = memberInfo;
MemberInfoParameters = memberInfoParameters;
}
public void Clear()
{
MemberInfo = null;
Value = null;
PreviousValue = null;
}
}
[Serializable]
[SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic")]
[SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors")]
private class InvalidCodeFragmentException : Exception
{
public InvalidCodeFragmentException(String invalidFragment)
: base(invalidFragment)
{
}
}
#endregion
}
}

Related

generic type conversion in C#

Is there any way the following code can be improved. I'm aware of the non nullable reference in C# 8.0 and was thinking either that or any other way they code below could be made more robust and better in general.
This method is called for converting xml data before insertion in a database Through entity framework core. Any way these tools can be used to improve this code is welcomed.
public object Convert(string value, Type toType)
{
try
{
if (toType == typeof(short))
{
return short.Parse(value);
}
if (toType == typeof(short?))
{
if (string.IsNullOrEmpty(value))
{
return null;
}
return short.Parse(value);
}
if (toType == typeof(int))
{
return int.Parse(value);
}
if (toType == typeof(int?))
{
if (string.IsNullOrEmpty(value))
{
return null;
}
return int.Parse(value);
}
if (toType == typeof(decimal))
{
return decimal.Parse(value);
}
if (toType == typeof(decimal?))
{
if (string.IsNullOrEmpty(value))
{
return null;
}
return decimal.Parse(value);
}
if (toType == typeof(DateTime))
{
return DateTime.Parse(value);
}
if (toType == typeof(DateTime?))
{
if (string.IsNullOrEmpty(value))
{
return null;
}
return DateTime.Parse(value);
}
throw new NotSupportedException($"No conversion defined for type:'{toType}'");
}
catch (System.FormatException excp)
{
throw new ConversionException($"Value:'{value}' could not be converted to:'{toType.Name}'", excp);
}
}
Many thanks in advance
The only thing I can offer is to improve robustness by using .TryParse() for the parsing instead of .Parse()
class Program
{
static void Main(string[] args)
{
var i = Parse<int>("100");
var x = Parse<double>("3.1417");
var s = Parse<string>("John");
var d = Parse<Decimal>("1234.56");
var f = Parse<DateTime>("4/1/2044");
var q = Parse<byte>("4A");
Decimal? p = Parse<decimal>("Not Decimal");
}
public static dynamic Parse<T>(string text)
{
var toType = typeof(T);
if (toType == typeof(int))
{
if (int.TryParse(text, out int x))
{
return x;
}
}
else if (toType == typeof(short))
{
if (short.TryParse(text, out short x))
{
return x;
}
}
else if (toType == typeof(double))
{
if (double.TryParse(text, out double x))
{
return x;
}
}
else if (toType == typeof(decimal))
{
if (decimal.TryParse(text, out decimal x))
{
return x;
}
}
else if (toType == typeof(DateTime))
{
if (DateTime.TryParse(text, out DateTime x))
{
return x;
}
}
else if (toType == typeof(byte))
{
if (byte.TryParse(text, System.Globalization.NumberStyles.HexNumber, null, out byte x))
{
return x;
}
}
else if (toType == typeof(string))
{
return text;
}
return null;
}
}
IMO the main improvement here would be to remove all boxing. Which means using generics throughout. Now, there's a complication there: with generics, it is hard to change types without with boxing (to convince the compiler you know what you're doing), or using meta-programming. So: I'd lean towards the latter. Something like:
public static T Convert<T>(string input)
=> TypeCache<T>.Convert(input);
private static TypeCache<T> {
public static readonly Func<string, T> Convert
= CreateConverter<T>();
}
private static Func<string, T> CreateConverter<T>()
{...}
The magic all happens in that last method. The problem is: it isn't trivial. The simplest approach should be to use reflection to discover a suitable parse method, then manually construct an Expression<Func<string, T>> by linking appropriate Expression nodes to repreresent the operation, then call Compile() to get a delegate. Another approach is to go straight to DynamicMethod and ILGenerator. Both are advanced topics.
This is the code I wrote and used in my company's project really well.
You can try to use this:
/// <summary>
/// Method : Simply Universal Type Converter
/// Requirement : C# 7.0+
/// Created by : Byungho Park(Tapegawui) in South Korea.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="val">Original value</param>
/// <param name="rfrom">(Optional)Character(s) want to replace from</param>
/// <param name="rto">(Optional)Character(s) will be replace to</param>
/// <returns></returns>
public static T Cast<T>(dynamic val, string rfrom = "", string rto = "") where T : IConvertible
{
try
{
// Convert null to empty else 0
if (val is null || val.Equals(DBNull.Value))
{
if (typeof(T) == typeof(string) || typeof(T) == typeof(DateTime))
{
val = string.Empty;
}
else if (typeof(T) == typeof(bool))
{
val = false;
}
else
{
val = 0;
}
}
else
{
// Replace string given parameter from a to b
if (typeof(T) == typeof(string) && rfrom == "" & rto.Length > 0)
{
if (val.ToString().Length == 0)
{
val = rto;
}
}
else if (typeof(T) == typeof(string) && rto.Length > 0)
{
val = (string)val.ToString().Replace(rfrom, rto);
}
}
// Convert type on this block finally
return (T)Convert.ChangeType(val, typeof(T));
}
catch (Exception)
{
return default(T);
}
}
And usage examples:
using System;
int vint = 10;
int vint2 = vint;
string vstr = "1000000";
string vdcm = "123456789123456789";
for (int i = 1; i <= vint; i++)
{
vint2 += i;
}
Console.WriteLine($"Adding int with loop : {vint2} from {vint}\n");
string tint = Cast<string>(vint);
for (int i = 1; i <= vint; i++)
{
tint += i;
}
Console.WriteLine($"Adding string with loop : {tint} from {vint}\n");
long tlong = Cast<long>(vstr);
tlong *= tlong;
Console.WriteLine($"Multiply long : {tlong} from {vstr}\n");
double tdbl = Cast<double>(vdcm);
for (int i = 1; i <= vint; i++)
{
tdbl *= i;
}
Console.WriteLine($"Multiply double with loop : {tdbl} from {vdcm}\n");
decimal tdcm = Cast<decimal>(vdcm);
for (int i = 1; i <= vint; i++)
{
tdcm *= i;
}
Console.WriteLine($"Multiply decimal with loop : {tdcm} from {vdcm}\n");
string ns = null;
Console.WriteLine($"Null string : {Cast<string>(ns)}\n");
int? ni = null;
Console.WriteLine($"Null int : {Cast<int>(ni)}\n");
long? nl = null;
Console.WriteLine($"Null long : {Cast<long>(nl)}\n");
double? ndbl = null;
Console.WriteLine($"Null double : {Cast<double>(ndbl)}\n");
decimal? nd = null;
Console.WriteLine($"Null decimal : {Cast<decimal>(nd)}\n");
string tb = "true";
Console.WriteLine($"Convert string to boolean : {Cast<bool>(tb)}\n");
bool? nb = null;
Console.WriteLine($"Null boolean : {Cast<bool>(nb)}\n");
// -----------------------
// From Microsoft examples
double d = -2.345;
int t = Cast<int>(d);
Console.WriteLine($"The double value {d} when converted to an int becomes {t}\n");
string s = "98/12/12";
DateTime dt = Cast<DateTime>(s);
Console.WriteLine($"The string value {s} when converted to a Date becomes {dt}\n");
// -----------------------
// ------------------------------------------
// Replace some character(s) with string type
string rs = "Replace this string with x to y.";
Console.WriteLine($"{Cast<string>(rs, " ", "_")}\n");
Console.WriteLine($"{Cast<string>("abcd", "", "abc")}\n");
Console.WriteLine($"{Cast<string>("", "", "abc")}\n");
string rs3 = "Replace this string from x to y.";
string ts = "string";
Console.WriteLine($"{Cast<string>(rs3, ts, ts.ToUpper())}\n");
Console.WriteLine($"Replace int character with string : {Cast<string>(vstr, "0", "1")}\n");
// ------------------------------------------
Console.WriteLine("Press any key to close...");
Console.ReadKey();
In addition, output results:
Adding int with loop : 65 from 10
Adding string with loop : 1012345678910 from 10
Multiply long : 1000000000000 from 1000000
Multiply double with loop : 4.479999963712E+23 from 123456789123456789
Multiply decimal with loop : 447999996371199995923200 from 123456789123456789
Null string :
Null int : 0
Null long : 0
Null double : 0
Null decimal : 0
Convert string to boolean : True
Null boolean : False
The double value -2.345 when converted to an int becomes -2
The string value 98/12/12 when converted to a Date becomes 1998-12-12 오전 12:00:00
Replace_this_string_with_x_to_y.
abcd
abc
Replace this STRING from x to y.
Replace int character with string : 1111111
Press any key to close...
public static class StringExtensions
{
public static TDest ConvertStringTo<TDest>(this string src)
{
if (src == null)
{
return default(TDest);
}
try
{
return ChangeType<TDest>(src);
}
catch
{
return default(TDest);
}
}
private static T ChangeType<T>(string value)
{
var t = typeof(T);
if (t.GetTypeInfo().IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
t = Nullable.GetUnderlyingType(t);
}
return (T)Convert.ChangeType(value, t);
}
}
Using runtime infrastructure to determine types and perform conversions between them as proposed by the other answers is very convenient and probably what you are looking for.
If, however, you need more control over your conversion (or rather parsing), e.g. because you get weird input formats that require pre-processing, may I suggest the following class.
It lets you provide a parser method for each type you register. The types from your question with their nullable cousins come pre-registered in the constructor, but you can also add any other method to the dictionary as your input data requires:
public delegate bool Parser<T>(string input, out T output);
public class Parsers
{
private delegate bool UntypedParser(string input, out object output);
private Dictionary<Type, UntypedParser> _parsersByType = new Dictionary<Type, UntypedParser>();
/// <summary>
/// Creates a <see cref="Parsers"/> instance pre-populated with parsers for the most common types.
/// </summary>
public static Parsers CreateDefault()
{
Parsers result = new Parsers();
result.AddParser<string>((string input, out string output) => { output = input; return true; });
result.AddParserForNullable<bool>(bool.TryParse);
result.AddParserForNullable<byte>(byte.TryParse);
result.AddParserForNullable<DateTime>(DateTime.TryParse);
result.AddParserForNullable<decimal>(decimal.TryParse);
result.AddParserForNullable<double>(double.TryParse);
result.AddParserForNullable<float>(float.TryParse);
result.AddParserForNullable<int>(int.TryParse);
result.AddParserForNullable<short>(short.TryParse);
return result;
}
/// <summary>
/// Registers a parser for the given type T
/// </summary>
public void AddParser<T>(Parser<T> parser)
{
_parsersByType[typeof(T)] = (string input, out object output) => ParseObject(parser, input, out output);
}
/// <summary>
/// Registers a parser for the given type T as well as <see cref="Nullable{T}"/>
/// </summary>
public void AddParserForNullable<T>(Parser<T> parser)
where T : struct
{
_parsersByType[typeof(T)] = (string input, out object output) => ParseObject(parser, input, out output);
_parsersByType[typeof(T?)] = (string input, out object output) => ParseNullable(parser, input, out output);
}
/// <summary>
/// For nullable types, return null when the input is an empty string or null.
/// </summary>
/// <remarks>This is not called for the string-parser. Meaning an empty string is not parsed into a null value.</remarks>
private bool ParseNullable<T>(Parser<T> parser, string input, out object output)
where T : struct
{
bool result;
if (string.IsNullOrEmpty(input))
{
result = true;
output = null;
}
else
{
result = ParseObject(parser, input, out output);
}
return result;
}
/// <summary>
/// Helper method to convert the typed output into an object (possibly boxing the value)
/// </summary>
private bool ParseObject<T>(Parser<T> parser, string input, out object output)
{
bool result = parser(input, out T typedOutput);
output = typedOutput;
return result;
}
/// <summary>
/// Finds the parser for the given target type and uses it to parse the input.
/// </summary>
public object Parse<T>(string input)
{
Type targetType = typeof(T);
object result;
if (!_parsersByType.TryGetValue(targetType, out UntypedParser parser))
{
throw new ArgumentException($"No parser defined for type '{targetType}'.");
}
else if (!parser(input, out result))
{
throw new ArgumentException($"Cannot parse '{targetType}' from input '{input}'.");
}
return result;
}
}

Getting a property value using IL for a struct

I'm trying to retrieve a value from the following struct.
public struct MyType
{
public string Key { get; set; }
public string Value { get; set; }
}
using the following code.
var mem = new MemberAccessor(typeof(MyType), "Value");
var r = new MyType {Key = "One", Value = "Two"};
object s = mem.Get(r);
Console.WriteLine(s); //Should be Two but is One
And it works, but it gives me the value of "Key" not "Value". If I try to get the value of "Key" then it gives me a "Cannot read from memory" error.
The following is the creation of the Get delegate used in the above code. This only appears to be a problem with a struct.
public class MemberAccessor
{
private readonly Type _targetType;
private readonly Type _memberType;
private readonly MemberInfo _member;
private static readonly Hashtable _mTypeHash = new Hashtable
{
[typeof(sbyte)] = OpCodes.Ldind_I1,
[typeof(byte)] = OpCodes.Ldind_U1,
[typeof(char)] = OpCodes.Ldind_U2,
[typeof(short)] = OpCodes.Ldind_I2,
[typeof(ushort)] = OpCodes.Ldind_U2,
[typeof(int)] = OpCodes.Ldind_I4,
[typeof(uint)] = OpCodes.Ldind_U4,
[typeof(long)] = OpCodes.Ldind_I8,
[typeof(ulong)] = OpCodes.Ldind_I8,
[typeof(bool)] = OpCodes.Ldind_I1,
[typeof(double)] = OpCodes.Ldind_R8,
[typeof(float)] = OpCodes.Ldind_R4
};
public static Type GetMemberInfoType(MemberInfo member)
{
Type type;
if (member is FieldInfo)
type = ((FieldInfo)member).FieldType;
else if (member is PropertyInfo)
type = ((PropertyInfo)member).PropertyType;
else if (member == null)
type = typeof(object);
else
throw new NotSupportedException();
return type;
}
/// <summary>
/// Creates a new property accessor.
/// </summary>
/// <param name="targetType">Target object type.</param>
/// <param name="memberName">Property name.</param>
public MemberAccessor(Type targetType, string memberName)
{
_targetType = targetType;
MemberInfo memberInfo = (targetType).GetProperties().First(x => x.Name == memberName);
if (memberInfo == null)
{
throw new Exception(string.Format("Property \"{0}\" does not exist for type " + "{1}.", memberName, targetType));
}
var canRead = IsField(memberInfo) || ((PropertyInfo)memberInfo).CanRead;
var canWrite = IsField(memberInfo) || ((PropertyInfo)memberInfo).CanWrite;
// roslyn automatically implemented properties, in particular for get-only properties: <{Name}>k__BackingField;
if (!canWrite)
{
var backingFieldName = $"<{memberName}>k__BackingField";
var backingFieldMemberInfo = targetType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(x => x.Name == backingFieldName);
if (backingFieldMemberInfo != null)
{
memberInfo = backingFieldMemberInfo;
canWrite = true;
}
}
_memberType = GetMemberInfoType(memberInfo);
_member = memberInfo;
if (canWrite)
{
SetDelegate = GetSetDelegate();
}
if (canRead)
{
GetDelegate = GetGetDelegate();
}
}
private Func<object, object> GetDelegate = null;
private Action<object, object> SetDelegate = null;
/// <summary>
/// Sets the property for the specified target.
/// </summary>
/// <param name="target">Target object.</param>
/// <param name="value">Value to set.</param>
public void Set(object target, object value)
{
SetDelegate?.Invoke(target, value);
}
public object Get(object target)
{
return GetDelegate?.Invoke(target);
}
private Action<object, object> GetSetDelegate()
{
Type[] setParamTypes = new Type[] { typeof(object), typeof(object) };
Type setReturnType = null;
var owner = _targetType.GetTypeInfo().IsAbstract || _targetType.GetTypeInfo().IsInterface ? null : _targetType;
var setMethod = owner != null
? new DynamicMethod(Guid.NewGuid().ToString(), setReturnType, setParamTypes, owner, true)
: new DynamicMethod(Guid.NewGuid().ToString(), setReturnType, setParamTypes, true);
// From the method, get an ILGenerator. This is used to
// emit the IL that we want.
//
ILGenerator setIL = setMethod.GetILGenerator();
//
// Emit the IL.
//
Type paramType = _memberType;
setIL.Emit(OpCodes.Ldarg_0); //Load the first argument
//(target object)
//Cast to the source type
setIL.Emit(OpCodes.Castclass, this._targetType);
setIL.Emit(OpCodes.Ldarg_1); //Load the second argument
//(value object)
if (paramType.GetTypeInfo().IsValueType)
{
setIL.Emit(OpCodes.Unbox, paramType); //Unbox it
if (_mTypeHash[paramType] != null) //and load
{
OpCode load = (OpCode)_mTypeHash[paramType];
setIL.Emit(load);
}
else
{
setIL.Emit(OpCodes.Ldobj, paramType);
}
}
else
{
setIL.Emit(OpCodes.Castclass, paramType); //Cast class
}
if (IsField(_member))
{
setIL.Emit(OpCodes.Stfld, (FieldInfo)_member);
}
else
{
MethodInfo targetSetMethod = GetSetMethodOnDeclaringType(((PropertyInfo)this._member));
if (targetSetMethod != null)
{
setIL.Emit(OpCodes.Callvirt, targetSetMethod);
}
else
{
setIL.ThrowException(typeof(MissingMethodException));
}
}
setIL.Emit(OpCodes.Ret);
var del = setMethod.CreateDelegate(Expression.GetActionType(setParamTypes));
return del as Action<object, object>;
}
public static bool IsField(MemberInfo member)
{
return member is FieldInfo;
}
public static MethodInfo GetSetMethodOnDeclaringType(PropertyInfo propertyInfo)
{
var methodInfo = propertyInfo.GetSetMethod(true);
return methodInfo ?? propertyInfo
.DeclaringType
.GetProperty(propertyInfo.Name)
.GetSetMethod(true);
}
private Func<object, object> GetGetDelegate()
{
Type[] setParamTypes = new[] { typeof(object) };
Type setReturnType = typeof(object);
Type owner = _targetType.GetTypeInfo().IsAbstract || _targetType.GetTypeInfo().IsInterface ? null : _targetType;
var getMethod = owner != null
? new DynamicMethod(Guid.NewGuid().ToString(), setReturnType, setParamTypes, owner, true)
: new DynamicMethod(Guid.NewGuid().ToString(), setReturnType, setParamTypes, true);
// From the method, get an ILGenerator. This is used to
// emit the IL that we want.
ILGenerator getIL = getMethod.GetILGenerator();
getIL.DeclareLocal(typeof(object));
getIL.Emit(OpCodes.Ldarg_0); //Load the first argument
//(target object)
//Cast to the source type
getIL.Emit(OpCodes.Castclass, this._targetType);
//Get the property value
if (IsField(_member))
{
getIL.Emit(OpCodes.Ldfld, (FieldInfo)_member);
if (_memberType.GetTypeInfo().IsValueType)
{
getIL.Emit(OpCodes.Box, _memberType);
}
}
else
{
var targetGetMethod = ((PropertyInfo)_member).GetGetMethod();
getIL.Emit(OpCodes.Callvirt, targetGetMethod);
if (targetGetMethod.ReturnType.GetTypeInfo().IsValueType)
{
getIL.Emit(OpCodes.Box, targetGetMethod.ReturnType);
}
}
getIL.Emit(OpCodes.Ret);
var del = getMethod.CreateDelegate(Expression.GetFuncType(setParamTypes.Concat(new[] { setReturnType }).ToArray()));
return del as Func<object, object>;
}
}
Problem in the IL code that you generate for GetDelegate. You should unbox struct from object type parameter before calling GetMethod of it's property, like this:
if (_targetType.IsValueType)
getIL.Emit(OpCodes.Unbox, _targetType);
else
getIL.Emit(OpCodes.Castclass, this._targetType);
Small side notes about GetGetDelegate method:
For struct types you can use Call instead Callvirt because it's faster and struct can't have virtual properties.
I was't able to understand why do you need declare local variable with this instruction getIL.DeclareLocal(typeof(object)) and I believe you can safely remove this line.
Just in case here version of GetGetDelegate method that worked for me:
private Func<object, object> GetGetDelegate()
{
Type setParamType = typeof(object);
Type[] setParamTypes = { setParamType };
Type setReturnType = typeof(object);
Type owner = _targetType.GetTypeInfo().IsAbstract || _targetType.GetTypeInfo().IsInterface ? null : _targetType;
var getMethod = owner != null
? new DynamicMethod(Guid.NewGuid().ToString(), setReturnType, setParamTypes, owner, true)
: new DynamicMethod(Guid.NewGuid().ToString(), setReturnType, setParamTypes, true);
// From the method, get an ILGenerator. This is used to
// emit the IL that we want.
ILGenerator getIL = getMethod.GetILGenerator();
getIL.Emit(OpCodes.Ldarg_0); //Load the first argument (target object)
if (_targetType.IsValueType)
getIL.Emit(OpCodes.Unbox, _targetType); //unbox struct
else
getIL.Emit(OpCodes.Castclass, this._targetType); //Cast to the source type
Type returnType = null;
if (IsField(_member))
{
getIL.Emit(OpCodes.Ldfld, (FieldInfo)_member);
returnType = _memberType;
}
else
{
var targetGetMethod = ((PropertyInfo)_member).GetGetMethod();
var opCode = _targetType.IsValueType ? OpCodes.Call : OpCodes.Callvirt;
getIL.Emit(opCode, targetGetMethod);
returnType = targetGetMethod.ReturnType;
}
if (returnType.IsValueType)
{
getIL.Emit(OpCodes.Box, returnType);
}
getIL.Emit(OpCodes.Ret);
var del = getMethod.CreateDelegate(Expression.GetFuncType(setParamType, setReturnType));
return del as Func<object, object>;
}

PropertyGrid how do you add an editable List<Class>?

I have a class MyClassA that has an IList property. I am using a PropertyGrid control to display all the properties of MyClassA and I would like the list of MyClassB to be displayed and editable via the PropertyGrid for MyClassA.
I currently have all the other properties being displayed in the Property grid except for the property that is the list of MyClassB. How do I go about adding the List of MyClassB to the property grid where the user can add/edit/remove items from the List?
I haven't really been able to find any examples that go into detail on this as of yet although I am still digging.
Here is a solution I have worked out so far, although it still doesn't fit in 100% to what I am looking for.
I found this reference to modify for my liking: http://www.codeproject.com/KB/tabs/customizingcollectiondata.aspx
What I did was create a new class that inherits from CollectionBase and that uses an ICustomTypeDescriptor.
After I did this and implemented the basic functionality, I had to create a PropertyDescriptor for the class.
Here is the code:
public class ZoneCollection : CollectionBase, ICustomTypeDescriptor
{
#region Collection Implementation
/// <summary>
/// Adds an zone object to the collection
/// </summary>
/// <param name="emp"></param>
public void Add(Zone zone)
{
this.List.Add(zone);
}
/// <summary>
/// Removes an zone object from the collection
/// </summary>
/// <param name="emp"></param>
public void Remove(Zone zone)
{
this.List.Remove(zone);
}
/// <summary>
/// Returns an zone object at index position.
/// </summary>
public Zone this[int index]
{
get
{
return (Zone)this.List[index];
}
}
#endregion
// Implementation of interface ICustomTypeDescriptor
#region ICustomTypeDescriptor impl
public String GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
public String GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this, true);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
/// <summary>
/// Called to get the properties of this type. Returns properties with certain
/// attributes. this restriction is not implemented here.
/// </summary>
/// <param name="attributes"></param>
/// <returns></returns>
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return GetProperties();
}
/// <summary>
/// Called to get the properties of this type.
/// </summary>
/// <returns></returns>
public PropertyDescriptorCollection GetProperties()
{
// Create a collection object to hold property descriptors
PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null);
// Iterate the list of employees
for (int i = 0; i < this.List.Count; i++)
{
// Create a property descriptor for the zone item and add to the property descriptor collection
ZoneCollectionPropertyDescriptor pd = new ZoneCollectionPropertyDescriptor(this, i);
pds.Add(pd);
}
// return the property descriptor collection
return pds;
}
#endregion
}
/// <summary>
/// Summary description for CollectionPropertyDescriptor.
/// </summary>
public class ZoneCollectionPropertyDescriptor : PropertyDescriptor
{
private ZoneCollection collection = null;
private int index = -1;
public ZoneCollectionPropertyDescriptor(ZoneCollection coll, int idx) :
base("#" + idx.ToString(), null)
{
this.collection = coll;
this.index = idx;
}
public override AttributeCollection Attributes
{
get
{
return new AttributeCollection(null);
}
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get
{
return this.collection.GetType();
}
}
public override string DisplayName
{
get
{
Zone zone = this.collection[index];
return zone.ID.ToString();
}
}
public override string Description
{
get
{
Zone zone = this.collection[index];
StringBuilder sb = new StringBuilder();
sb.Append(zone.ID.ToString());
if ( zone.Streets.Route != String.Empty || zone.Streets.Crossing != String.Empty)
sb.Append("::");
if (zone.Streets.Route != String.Empty)
sb.Append(zone.Streets.Route);
if ( zone.Streets.Crossing != String.Empty)
{
sb.Append(" and ");
sb.Append(zone.Streets.Crossing);
}
return sb.ToString();
}
}
public override object GetValue(object component)
{
return this.collection[index];
}
public override bool IsReadOnly
{
get { return false; }
}
public override string Name
{
get { return "#" + index.ToString(); }
}
public override Type PropertyType
{
get { return this.collection[index].GetType(); }
}
public override void ResetValue(object component)
{
}
public override bool ShouldSerializeValue(object component)
{
return true;
}
public override void SetValue(object component, object value)
{
// this.collection[index] = value;
}
}
Intersection now contains a ZoneCollection instead of an IList and I can now edit/add/remove the zones contained within the collection.
Now, if I could make this more generic I'd be relatively happy. Another hindrance for my model is that I had to inherit from Collection base using this, instead of IList. This completely broke my mapping of my class for NHibernate and I'm now having to try and figure out how to remap this list using the method mentioned above.
If anyone wants to elaborate this any further I'd greatly appreciate some more insight.
I know this Topic is more than 2 years old, but maybe this could be interesting for you.
I had a similar Problem.
Starting with: I need a Point in 3D-Space which should be configurable in Property-Grid
For this I created a Class Koord. To make it changeable in PropertyGrid, I created a new Class "KoordConverter : TypeConverter"
This is used in a Vexel (check Wikipedia to find out what it's for :-) )
To create an TestBock (some 3D-Object) I'm using a List of Vexels.
Unfortunately I need a List of TestBlocks in my Program, Visible through the Property-Grid.
To start Topmost:
public partial class FormMain : Form
{
private BlockProperties _bp = new BlockProperties();
public FormMain()
{
InitializeComponent();
pgProperties.SelectedObject = _bp;
}
[...]
}
The Class BlockProperties includes the List of TestBocks which I filled a bit to show you what's inside.
class BlockProperties
{
public List<TestBocks> Testing { get; set; }
public BlockProperties()
{
Testing = new List<TestBocks>(3);
List<Vexel> t1 = new List<Vexel>(1);
t1.Add(new Vexel(new Koord(1,0,1), 1));
List<Vexel> t2 = new List<Vexel>(2);
t2.Add(new Vexel(new Koord(2, 0, 1), 2));
t2.Add(new Vexel(new Koord(2, 0, 2), 2));
List<Vexel> t3 = new List<Vexel>(3);
t3.Add(new Vexel(new Koord(3, 0, 1), 3));
t3.Add(new Vexel(new Koord(3, 0, 2), 3));
t3.Add(new Vexel(new Koord(3, 0, 3), 3));
TestBocks tb1 = new TestBocks();
tb1.Koords = t1;
TestBocks tb2 = new TestBocks();
tb2.Koords = t2;
TestBocks tb3 = new TestBocks();
tb3.Koords = t3;
Testing.Add(tb1);
Testing.Add(tb2);
Testing.Add(tb3);
[...]
}
[...]
}
Next is my TestBlock class, which is simply straight forward
[Serializable]
public class TestBocks
{
public List<Vexel> Vexels{ get; set; }
public TestBocks()
{
Vexels = new List<Vexel>();
}
}
In the Vexels is most of the magic I need for my Program:
I even put a ToString() here to make it easy during debugging.
public class Vexel
{
private Koord _origin;
private double _extent;
public Koord Origin { get { return _origin; } set { _origin = value; } }
public double Extent { get { return _extent; } set { _extent = value; } }
public string ToString()
{
NumberFormatInfo nFormatInfo = new NumberFormatInfo
{
NumberDecimalSeparator = ".",
NumberGroupSeparator = ""
};
return String.Format(nFormatInfo, "Origin;{0};{1};{2};Extent;{3}", _origin.X, _origin.Y, _origin.Z, _extent);
}
public Vexel()
{
_origin = new Koord(0,0,0);
Extent = 0;
}
public Vexel(Koord origin, double extent)
{
//TODO do some checking
_origin = origin;
_extent = extent;
}
So far everything worked fine for the PropertyGrid, but I could not edit the Koords.
The Class was pretty simple but not editable in the PropertyGrid.
Adding a TypeConverterClass solved this Problem (you can find the TypeConverter below the code of the Koord)
[TypeConverter(typeof(KoordConverter))]
[Serializable]
public class Koord
{
private double p_1;
private double p_2;
private double p_3;
public Koord(double x, double y, double z)
{
this.p_1 = x;
this.p_2 = y;
this.p_3 = z;
}
public string ToString()
{
return String.Format("X;{0};Y;{1};Z;{2}", p_1, p_2, p_3);
}
public double X { get { return p_1; } }
public double Y { get { return p_2; } }
public double Z { get { return p_3; } }
}
The Typeconverter was the most complicated code to write. You can find it below:
public class KoordConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(InstanceDescriptor) || base.CanConvertTo(context, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
string text = value as string;
if (text == null)
{
return base.ConvertFrom(context, culture, value);
}
string text2 = text.Trim();
if (text2.Length == 0)
{
return null;
}
if (culture == null)
{
culture = CultureInfo.CurrentCulture;
}
char c = culture.TextInfo.ListSeparator[0];
string[] array = text2.Split(new char[]
{
c
});
int[] array2 = new int[array.Length];
TypeConverter converter = TypeDescriptor.GetConverter(typeof(int));
for (int i = 0; i < array2.Length; i++)
{
array2[i] = (int)converter.ConvertFromString(context, culture, array[i]);
}
if (array2.Length == 3)
{
return new Koord(array2[0], array2[1], array2[2]);
}
throw new ArgumentException("TextParseFailedFormat");
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == null)
{
throw new ArgumentNullException("destinationType");
}
if (value is Koord)
{
if (destinationType == typeof(string))
{
Koord Koord = (Koord)value;
if (culture == null)
{
culture = CultureInfo.CurrentCulture;
}
string separator = culture.TextInfo.ListSeparator + " ";
TypeConverter converter = TypeDescriptor.GetConverter(typeof(int));
string[] array = new string[3];
int num = 0;
array[num++] = converter.ConvertToString(context, culture, Koord.X);
array[num++] = converter.ConvertToString(context, culture, Koord.Y);
array[num++] = converter.ConvertToString(context, culture, Koord.Z);
return string.Join(separator, array);
}
if (destinationType == typeof(InstanceDescriptor))
{
Koord Koord2 = (Koord)value;
ConstructorInfo constructor = typeof(Koord).GetConstructor(new Type[]
{
typeof(double),
typeof(double),
typeof(double)
});
if (constructor != null)
{
return new InstanceDescriptor(constructor, new object[]
{
Koord2.X,
Koord2.Y,
Koord2.Z
});
}
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
{
if (propertyValues == null)
{
throw new ArgumentNullException("propertyValues");
}
object obj = propertyValues["X"];
object obj2 = propertyValues["Y"];
object obj3 = propertyValues["Z"];
if (obj == null || obj2 == null || obj3 == null || !(obj is double) || !(obj2 is double) || !(obj3 is double))
{
throw new ArgumentException("PropertyValueInvalidEntry");
}
return new Koord((double)obj, (double)obj2, (double)obj3);
}
public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
{
return true;
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(Koord), attributes);
return properties.Sort(new string[]
{
"X",
"Y",
"Z"
});
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
}
Basically after all of this was set up, It was no ploblem to modify any List of objects (TestBlocks or the Vexels within each TestBlock)
Hope it helps someone if they step over this Thread.
Best Regards
Robin Blood
PS:Editing is no problem in the PropertyGrid, maybe you just didn't get your constructors right !?
http://i.stack.imgur.com/LD3zf.png

NHibernate 3.2 mapping by code ignores my IUserType

I have a LocalizedString classed used to store localization of a single value. The concept is loosely based on a post by Fabio Maulo.
I'm using the new Mapping-By-Code concept in NHibernate 3.2, but it seem to ignore the IUserType implementation, because when it generate the SQL, it create a column with a different name and with the default string NVARCHAR(255) type.
I'm trying to map this simple class
public class Region : Entity
{
/// <summary>
/// Initializes a new instance of the <see cref="Region"/> class.
/// </summary>
public Region()
{
}
/// <summary>
/// Gets or sets the localized name of the <see cref="Region"/>.
/// </summary>
public virtual LocalizedString Name { get; set; }
}
The resulting SQL is
create table Regions (RegionId INT not null, Item NVARCHAR(255) not null, primary key (RegionId))
The Item column here should be called Name and it should be of type XML. I presume the column name come from the name of the indexer of the LocalizedString.
This is my NHibernate configuration (its not complete, I'm in the process of building the convention)
private static Configuration CreateNHibernateConfiguration()
{
var cfg = new Configuration();
cfg.Proxy(p => p.ProxyFactoryFactory<NHibernate.Bytecode.DefaultProxyFactoryFactory>())
.DataBaseIntegration(db =>
{
db.ConnectionStringName = "***";
db.Dialect<MsSql2008Dialect>();
db.BatchSize = 500;
});
var mapper = new ConventionModelMapper();
var baseEntityType = typeof(Entity);
mapper.IsEntity((t, declared) => baseEntityType.IsAssignableFrom(t) && baseEntityType != t && !t.IsInterface);
mapper.IsRootEntity((t, declared) => baseEntityType.Equals(t.BaseType));
mapper.BeforeMapClass += (mi, t, map) =>
{
map.Table(Inflector.MakePlural(t.Name));
map.Id(x =>
{
x.Column(t.Name + "Id");
});
};
mapper.BeforeMapManyToOne += (insp, prop, map) =>
{
map.Column(prop.LocalMember.GetPropertyOrFieldType().Name + "Id");
map.Cascade(Cascade.Persist);
};
mapper.BeforeMapBag += (insp, prop, map) =>
{
map.Key(km => km.Column(prop.GetContainerEntity(insp).Name + "Id"));
map.Cascade(Cascade.All);
};
mapper.BeforeMapProperty += (insp, prop, map) =>
{
map.NotNullable(true);
};
var exportedTypes = baseEntityType.Assembly.GetExportedTypes();
mapper.AddMappings(exportedTypes.Where(t => t.Namespace.EndsWith("Mappings", StringComparison.Ordinal)));
var mapping = mapper.CompileMappingFor(exportedTypes.Where(t => t.Namespace.EndsWith("Data", StringComparison.Ordinal)));
cfg.AddDeserializedMapping(mapping, "MyModel");
SchemaMetadataUpdater.QuoteTableAndColumns(cfg);
return cfg;
}
This is the IUserType definition of my LocalizedString class:
/// <summary>
/// Defines a string that can have a different value in multiple cultures.
/// </summary>
public sealed partial class LocalizedString : IUserType
{
object IUserType.Assemble(object cached, object owner)
{
var value = cached as string;
if (value != null)
{
return LocalizedString.Parse(value);
}
return null;
}
object IUserType.DeepCopy(object value)
{
var toCopy = value as LocalizedString;
if (toCopy == null)
{
return null;
}
var localizedString = new LocalizedString();
foreach (var localizedValue in toCopy.localizedValues)
{
localizedString.localizedValues.Add(localizedValue.Key, localizedValue.Value);
}
return localizedString;
}
object IUserType.Disassemble(object value)
{
var localizedString = value as LocalizedString;
if (localizedString != null)
{
return localizedString.ToXml();
}
return null;
}
bool IUserType.Equals(object x, object y)
{
if (x == null && y == null)
{
return true;
}
if (x == null || y == null)
{
return false;
}
var localizedStringX = (LocalizedString)x;
var localizedStringY = (LocalizedString)y;
if (localizedStringX.localizedValues.Count() != localizedStringY.localizedValues.Count())
{
return false;
}
foreach (var value in localizedStringX.localizedValues)
{
if (!localizedStringY.localizedValues.ContainsKey(value.Key) || localizedStringY.localizedValues[value.Key] == value.Value)
{
return false;
}
}
return true;
}
int IUserType.GetHashCode(object x)
{
if (x == null)
{
throw new ArgumentNullException("x");
}
return x.GetHashCode();
}
bool IUserType.IsMutable
{
get { return true; }
}
object IUserType.NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
{
if (rs == null)
{
throw new ArgumentNullException("rs");
}
if (names == null)
{
throw new ArgumentNullException("names");
}
if (names.Length != 1)
{
throw new InvalidOperationException("names array has more than one element. can't handle this!");
}
var val = rs[names[0]] as string;
if (val != null)
{
return LocalizedString.Parse(val);
}
return null;
}
void IUserType.NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
{
if (cmd == null)
{
throw new ArgumentNullException("cmd");
}
var parameter = (DbParameter)cmd.Parameters[index];
var localizedString = value as LocalizedString;
if (localizedString == null)
{
parameter.Value = DBNull.Value;
}
else
{
parameter.Value = localizedString.ToXml();
}
}
object IUserType.Replace(object original, object target, object owner)
{
throw new NotImplementedException();
}
Type IUserType.ReturnedType
{
get { return typeof(LocalizedString); }
}
NHibernate.SqlTypes.SqlType[] IUserType.SqlTypes
{
get { return new[] { new XmlSqlType() }; }
}
}
You shouldn't use the IUserType in your domain model.
The IUserType interface should actually be called something like IUserTypeMapper, and you have to specify it explicitly in your mapping.
I suggest that you re-read that post.
Update: try this to map your type by convention:
mapper.BeforeMapProperty +=
(insp, prop, map) =>
{
if (/*determine if this is member should be mapped as LocalizedString*/)
map.Type<LocalizedString>();
};
Of course the "determine if..." part would be something you determine, like the property name starting with "Localized", a custom attribute, or anything you want.

C# implementation of deep/recursive object comparison in .net 3.5

I am looking for a C# specific , open source (or source code available) implementation of recursive, or deep, object comparison.
I currently have two graphs of live objects that I am looking to compare to each other, with the result of the comparison being a set of discrepancies in the graphs. The objects are instantiations of a set of classes that are known at run time (but not necessarily at compile time).
There is a specific requirement to be able to map from the discrepancies in the graphs, back to the objects containing the discrepancies.
I found a really nice, free implementation at www.kellermansoftware.com called Compare .NET Objects which can be found here. Highly recommended.
Appears to have relocated to github - most recent version is available here
This is a complex area; I've done some things like this at runtime, and it quickly gets messy. If possible, you might find that the simplest way to do this is to serialize the objects and compare the serialized form (perhaps xml-diff and XmlSerializer). This is complicated a little by the types not being known until runtime, but not hugely (you can always use new XmlSerializer(obj.GetType()) etc).
That would be my default approach, anyway.
Here's an NUnit 2.4.6 custom constraint we use for comparing complex graphs. It supports embedded collections, parent references, setting tolerance for numeric comparisons, identifying field names to ignore (even deep within the hierarchy), and decorating types to be always ignored.
I'm sure this code can be adapted to be used outside NUnit, the bulk of the code isn't dependent on NUnit.
We use this in thousands of unit tests.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using NUnit.Framework;
using NUnit.Framework.Constraints;
namespace Tests
{
public class ContentsEqualConstraint : Constraint
{
private readonly object expected;
private Constraint failedEquality;
private string expectedDescription;
private string actualDescription;
private readonly Stack<string> typePath = new Stack<string>();
private string typePathExpanded;
private readonly HashSet<string> _ignoredNames = new HashSet<string>();
private readonly HashSet<Type> _ignoredTypes = new HashSet<Type>();
private readonly LinkedList<Type> _ignoredInterfaces = new LinkedList<Type>();
private readonly LinkedList<string> _ignoredSuffixes = new LinkedList<string>();
private readonly IDictionary<Type, Func<object, object, bool>> _predicates = new Dictionary<Type, Func<object, object, bool>>();
private bool _withoutSort;
private int _maxRecursion = int.MaxValue;
private readonly HashSet<VisitedComparison> _visitedObjects = new HashSet<VisitedComparison>();
private static readonly HashSet<string> _globallyIgnoredNames = new HashSet<string>();
private static readonly HashSet<Type> _globallyIgnoredTypes = new HashSet<Type>();
private static readonly LinkedList<Type> _globallyIgnoredInterfaces = new LinkedList<Type>();
private static object _regionalTolerance;
public ContentsEqualConstraint(object expectedValue)
{
expected = expectedValue;
}
public ContentsEqualConstraint Comparing<T>(Func<T, T, bool> predicate)
{
Type t = typeof (T);
if (predicate == null)
{
_predicates.Remove(t);
}
else
{
_predicates[t] = (x, y) => predicate((T) x, (T) y);
}
return this;
}
public ContentsEqualConstraint Ignoring(string fieldName)
{
_ignoredNames.Add(fieldName);
return this;
}
public ContentsEqualConstraint Ignoring(Type fieldType)
{
if (fieldType.IsInterface)
{
_ignoredInterfaces.AddFirst(fieldType);
}
else
{
_ignoredTypes.Add(fieldType);
}
return this;
}
public ContentsEqualConstraint IgnoringSuffix(string suffix)
{
if (string.IsNullOrEmpty(suffix))
{
throw new ArgumentNullException("suffix");
}
_ignoredSuffixes.AddLast(suffix);
return this;
}
public ContentsEqualConstraint WithoutSort()
{
_withoutSort = true;
return this;
}
public ContentsEqualConstraint RecursingOnly(int levels)
{
_maxRecursion = levels;
return this;
}
public static void GlobalIgnore(string fieldName)
{
_globallyIgnoredNames.Add(fieldName);
}
public static void GlobalIgnore(Type fieldType)
{
if (fieldType.IsInterface)
{
_globallyIgnoredInterfaces.AddFirst(fieldType);
}
else
{
_globallyIgnoredTypes.Add(fieldType);
}
}
public static IDisposable RegionalIgnore(string fieldName)
{
return new RegionalIgnoreTracker(fieldName);
}
public static IDisposable RegionalIgnore(Type fieldType)
{
return new RegionalIgnoreTracker(fieldType);
}
public static IDisposable RegionalWithin(object tolerance)
{
return new RegionalWithinTracker(tolerance);
}
public override bool Matches(object actualValue)
{
typePathExpanded = null;
actual = actualValue;
return Matches(expected, actualValue);
}
private bool Matches(object expectedValue, object actualValue)
{
bool matches = true;
if (!MatchesNull(expectedValue, actualValue, ref matches))
{
return matches;
}
// DatesEqualConstraint supports tolerance in dates but works as equal constraint for everything else
Constraint eq = new DatesEqualConstraint(expectedValue).Within(tolerance ?? _regionalTolerance);
if (eq.Matches(actualValue))
{
return true;
}
if (MatchesVisited(expectedValue, actualValue, ref matches))
{
if (MatchesDictionary(expectedValue, actualValue, ref matches) &&
MatchesList(expectedValue, actualValue, ref matches) &&
MatchesType(expectedValue, actualValue, ref matches) &&
MatchesPredicate(expectedValue, actualValue, ref matches))
{
MatchesFields(expectedValue, actualValue, eq, ref matches);
}
}
return matches;
}
private bool MatchesNull(object expectedValue, object actualValue, ref bool matches)
{
if (IsNullEquivalent(expectedValue))
{
expectedValue = null;
}
if (IsNullEquivalent(actualValue))
{
actualValue = null;
}
if (expectedValue == null && actualValue == null)
{
matches = true;
return false;
}
if (expectedValue == null)
{
expectedDescription = "null";
actualDescription = "NOT null";
matches = Failure;
return false;
}
if (actualValue == null)
{
expectedDescription = "not null";
actualDescription = "null";
matches = Failure;
return false;
}
return true;
}
private bool MatchesType(object expectedValue, object actualValue, ref bool matches)
{
Type expectedType = expectedValue.GetType();
Type actualType = actualValue.GetType();
if (expectedType != actualType)
{
try
{
Convert.ChangeType(actualValue, expectedType);
}
catch(InvalidCastException)
{
expectedDescription = expectedType.FullName;
actualDescription = actualType.FullName;
matches = Failure;
return false;
}
}
return true;
}
private bool MatchesPredicate(object expectedValue, object actualValue, ref bool matches)
{
Type t = expectedValue.GetType();
Func<object, object, bool> predicate;
if (_predicates.TryGetValue(t, out predicate))
{
matches = predicate(expectedValue, actualValue);
return false;
}
return true;
}
private bool MatchesVisited(object expectedValue, object actualValue, ref bool matches)
{
var c = new VisitedComparison(expectedValue, actualValue);
if (_visitedObjects.Contains(c))
{
matches = true;
return false;
}
_visitedObjects.Add(c);
return true;
}
private bool MatchesDictionary(object expectedValue, object actualValue, ref bool matches)
{
if (expectedValue is IDictionary && actualValue is IDictionary)
{
var expectedDictionary = (IDictionary)expectedValue;
var actualDictionary = (IDictionary)actualValue;
if (expectedDictionary.Count != actualDictionary.Count)
{
expectedDescription = expectedDictionary.Count + " item dictionary";
actualDescription = actualDictionary.Count + " item dictionary";
matches = Failure;
return false;
}
foreach (DictionaryEntry expectedEntry in expectedDictionary)
{
if (!actualDictionary.Contains(expectedEntry.Key))
{
expectedDescription = expectedEntry.Key + " exists";
actualDescription = expectedEntry.Key + " does not exist";
matches = Failure;
return false;
}
if (CanRecurseFurther)
{
typePath.Push(expectedEntry.Key.ToString());
if (!Matches(expectedEntry.Value, actualDictionary[expectedEntry.Key]))
{
matches = Failure;
return false;
}
typePath.Pop();
}
}
matches = true;
return false;
}
return true;
}
private bool MatchesList(object expectedValue, object actualValue, ref bool matches)
{
if (!(expectedValue is IList && actualValue is IList))
{
return true;
}
var expectedList = (IList) expectedValue;
var actualList = (IList) actualValue;
if (!Matches(expectedList.Count, actualList.Count))
{
matches = false;
}
else
{
if (CanRecurseFurther)
{
int max = expectedList.Count;
if (max != 0 && !_withoutSort)
{
SafeSort(expectedList);
SafeSort(actualList);
}
for (int i = 0; i < max; i++)
{
typePath.Push(i.ToString());
if (!Matches(expectedList[i], actualList[i]))
{
matches = false;
return false;
}
typePath.Pop();
}
}
matches = true;
}
return false;
}
private void MatchesFields(object expectedValue, object actualValue, Constraint equalConstraint, ref bool matches)
{
Type expectedType = expectedValue.GetType();
FieldInfo[] fields = expectedType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
// should have passed the EqualConstraint check
if (expectedType.IsPrimitive ||
expectedType == typeof(string) ||
expectedType == typeof(Guid) ||
fields.Length == 0)
{
failedEquality = equalConstraint;
matches = Failure;
return;
}
if (expectedType == typeof(DateTime))
{
var expectedDate = (DateTime)expectedValue;
var actualDate = (DateTime)actualValue;
if (Math.Abs((expectedDate - actualDate).TotalSeconds) > 3.0)
{
failedEquality = equalConstraint;
matches = Failure;
return;
}
matches = true;
return;
}
if (CanRecurseFurther)
{
while(true)
{
foreach (FieldInfo field in fields)
{
if (!Ignore(field))
{
typePath.Push(field.Name);
if (!Matches(GetValue(field, expectedValue), GetValue(field, actualValue)))
{
matches = Failure;
return;
}
typePath.Pop();
}
}
expectedType = expectedType.BaseType;
if (expectedType == null)
{
break;
}
fields = expectedType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
}
}
matches = true;
return;
}
private bool Ignore(FieldInfo field)
{
if (_ignoredNames.Contains(field.Name) ||
_ignoredTypes.Contains(field.FieldType) ||
_globallyIgnoredNames.Contains(field.Name) ||
_globallyIgnoredTypes.Contains(field.FieldType) ||
field.GetCustomAttributes(typeof (IgnoreContentsAttribute), false).Length != 0)
{
return true;
}
foreach(string ignoreSuffix in _ignoredSuffixes)
{
if (field.Name.EndsWith(ignoreSuffix))
{
return true;
}
}
foreach (Type ignoredInterface in _ignoredInterfaces)
{
if (ignoredInterface.IsAssignableFrom(field.FieldType))
{
return true;
}
}
return false;
}
private static bool Failure
{
get
{
return false;
}
}
private static bool IsNullEquivalent(object value)
{
return value == null ||
value == DBNull.Value ||
(value is int && (int) value == int.MinValue) ||
(value is double && (double) value == double.MinValue) ||
(value is DateTime && (DateTime) value == DateTime.MinValue) ||
(value is Guid && (Guid) value == Guid.Empty) ||
(value is IList && ((IList)value).Count == 0);
}
private static object GetValue(FieldInfo field, object source)
{
try
{
return field.GetValue(source);
}
catch(Exception ex)
{
return ex;
}
}
public override void WriteMessageTo(MessageWriter writer)
{
if (TypePath.Length != 0)
{
writer.WriteLine("Failure on " + TypePath);
}
if (failedEquality != null)
{
failedEquality.WriteMessageTo(writer);
}
else
{
base.WriteMessageTo(writer);
}
}
public override void WriteDescriptionTo(MessageWriter writer)
{
writer.Write(expectedDescription);
}
public override void WriteActualValueTo(MessageWriter writer)
{
writer.Write(actualDescription);
}
private string TypePath
{
get
{
if (typePathExpanded == null)
{
string[] p = typePath.ToArray();
Array.Reverse(p);
var text = new StringBuilder(128);
bool isFirst = true;
foreach(string part in p)
{
if (isFirst)
{
text.Append(part);
isFirst = false;
}
else
{
int i;
if (int.TryParse(part, out i))
{
text.Append("[" + part + "]");
}
else
{
text.Append("." + part);
}
}
}
typePathExpanded = text.ToString();
}
return typePathExpanded;
}
}
private bool CanRecurseFurther
{
get
{
return typePath.Count < _maxRecursion;
}
}
private static bool SafeSort(IList list)
{
if (list == null)
{
return false;
}
if (list.Count < 2)
{
return true;
}
try
{
object first = FirstNonNull(list) as IComparable;
if (first == null)
{
return false;
}
if (list is Array)
{
Array.Sort((Array)list);
return true;
}
return CallIfExists(list, "Sort");
}
catch
{
return false;
}
}
private static object FirstNonNull(IEnumerable enumerable)
{
if (enumerable == null)
{
throw new ArgumentNullException("enumerable");
}
foreach (object item in enumerable)
{
if (item != null)
{
return item;
}
}
return null;
}
private static bool CallIfExists(object instance, string method)
{
if (instance == null)
{
throw new ArgumentNullException("instance");
}
if (String.IsNullOrEmpty(method))
{
throw new ArgumentNullException("method");
}
Type target = instance.GetType();
MethodInfo m = target.GetMethod(method, new Type[0]);
if (m != null)
{
m.Invoke(instance, null);
return true;
}
return false;
}
#region VisitedComparison Helper
private class VisitedComparison
{
private readonly object _expected;
private readonly object _actual;
public VisitedComparison(object expected, object actual)
{
_expected = expected;
_actual = actual;
}
public override int GetHashCode()
{
return GetHashCode(_expected) ^ GetHashCode(_actual);
}
private static int GetHashCode(object o)
{
if (o == null)
{
return 0;
}
return o.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
if (obj.GetType() != typeof(VisitedComparison))
{
return false;
}
var other = (VisitedComparison) obj;
return _expected == other._expected &&
_actual == other._actual;
}
}
#endregion
#region RegionalIgnoreTracker Helper
private class RegionalIgnoreTracker : IDisposable
{
private readonly string _fieldName;
private readonly Type _fieldType;
public RegionalIgnoreTracker(string fieldName)
{
if (!_globallyIgnoredNames.Add(fieldName))
{
_globallyIgnoredNames.Add(fieldName);
_fieldName = fieldName;
}
}
public RegionalIgnoreTracker(Type fieldType)
{
if (!_globallyIgnoredTypes.Add(fieldType))
{
_globallyIgnoredTypes.Add(fieldType);
_fieldType = fieldType;
}
}
public void Dispose()
{
if (_fieldName != null)
{
_globallyIgnoredNames.Remove(_fieldName);
}
if (_fieldType != null)
{
_globallyIgnoredTypes.Remove(_fieldType);
}
}
}
#endregion
#region RegionalWithinTracker Helper
private class RegionalWithinTracker : IDisposable
{
public RegionalWithinTracker(object tolerance)
{
_regionalTolerance = tolerance;
}
public void Dispose()
{
_regionalTolerance = null;
}
}
#endregion
#region IgnoreContentsAttribute
[AttributeUsage(AttributeTargets.Field)]
public sealed class IgnoreContentsAttribute : Attribute
{
}
#endregion
}
public class DatesEqualConstraint : EqualConstraint
{
private readonly object _expected;
public DatesEqualConstraint(object expectedValue) : base(expectedValue)
{
_expected = expectedValue;
}
public override bool Matches(object actualValue)
{
if (tolerance != null && tolerance is TimeSpan)
{
if (_expected is DateTime && actualValue is DateTime)
{
var expectedDate = (DateTime) _expected;
var actualDate = (DateTime) actualValue;
var toleranceSpan = (TimeSpan) tolerance;
if ((actualDate - expectedDate).Duration() <= toleranceSpan)
{
return true;
}
}
tolerance = null;
}
return base.Matches(actualValue);
}
}
}
This is actually a simple process. Using reflection you can compare each field on the object.
public static Boolean ObjectMatches(Object x, Object y)
{
if (x == null && y == null)
return true;
else if ((x == null && y != null) || (x != null && y == null))
return false;
Type tx = x.GetType();
Type ty = y.GetType();
if (tx != ty)
return false;
foreach(FieldInfo field in tx.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
if (field.FieldType.IsValueType && (field.GetValue(x).ToString() != field.GetValue(y).ToString()))
return false;
else if (field.FieldType.IsClass && !ObjectMatches(field.GetValue(x), field.GetValue(y)))
return false;
}
return true;
}
Using the Nuget suggested by Jesse and this code I managed to compare two objects with great results.
using KellermanSoftware.CompareNetObjects;
using System;
namespace MyProgram.UnitTestHelper
{
public class ObjectComparer
{
public static bool ObjectsHaveSameValues(object first, object second)
{
CompareLogic cl = new CompareLogic();
ComparisonResult result = cl.Compare(first, second);
if (!result.AreEqual)
Console.WriteLine(result.DifferencesString);
return result.AreEqual;
}
}
}
Here is a simple comparer that we've used with unit testing to assert that two objects have equal properties. It's a mash-up of ideas found in various articles, and handles circular references.
public static class TestHelper
{
public static void AssertAreEqual(Object expected, Object actual, String name)
{
// Start a new check with an empty list of actual objects checked
// The list of actual objects checked is used to ensure that circular references don't result in infinite recursion
List<Object> actualObjectsChecked = new List<Object>();
AssertAreEqual(expected, actual, name, actualObjectsChecked);
}
private static void AssertAreEqual(Object expected, Object actual, String name, List<Object> actualObjectsChecked)
{
// Just return if already checked the actual object
if (actualObjectsChecked.Contains(actual))
{
return;
}
actualObjectsChecked.Add(actual);
// If both expected and actual are null, they are considered equal
if (expected == null && actual == null)
{
return;
}
if (expected == null && actual != null)
{
Assert.Fail(String.Format("The actual value of {0} was not null when null was expected.", name));
}
if (expected != null && actual == null)
{
Assert.Fail(String.Format("The actual value of {0} was null when an instance was expected.", name));
}
// Get / check type info
// Note: GetType always returns instantiated (i.e. most derived) type
Type expectedType = expected.GetType();
Type actualType = actual.GetType();
if (expectedType != actualType)
{
Assert.Fail(String.Format("The actual type of {0} was not the same as the expected type.", name));
}
// If expected is a Primitive, Value, or IEquatable type, assume Equals is sufficient to check
// Note: Every IEquatable type should have also overridden Object.Equals
if (expectedType.IsPrimitive || expectedType.IsValueType || expectedType.IsIEquatable())
{
Assert.IsTrue(expected.Equals(actual), "The actual {0} is not equal to the expected.", name);
return;
}
// If expected is an IEnumerable type, assume comparing enumerated items is sufficient to check
IEnumerable<Object> expectedEnumerable = expected as IEnumerable<Object>;
IEnumerable<Object> actualEnumerable = actual as IEnumerable<Object>;
if ((expectedEnumerable != null) && (actualEnumerable != null))
{
Int32 actualEnumerableCount = actualEnumerable.Count();
Int32 expectedEnumerableCount = expectedEnumerable.Count();
// Check size first
if (actualEnumerableCount != expectedEnumerableCount)
{
Assert.Fail(String.Format("The actual number of enumerable items in {0} did not match the expected number.", name));
}
// Check items in order, assuming order is the same
for (int i = 0; i < actualEnumerableCount; ++i)
{
AssertAreEqual(expectedEnumerable.ElementAt(i), actualEnumerable.ElementAt(i), String.Format("{0}[{1}]", name, i), actualObjectsChecked);
}
return;
}
// If expected is not a Primitive, Value, IEquatable, or Ienumerable type, assume comparing properties is sufficient to check
// Iterate through properties
foreach (PropertyInfo propertyInfo in actualType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
// Skip properties that can't be read or require parameters
if ((!propertyInfo.CanRead) || (propertyInfo.GetIndexParameters().Length != 0))
{
continue;
}
// Get properties from both
Object actualProperty = propertyInfo.GetValue(actual, null);
Object expectedProperty = propertyInfo.GetValue(expected, null);
AssertAreEqual(expectedProperty, actualProperty, String.Format("{0}.{1}", name, propertyInfo.Name), actualObjectsChecked);
}
}
public static Boolean IsIEquatable(this Type type)
{
return type.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEquatable<>));
}
}

Categories