I have provided a dotnetfiddle to show the issue.
I try to copy object from a source that have the same property names and type except some properties that have IEnumerable and target object has IList using reflection.
public T CopyTo<T>(object src)
where T : new()
{
var targetObj = new T();
//Getting Type of Src
var sourceType = src.GetType();
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty;
var sourcePi = sourceType.GetProperties(flags);
foreach (var property in sourcePi)
{
var pi = targetObj.GetType().GetProperty(property.Name);
if (pi == null || !pi.CanWrite)
continue;
object sourceValue = property.GetValue(src, null);
//var sourceValue = Convert.ChangeType(property.GetValue(src, null), pi.PropertyType);
//this works, but hard wired
if (sourceValue is IEnumerable<string> i)
sourceValue = ((IEnumerable<string>)i).Cast<string>().ToList();
pi.SetValue(targetObj, sourceValue, null);
}
return targetObj;
}
It raises an error:
System.ArgumentException: 'Object of type 'System.String[]' cannot be converted to type 'System.Collections.Generic.List`1[System.String]'.'
I tried to convert:
var sourceValue = Convert.ChangeType(property.GetValue(src, null), pi.PropertyType);
but also get error
System.InvalidCastException: Object must implement IConvertible.
this issue can't help.
My workaround solution is casting :
sourceValue = ((IEnumerable<string>) sourceValue).Cast<string>().ToList();
The disadvantage is hard wiring the cast to IEnumerable<string>
Is there a better way to copy IEnumerable<T> to IList<T>
or any generic collection using reflection.
CodePudding user response:
I think you need to add a check of the destination type too.
And then, With the two different type (source and destination), You can create a function that will handle the conversion/cast.
Something like this:
1. Source IEnumerable<T>, Destination: List<T> => Create New list and put in CTOR
2. Source List<T>, Destination: List<T> => Just Copy
3. Source List<T>, Destination: IEnumerable<T> => Create a list
4. Source IEnumerable<T> ,Destination: IEnumerable<T> => Made a copy?
CodePudding user response:
A simple approach would be to check if IEnumerable<T>
is assignable from the source property's type, check if the destination property's type is assignable from List<T>
, and if so then assign a new List<T>
to the destination property passing the source property's value into the constructor.
public T CopyTo<T>(object src) where T : new()
{
var targetObj = new T();
var sourceType = src.GetType();
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty;
var sourcePi = sourceType.GetProperties(flags);
foreach (var property in sourcePi)
{
var pi = targetObj.GetType().GetProperty(property.Name);
if (pi == null || !pi.CanWrite)
continue;
object sourceValue = property.GetValue(src, null);
//var sourceValue = Convert.ChangeType(property.GetValue(src, null), pi.PropertyType);
//this works, but hard wired
if (property.PropertyType.IsGenericType)
{
Type enumerable = typeof(IEnumerable<>).MakeGenericType(property.PropertyType.GenericTypeArguments);
if (enumerable.IsAssignableFrom(property.PropertyType))
{
Type list = typeof(List<>).MakeGenericType(property.PropertyType.GenericTypeArguments);
if (pi.PropertyType.IsAssignableFrom(list))
{
var pValue = Activator.CreateInstance(list, new[] { sourceValue });
pi.SetValue(targetObj, pValue, null);
}
}
}
else
{
pi.SetValue(targetObj, sourceValue, null);
}
}
return targetObj;
}
It isn't hard to imagine situations where this wouldn't work... for example if the destination type's property is LinkedList<T>
or T[]
then the properties won't copy. You'd have to modify it further to handle cases like this.
This would also create a new List<T>
for the destination object even if the properties on the two objects are both exactly the same type. It isn't clear if that is what you want or not, but it is different from how other properties are copied by this method so it is worth mentioning.