Invoke Enumerable.Except (IEnumerable) on dictionaries using reflection - c#

I'm trying to use reflection to compare properties of objects that are the same type.
The problem is that with reference types <T> == <T> won't do So I try to use reflection to compare values of IEnumerable for this I try to invoke Enumerable.Except(T)
It works on List but won't work for Dictionaries:
Unable to cast object of type
'd__571[System.Collections.Generic.KeyValuePair2[System.String,System.String]]'
to type 'System.Collections.Generic.IEnumerable`1[System.Object]'.
The issue is with this code :
var typeKeyValuePair = typeof(KeyValuePair<,>);
Type[] typeArgs = { args[0], args[1] };
exceptMethods = typeof(Enumerable)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.FirstOrDefault(mi => mi.Name == "Except")
?.MakeGenericMethod(typeKeyValuePair.MakeGenericType(typeArgs));
Full code for the info
public static List<Variance> DetailedCompare<T>(this T val1, T val2)
{
List<Variance> variances = new List<Variance>();
PropertyInfo[] propertyInfo = val1.GetType().GetProperties();
foreach (PropertyInfo p in propertyInfo)
{
Variance v = new Variance();
v.Prop = p.Name;
v.valA = p.GetValue(val1);
v.valB = p.GetValue(val2);
switch (v.valA)
{
case null when v.valB == null:
continue;
case null:
variances.Add(v);
continue;
}
if (v.valA.Equals(v.valB)) continue;
if (typeof(IEnumerable).IsAssignableFrom(p.PropertyType))
{
//string
if (p.PropertyType == typeof(string))
{
variances.Add(v);
continue;
}
var args = p.PropertyType.GetGenericArguments();
MethodInfo exceptMethods = null;
if (args.Length == 2) //dictionaries
{
variances.Add(v); // add to difference while not able to compare
/*
var typeKeyValuePair = typeof(KeyValuePair<,>);
Type[] typeArgs = { args[0], args[1] };
exceptMethods = typeof(Enumerable)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.FirstOrDefault(mi => mi.Name == "Except")
?.MakeGenericMethod(typeKeyValuePair.MakeGenericType(typeArgs));*/
}
else if (args.Length == 1)//lists
{
exceptMethods = typeof(Enumerable)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.FirstOrDefault(mi => mi.Name == "Except")
?.MakeGenericMethod(p.PropertyType.GetGenericArguments().FirstOrDefault());
}
else//not
{
variances.Add(v);
}
if (exceptMethods != null)
{
try
{
var res1 = (IEnumerable<object>)exceptMethods.Invoke(v.valA, new[] { v.valA, v.valB });
var res2 = (IEnumerable<object>)exceptMethods.Invoke(v.valB, new[] { v.valB, v.valA });
if (res1.Any() != res2.Any()) variances.Add(v);
}
catch (Exception ex)
{
}
/* if (v.valA.Except(v.valB).Any() || v.valB.Except(v.valA).Any())
{
variances.Add(v);
}*/
}
}
}
return variances;
}
}
class Variance
{
public string Prop { get; set; }
public object valA { get; set; }
public object valB { get; set; }
}

I think you might consider casting to the generic IEnumerable, and then boxing it to objects with the .OfType overloading function. The complete code would look like this:
void TestFunction()
{
var v1 = new { yes = "asdf", no = "as", ar = new List<int>() { 1, 2, 3 }, dict = new Dictionary<object, object>() { { 1, 1 }, { 2, 2 } } };
var v2 = new { yes = "asdf", no = "fd", ar = new List<int>() { 1, 2, 3 }, dict = new Dictionary<object, object>() { { 1, 1 }, { 2, 2 } } };
var differences = DetailedCompare(v1, v2);
}
public static List<Variance> DetailedCompare<T>(T val1, T val2)
{
List<Variance> variances = new List<Variance>();
PropertyInfo[] proppertyInfo = val1.GetType().GetProperties();
foreach (PropertyInfo p in proppertyInfo)
{
Variance v = new Variance();
v.Prop = p.Name;
v.valA = p.GetValue(val1);
v.valB = p.GetValue(val2);
switch (v.valA)
{
case null when v.valB == null:
continue;
case null:
variances.Add(v);
continue;
}
if (v.valA.Equals(v.valB)) continue;
if (typeof(IEnumerable).IsAssignableFrom(p.PropertyType))
{
//string
if (p.PropertyType == typeof(string))
{
variances.Add(v);
continue;
}
var args = p.PropertyType.GetGenericArguments();
MethodInfo exceptMethods = null;
if (args.Length == 2) //dictionaries
{
//variances.Add(v); // add to difference while not able to compare
var typeKeyValuePair = typeof(KeyValuePair<,>);
Type[] typeArgs = { args[0], args[1] };
exceptMethods = typeof(Enumerable)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.FirstOrDefault(mi => mi.Name == "Except")
?.MakeGenericMethod(typeKeyValuePair.MakeGenericType(typeArgs));
}
else if (args.Length == 1)//lists
{
exceptMethods = typeof(Enumerable)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.FirstOrDefault(mi => mi.Name == "Except")
?.MakeGenericMethod(p.PropertyType.GetGenericArguments().FirstOrDefault());
}
else//not
{
variances.Add(v);
}
if (exceptMethods != null)
{
try
{
var res1 = (IEnumerable)exceptMethods.Invoke(v.valA, new[] { v.valA, v.valB });
var res2 = (IEnumerable)exceptMethods.Invoke(v.valB, new[] { v.valB, v.valA });
// TODO: maybe implement better comparisson
if (res1.OfType<object>().Any() != res2.OfType<object>().Any()) variances.Add(v);
}
catch (Exception ex)
{
}
/* if (v.valA.Except(v.valB).Any() || v.valB.Except(v.valA).Any())
{
variances.Add(v);
}*/
}
}
}
return variances;
}
public class Variance
{
public string Prop { get; set; }
public object valA { get; set; }
public object valB { get; set; }
public override string ToString() => $" Property {Prop} is either {valA} resp. {valB}";
}

Related

Create object by merging other three with priority (C# .NET 3.5)

I have three C# (using .NET 3.5 for a legacy sw) objects of the same class (A, B, C) that has all public properties (string, int, short, byte, datetime, double)
I need to create a fourth (D) by merging the "three" objects.
If A has a property set (not null or empty) I have to set it in D, otherwise I have to check B and then as last C.
What's the most effective and elegant way to do it?
I've read about Reflection is that the right way?
Reflection is a way to do it. Below is an example; probably not the most elegant, but it can be built upon:
using System
using System.Reflection;
namespace Test
{
class Program
{
static void Main(string[] args)
{
Car A = new Car
{
Make = "Volvo"
};
Car B = new Car
{
Year = 2001,
CreateDate = DateTime.Now
};
Car C = new Car
{
ShortValue = 1,
MSRP = 20000,
ByteValue = 10
};
Car D = new Car();
Mapper mapobj = new Mapper();
D = mapobj.Map<Car>(A);
D = mapobj.Compare<Car>(B, D);
D = mapobj.Compare<Car>(C, D);
// Car D now has all the initialized properties of A,B,C
}
public class Mapper
{
public T Map<T>(T data)
{
var _result = (T)Activator.CreateInstance(typeof(T), new object[] { });
foreach (PropertyInfo propertyInfo in typeof(T).GetProperties())
{
if (typeof(T).GetProperty(propertyInfo.Name) != null)
typeof(T)
.GetProperty(propertyInfo.Name,
BindingFlags.IgnoreCase |
BindingFlags.Instance |
BindingFlags.Public)
.SetValue(_result,
propertyInfo.GetValue(data));
}
return _result;
}
public T Compare<T>(T data, T data2)
{
var _result = (T)Activator.CreateInstance(typeof(T), new object[] { });
foreach (PropertyInfo propertyInfo in typeof(T).GetProperties())
{
if (typeof(T).GetProperty(propertyInfo.Name) != null)
{
bool isnullvalue = false;
DateTime zerodate = new DateTime();
switch (propertyInfo.PropertyType.Name)
{
case "String":
if ((string)propertyInfo.GetValue(data) != null && (string)propertyInfo.GetValue(data2) == null)
isnullvalue = true;
break;
case "Int32":
if ((Int32)propertyInfo.GetValue(data) != 0 && (Int32)propertyInfo.GetValue(data2) == 0)
isnullvalue = true;
break;
case "Int16":
if ((Int16)propertyInfo.GetValue(data) != 0 && (Int16)propertyInfo.GetValue(data2) == 0)
isnullvalue = true;
break;
case "Byte":
if ((Byte)propertyInfo.GetValue(data) != 0 && (Byte)propertyInfo.GetValue(data2) == 0)
isnullvalue = true;
break;
case "Double":
if ((Double)propertyInfo.GetValue(data) != 0 && (Double)propertyInfo.GetValue(data2) == 0)
isnullvalue = true;
break;
case "DateTime": // DateTime.Compare(date1, date2)
DateTime time1 = (DateTime)propertyInfo.GetValue(data);
DateTime time2 = (DateTime)propertyInfo.GetValue(data2);
if (DateTime.Compare(time1, zerodate) != 0 && DateTime.Compare(time2, zerodate) == 0)
isnullvalue = true;
break;
default:
Console.WriteLine("No handler for type {} found");
Console.ReadLine();
Environment.Exit(-1);
break;
}
if (isnullvalue)
{
typeof(T).GetProperty(propertyInfo.Name,
BindingFlags.IgnoreCase |
BindingFlags.Instance |
BindingFlags.Public)
.SetValue(_result,
propertyInfo.GetValue(data));
}
else
{
typeof(T).GetProperty(propertyInfo.Name,
BindingFlags.IgnoreCase |
BindingFlags.Instance |
BindingFlags.Public)
.SetValue(_result,
propertyInfo.GetValue(data2));
}
}
}
return _result;
}
}
public class Car
{
public string Make { get; set; }
public int Year { get; set; }
public short ShortValue { get; set; }
public byte ByteValue { get; set; }
public DateTime CreateDate { get; set; }
public double MSRP { get; set; }
}
}
}
You could undoubtedly make a reflection-based solution work here, but you might not need it. If you know the type being merged in advanced, you can write a very simple mapping function to handle this.
For example, given a simple class...
class MyClassToMap
{
public string MyString { get; set; }
public int MyInt { get; set; }
}
you could write a simple method...
MyClassToMap Map(params MyClassToMap[] toMap)
{
var mapped = new MyClassToMap();
foreach (var m in toMap)
{
// 'default' is shorthand for a type's uninitalized value. In the case of
// string, it resolves to "null", and in the case of int, it resolves to 0.
// You could also use the literal values here, if you prefer.
// Note that for C# versions < 7.1, you must specify the type--eg "default(string)".
// See: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/default
if (m.MyString != default) mapped.MyString = m.MyString;
if (m.MyInt != default) mapped.MyInt = m.MyInt;
}
return mapped;
}
and call it like so...
var a = new MyClassToMap { MyString = "foo", MyInt = 0 };
var b = new MyClassToMap { MyString = "bar", MyInt = 100 };
var c = new MyClassToMap { MyString = null, MyInt = 0 };
var mapped = Map(a, b, c);
Console.WriteLine($"MyString = {mapped.MyString}, MyInt = {mapped.MyInt}");
// prints: { MyString = "bar", MyInt = 100 };
You could use reflection for the purpose. For example, Please check following code (Inline comments added to method explaining the approach)
public T AssignProperties<T>(params T[] sources)
{
// Types of properties that has to be copied
var filteredPropertyTypes = new []{typeof(int),typeof(string),typeof(short),typeof(byte),typeof(DateTime),typeof(double)};
// Create Default Instance of T
var newInstance = (T)Activator.CreateInstance(typeof(T));
// Iterate through each property
foreach(var property in typeof(T).GetProperties().Where(x=>filteredPropertyTypes.Contains(x.PropertyType)))
{
// Get the default Value of the Type and get the first instance in sources, which has a non-default value for the property
var defaultValue = property.PropertyType.IsValueType ? Convert.ChangeType(Activator.CreateInstance(property.PropertyType),property.PropertyType) : null;
if(sources.Any(x=>!Convert.ChangeType(property.GetValue(x),property.PropertyType).Equals(defaultValue)))
{
var newValue = property.GetValue(sources.First(x=>!Convert.ChangeType(property.GetValue(x),property.PropertyType).Equals(defaultValue)));
property.SetValue(newInstance,newValue);
}
}
return newInstance;
}
You could now use the method for N number of instances(A,B,C...), with instances passed in order in which they need to be processed.
var newInstance = AssignProperties(instanceA,instanceB,instanceC);
Demo Code

How to check if a Type is custom Class

I'm trying fill a list of object from data table using the following method
public static List<T> toList<T>(this DataTable table) where T : new()
{
try
{
List<T> list = new List<T>();
foreach (var row in table.AsEnumerable())
{
var obj = new T();
foreach (var prop in typeof(T).GetProperties())
{
PropertyInfo propertyInfo = obj.GetType().GetProperty(prop.Name);
Type targetType = propertyInfo.PropertyType;
if (table.Columns.Contains(prop.Name))
{
try
{
object value = row[prop.Name];
if (value != null)
{
if (value.GetType() == typeof(string))
{
if (string.IsNullOrWhiteSpace(value.ToString()))
{
value = null;
}
}
if (targetType.IsGenericType && targetType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
targetType = Nullable.GetUnderlyingType(targetType);
}
value = Convert.ChangeType(value, targetType);
propertyInfo.SetValue(obj, value);
}
}
catch
{
continue;
}
}
}
list.Add(obj);
}
return list;
}
catch (Exception ex)
{
return null;
}
}
I've the following models
public class A
{
public string str1 {get;set;}
public int int1 {get;set;}
public DateTime dateTime1 {get;set;}
}
public class B
{
public string str2 {get;set;}
public int int2 {get;set;}
public DateTime dateTime2 {get;set;}
public A vara {get;set;}
}
My Data table looks like this
+-----------+-----------+-----------+-----------+---------------+---------------+
| str1 | str2 | int1 | int2 | dateTime1 | dateTime2 |
+-----------+-----------+-----------+-----------+---------------+---------------+
| "abc" | "def" | 1 | 2 | NULL | NULL |
+-----------+-----------+-----------+-----------+---------------+---------------+
All this works fine If I use
List<B> list = dataTable.toList<B>();
But I also want to set the value of vara in each element of list.
How can I check that if a Type is custom defined type?
I can not use Type.IsClass because it is true for string too.
If I can detect that a property is of Custom Class Type then I can fill that's value using the same method.
I hope that I've explained it well.
I was able create a following generic solution
public static List<T> toList<T>(this DataTable table) where T : new()
{
try
{
var list = table.toList(typeof(T));
var newLIst = list.Cast<T>().ToList();
return newLIst;
}
catch
{
return null;
}
}
public static List<object> toList(this DataTable table, Type type)
{
try
{
List<object> list = new List<object>();
foreach (var row in table.AsEnumerable())
{
var obj = row.toObject(type);
list.Add(obj);
}
return list;
}
catch
{
return null;
}
}
public static object toObject(this DataRow row, Type type, string sourcePropName = "")
{
try
{
var obj = Activator.CreateInstance(type);
var props = type.GetProperties();
foreach (var prop in props)
{
PropertyInfo propertyInfo = type.GetProperty(prop.Name);
Type targetType = propertyInfo.PropertyType;
string propName = prop.Name;
if (!string.IsNullOrWhiteSpace(sourcePropName))
{
propName = sourcePropName + "__" + propName;
if (!row.Table.Columns.Contains(propName))
{
propName = prop.Name;
}
}
if (row.Table.Columns.Contains(propName))
{
try
{
object value = row[propName];
if (value != null)
{
if (value.GetType() == typeof(string))
{
if (string.IsNullOrWhiteSpace(value.ToString()))
{
value = null;
}
}
targetType = targetType.handleNullableType();
value = Convert.ChangeType(value, targetType);
propertyInfo.SetValue(obj, value);
}
}
catch
{
continue;
}
}
else
if (targetType.IsClass && targetType != typeof(string))
{
if (targetType.IsGenericList())
{
Type ltype = targetType.GetProperty("Item").PropertyType;
object value = row.toObject(ltype, propName);
if (value == null)
{
continue;
}
var valList = new List<object> { value }.ConvertList(targetType);
try
{
propertyInfo.SetValue(obj, valList);
}
catch (Exception ex)
{
log.Error(ex);
}
}
else
{
object value = row.toObject(targetType, propName);
propertyInfo.SetValue(obj, value);
}
}
}
return obj;
}
catch
{
return null;
}
}
public static object ConvertList(this List<object> value, Type type)
{
IList list = (IList)Activator.CreateInstance(type);
foreach (var item in value)
{
list.Add(item);
}
return list;
}
In order to fill all properties of class B including vara property, I must prefix the names of columns that belong to vara properties and added splitter ___
Therefore, the about table should be as follows
+-----------------+-----------+-----------------+-----------+---------------------+---------------+
| vara__str1 | str2 | vara__int1 | int2 | vara__dateTime1 | dateTime2 |
+-----------------+-----------+-----------------+-----------+---------------------+---------------+
| "abc" | "def" | 1 | 2 | NULL | NULL |
+-----------------+-----------+-----------------+-----------+---------------------+---------------+

Recursively Mapping ExpandoObject

In my application i have to use ExpandoObject in order to create/delete properties during the runtime; However, i have to map the returned ExpandoObject of a function to the corresponding object/class. So i have came up with a small Mapper that does the job but with 3 problems:
It does not recursively map the inner objects of the ExpandoObject
as supposed.
When i try to map int to a Nullable simply it will throw a type
mismatch because i can't find a way to detect and cast it properly.
Fields can't be mapped public string Property;.
Code:
I- Implementation:
public static class Mapper<T> where T : class
{
#region Properties
private static readonly Dictionary<string, PropertyInfo> PropertyMap;
#endregion
#region Ctor
static Mapper() { PropertyMap = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToDictionary(p => p.Name.ToLower(), p => p); }
#endregion
#region Methods
public static void Map(ExpandoObject source, T destination)
{
if (source == null)
throw new ArgumentNullException("source");
if (destination == null)
throw new ArgumentNullException("destination");
foreach (var kv in source)
{
PropertyInfo p;
if (PropertyMap.TryGetValue(kv.Key.ToLower(), out p))
{
Type propType = p.PropertyType;
if (kv.Value == null)
{
if (!propType.IsByRef && propType.Name != "Nullable`1")
{
throw new ArgumentException("not nullable");
}
}
else if (kv.Value.GetType() != propType)
{
throw new ArgumentException("type mismatch");
}
p.SetValue(destination, kv.Value, null);
}
}
}
#endregion
}
II: Usage:
public static void Main()
{
Class c = new Class();
dynamic o = new ExpandoObject();
o.Name = "Carl";
o.Level = 7;
o.Inner = new InnerClass
{
Name = "Inner Carl",
Level = 10
};
Mapper<Class>.Map(o, c);
Console.Read();
}
internal class Class
{
public string Name { get; set; }
public int? Level { get; set; }
public InnerClass Inner { get; set; }
public string Property;
}
internal class InnerClass
{
public string Name { get; set; }
public int? Level { get; set; }
}
3- If the property is formated like this public string Property; the get properties does not get it.
Oh, that's not a property, that's a field. If you want consider fields as well.
static Mapper()
{
PropertyMap = typeof(T).GetProperties(BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance)
.ToDictionary(p => p.Name.ToLower(), p => p);
FieldMap = typeof(T).GetFields(BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance)
.ToDictionary(f => f.Name.ToLower(), f => f);
}
2- When i try to map int to a Nullable simply it will throw a type mismatch because i can't find a way to detect and cast it properly.
Why check for Nullable type, let reflection figure it out. If value is valid, it will be assigned.
public static void Map(ExpandoObject source, T destination)
{
if (source == null)
throw new ArgumentNullException("source");
if (destination == null)
throw new ArgumentNullException("destination");
foreach (var kv in source)
{
PropertyInfo p;
if (PropertyMap.TryGetValue(kv.Key.ToLower(), out p))
{
p.SetValue(destination, kv.Value, null);
}
else
{
FieldInfo f;
if (FieldMap.TryGetValue(kv.Key.ToLower(), out f))
{
f.SetValue(destination, kv.Value);
}
}
}
}
1 - It does not recursively map the inner objects of the ExpandoObject as supposed.
Seems to work for your InnerClass at least.
Class c = new Class();
dynamic o = new ExpandoObject();
o.Name = "Carl";
o.Level = 7;
o.Inner = new InnerClass
{
Name = "Inner Carl",
Level = 10
};
o.Property = "my Property value"; // dont forget to set this
Mapper<Class>.Map(o, c);
EDIT: based on your comments, I've create two overloaded methods MergeProperty. You can write similarly overloaded methods for fields.
public static void MergeProperty(PropertyInfo pi, ExpandoObject source, object target)
{
Type propType = pi.PropertyType;
// dont recurse for value type, Nullable<T> and strings
if (propType.IsValueType || propType == typeof(string))
{
var sourceVal = source.First(kvp => kvp.Key == pi.Name).Value;
if(sourceVal != null)
pi.SetValue(target, sourceVal, null);
}
else // recursively map inner class properties
{
var props = propType.GetProperties(BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance);
foreach (var p in props)
{
var sourcePropValue = source.First(kvp => kvp.Key == pi.Name).Value;
var targetPropValue = pi.GetValue(target, null);
if (sourcePropValue != null)
{
if (targetPropValue == null) // replace
{
pi.SetValue(target, source.First(kvp => kvp.Key == pi.Name).Value, null);
}
else
{
MergeProperty(p, sourcePropValue, targetPropValue);
}
}
}
}
}
public static void MergeProperty(PropertyInfo pi, object source, object target)
{
Type propType = pi.PropertyType;
PropertyInfo sourcePi = source.GetType().GetProperty(pi.Name);
// dont recurse for value type, Nullable<T> and strings
if (propType.IsValueType || propType == typeof(string))
{
var sourceVal = sourcePi.GetValue(source, null);
if(sourceVal != null)
pi.SetValue(target, sourceVal, null);
}
else // recursively map inner class properties
{
var props = propType.GetProperties(BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance);
foreach (var p in props)
{
var sourcePropValue = sourcePi.GetValue(source, null);
var targetPropValue = pi.GetValue(target, null);
if (sourcePropValue != null)
{
if (targetPropValue == null) // replace
{
pi.SetValue(target, sourcePi.GetValue(source, null), null);
}
else
{
MergeProperty(p, sourcePropValue, targetPropValue);
}
}
}
}
}
You can use the methods this way:
public static void Map(ExpandoObject source, T destination)
{
if (source == null)
throw new ArgumentNullException("source");
if (destination == null)
throw new ArgumentNullException("destination");
foreach (var kv in source)
{
PropertyInfo p;
if (PropertyMap.TryGetValue(kv.Key.ToLower(), out p))
{
MergeProperty(p, source, destination);
}
else
{
// do similar merge for fields
}
}
}

How to deep copy members of a strongly typed collection

I have two classes XmlPerson and Person, each class has public properties, no methods nor any fields.
How would I deep copy all the properties from Person to XmlPerson? I dont want to use a third-party library like MiscUtil.PropertyCopy or Automapper. I have managed to copy the "first-level" properties that are primitive types and strongly typed objects but when it comes the List I have no idea.
The structure of the Person class is below:
public class Person
{
public string FirstName { get; set; }
public string Surname { get; set; }
public decimal? Salary { get; set; }
public List<AddressDetails> AddressDetails { get; set; }
public NextOfKin NextOfKin { get; set; }
}
public class NextOfKin
{
public string FirstName { get; set; }
public string Surname { get; set; }
public string ContactNumber { get; set; }
public List<AddressDetails> AddressDetails { get; set; }
}
public class AddressDetails
{
public int HouseNumber { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
}
thank u for the help.
charles
Here is the what I have so far:
public class XmlTestCaseToClassMapper
{
internal TTarget MapXmlClassTotargetClass(TSource xmlPerson)
{
var targetObject = Activator.CreateInstance();
var sourceObject = Activator.CreateInstance();
//var xmlClassProperties = xmlPerson.GetType().GetProperties().ToList().OrderBy(x => x.Name);
var xmlClassProperties = GetProperties(xmlPerson.GetType());
//var targetClassProperties = targetObject.GetType().GetProperties().ToList().OrderBy(x => x.Name);
var targetClassProperties = GetProperties(targetObject.GetType());
PropertyInfo targetClassProperty = null;
foreach (var xmlProperty in xmlClassProperties)
{
if (!xmlProperty.PropertyType.IsClass || xmlProperty.PropertyType.UnderlyingSystemType == typeof(string)
|| xmlProperty.PropertyType.IsPrimitive)
{
targetClassProperty = targetClassProperties.ToList().FirstOrDefault(x => x.Name == xmlProperty.Name);
var propertyValue = xmlProperty.GetValue(xmlPerson, null);
targetClassProperty.SetValue(targetObject, propertyValue, null);
}
else if (xmlProperty.PropertyType.UnderlyingSystemType == typeof(NextOfKin)) //Check subType of the property
{
var subPropertyInstance = Activator.CreateInstance(xmlProperty.GetType());
var subProperties = GetProperties(xmlProperty.GetType());
subProperties.ForEach(subProperty =>
{
targetClassProperty = targetClassProperties.ToList().FirstOrDefault(x => x.Name == subProperty.Name && x.GetType().IsClass);
targetClassProperty.SetValue(subPropertyInstance, xmlProperty.GetValue(this, null), null);
});
}
//else if (xmlProperty.PropertyType.IsGenericType)
//{
// var xmlGenericType = xmlProperty.PropertyType.GetGenericArguments().First();
// var xmlGenericTypeProperties = GetProperties(xmlGenericType);
// targetClassProperty = targetClassProperties.ToList().FirstOrDefault(x => x.Name == xmlProperty.Name);
// var targetGenericType = targetClassProperty.PropertyType.GetGenericArguments().First();
// var targetGenericProperties = GetProperties(targetGenericType);
// Type targetGenericList = typeof(List<>).MakeGenericType(new Type[] { targetGenericType });
// object listInstance = Activator.CreateInstance(targetGenericList);
// //foreach (var xmlGenericProperty in xmlGenericTypeProperties)
// //{
// // var targetGenericProperty = targetGenericProperties.FirstOrDefault(x => x.Name == xmlGenericProperty.Name);
// // targetGenericProperty.SetValue(targetGenericProperty, xmlGenericProperty.GetValue(xmlGenericType, null), null);
// //}
// xmlGenericTypeProperties.ForEach(x =>
// {
// foreach (var targetGenericProperty in targetGenericProperties)
// {
// targetGenericProperty.SetValue(targetGenericProperty, targetGenericProperty.GetValue(x, null), null);
// }
// });
//}
//}
}
return targetObject;
}
private List<PropertyInfo> GetProperties(Type targetType)
{
var properties = new List<PropertyInfo>();
targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList().ForEach(property =>
{
properties.Add(property);
});
return properties.OrderBy(x => x.Name).ToList();
}
}
Here is a possible solution. It probably requires some tweaks based on your actual classes.
public T DeepCopy<S, T>(S source) where T : new()
{
var sourceProperties = typeof(S).GetProperties(BindingFlags.Instance | BindingFlags.Public);
T target = new T();
foreach (var sourceProperty in sourceProperties)
{
var property = typeof(T).GetProperty(sourceProperty.Name);
if (property.PropertyType.IsPrimitive ||
property.PropertyType == typeof(string) ||
(property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)))
{
object value = sourceProperty.GetValue(source);
property.SetValue(target, value);
}
else if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(List<>))
{
var sourceList = (IEnumerable)sourceProperty.GetValue(source);
if (sourceList != null)
{
var deepCopy = this.GetType().GetMethod("DeepCopy").MakeGenericMethod(sourceProperty.PropertyType.GenericTypeArguments[0], property.PropertyType.GenericTypeArguments[0]);
var ctor = property.PropertyType.GetConstructor(Type.EmptyTypes);
IList targetList = (IList) ctor.Invoke(null);
foreach (var element in sourceList)
{
targetList.Add(deepCopy.Invoke(this, new object[] { element } ));
}
property.SetValue(target, targetList);
}
}
else
{
var value = sourceProperty.GetValue(source);
if (value != null)
{
var deepCopy = this.GetType().GetMethod("DeepCopy").MakeGenericMethod(sourceProperty.PropertyType, property.PropertyType);
property.SetValue(target, deepCopy.Invoke(this, new object[] { value }));
}
}
}
return target;
}

Making A Function Recursive

the following function needs to look inside the input object if there is a property in it that returns a custom object it needs to do the trimming of that object as well. The code below works for the input object fine, but wont recursively look into a property that returns a custom object and do the trimming process.
public object TrimObjectValues(object instance)
{
var props = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
// Ignore non-string properties
.Where(prop => prop.PropertyType == typeof(string) | prop.PropertyType == typeof(object))
// Ignore indexers
.Where(prop => prop.GetIndexParameters().Length == 0)
// Must be both readable and writable
.Where(prop => prop.CanWrite && prop.CanRead);
foreach (PropertyInfo prop in props)
{
if (prop.PropertyType == typeof(string))
{
string value = (string)prop.GetValue(instance, null);
if (value != null)
{
value = value.Trim();
prop.SetValue(instance, value, null);
}
}
else if (prop.PropertyType == typeof(object))
{
TrimObjectValues(prop);
}
}
return instance;
}
I need to change this somehow to look for other objects inside the initial object
.Where(prop => prop.PropertyType == typeof(string) | prop.PropertyType == typeof(object))
This code isn't working reason is for a example is the object I am passing as input has a property that returns a type of "Address" therefore typeof(object) never gets hit.
Here is a tree of data to test against pass the function "o" in this case
Order o = new Order();
o.OrderUniqueIdentifier = "TYBDEU83e4e4Ywow";
o.VendorName = "Kwhatever";
o.SoldToCustomerID = "Abc98971";
o.OrderType = OrderType.OnOrBefore;
o.CustomerPurchaseOrderNumber = "MOOMOO 56384";
o.EmailAddress = "abc#electric.com";
o.DeliveryDate = DateTime.Now.AddDays(35);
Address address1 = new Address();
//address1.AddressID = "Z0mmn01034";
address1.AddressID = "E0000bbb6 ";
address1.OrganizationName = " Nicks Organization ";
address1.AddressLine1 = " 143 E. WASHINGTON STREET ";
address1.City = " Rock ";
address1.State = "MA ";
address1.ZipCode = " 61114";
address1.Country = "US ";
o.ShipToAddress = address1;
Your tests with typeof(object) will all fail.
Try like this:
static void TrimObjectValues(object instance)
{
// if the instance is null we have nothing to do here
if (instance == null)
{
return;
}
var props = instance
.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
// Ignore indexers
.Where(prop => prop.GetIndexParameters().Length == 0)
// Must be both readable and writable
.Where(prop => prop.CanWrite && prop.CanRead);
foreach (PropertyInfo prop in props)
{
if (prop.PropertyType == typeof(string))
{
// if we have a string property we trim it
string value = (string)prop.GetValue(instance, null);
if (value != null)
{
value = value.Trim();
prop.SetValue(instance, value, null);
}
}
else
{
// if we don't have a string property we recurse
TrimObjectValues(prop.GetValue(instance, null));
}
}
}
I have also made the function return no value because you are modifying the argument instance anyway.
Test case:
public enum OrderType
{
OnOrBefore
}
public class Order
{
public string OrderUniqueIdentifier { get; set; }
public string VendorName { get; set; }
public string SoldToCustomerID { get; set; }
public OrderType OrderType { get; set; }
public string CustomerPurchaseOrderNumber { get; set; }
public string EmailAddress { get; set; }
public DateTime DeliveryDate { get; set; }
public Address ShipToAddress { get; set; }
}
public class Address
{
public string AddressID { get; set; }
public string OrganizationName { get; set; }
public string AddressLine1 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public string Country { get; set; }
}
class Program
{
static void Main()
{
Order o = new Order();
o.OrderUniqueIdentifier = "TYBDEU83e4e4Ywow";
o.VendorName = "Kwhatever";
o.SoldToCustomerID = "Abc98971";
o.OrderType = OrderType.OnOrBefore;
o.CustomerPurchaseOrderNumber = "MOOMOO 56384";
o.EmailAddress = "abc#electric.com";
o.DeliveryDate = DateTime.Now.AddDays(35);
Address address1 = new Address();
//address1.AddressID = "Z0mmn01034";
address1.AddressID = "E0000bbb6 ";
address1.OrganizationName = " Nicks Organization ";
address1.AddressLine1 = " 143 E. WASHINGTON STREET ";
address1.City = " Rock ";
address1.State = "MA ";
address1.ZipCode = " 61114";
address1.Country = "US ";
o.ShipToAddress = address1;
TrimObjectValues(o);
}
static void TrimObjectValues(object instance)
{
if (instance == null)
{
return;
}
var props = instance
.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
// Ignore indexers
.Where(prop => prop.GetIndexParameters().Length == 0)
// Must be both readable and writable
.Where(prop => prop.CanWrite && prop.CanRead);
foreach (PropertyInfo prop in props)
{
if (prop.PropertyType == typeof(string))
{
string value = (string)prop.GetValue(instance, null);
if (value != null)
{
value = value.Trim();
prop.SetValue(instance, value, null);
}
}
else
{
TrimObjectValues(prop.GetValue(instance, null));
}
}
}
}
UPDATE 2:
It seems that you want to handle also lists of objects. You could adapt the method:
static void TrimObjectValues(object instance)
{
if (instance == null)
{
return;
}
var props = instance
.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
// Ignore indexers
.Where(prop => prop.GetIndexParameters().Length == 0)
// Must be both readable and writable
.Where(prop => prop.CanWrite && prop.CanRead);
if (instance is IEnumerable)
{
foreach (var element in (IEnumerable)instance)
{
TrimObjectValues(element);
}
return;
}
foreach (PropertyInfo prop in props)
{
if (prop.PropertyType == typeof(string))
{
string value = (string)prop.GetValue(instance, null);
if (value != null)
{
value = value.Trim();
prop.SetValue(instance, value, null);
}
}
else
{
TrimObjectValues(prop.GetValue(instance, null));
}
}
}
prop.PropertyType == typeof(object) does not work, because this will only be true for object and not for derived types. You would have to write typeof(object).IsAssignableFrom(prop.PropertyType); however, this applies for all the types! Drop both conditions (for string and for object).
Note: Also drop the condition before the TrimObjectValues(prop);. (Replace else if (...) by else)
public object TrimObjectValues(object instance)
{
if (instance is string)
{
instance = ((string)instance).Trim();
return instance;
}
if (instance is IEnumerable)
{
foreach (var element in (IEnumerable)instance)
{
TrimObjectValues(element);
}
return instance;
}
var props = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
// Ignore non-string properties
.Where(prop => prop.PropertyType == typeof(string) | prop.PropertyType is object)
// Ignore indexers
.Where(prop => prop.GetIndexParameters().Length == 0)
// Must be both readable and writable
.Where(prop => prop.CanWrite && prop.CanRead);
foreach (PropertyInfo prop in props)
{
if (prop.PropertyType == typeof(string))
{
string value = (string)prop.GetValue(instance, null);
if (value != null)
{
value = value.Trim();
prop.SetValue(instance, value, null);
}
}
else if (prop.PropertyType is object)
{
TrimObjectValues(prop.GetValue(instance, null));
}
}
return instance;
}

Categories