I am working on some code that is written in C#. In this app, I have a custom collection defined as follows:
public class ResultList<T> : IEnumerable<T>
{
public List<T> Results { get; set; }
public decimal CenterLatitude { get; set; }
public decimal CenterLongitude { get; set; }
}
The type used by Results are one of three custom types. The properties of each of the custom types are just primitive types (ints, strings, bools, int?, bool?). Here is an example of one of the custom types:
public class ResultItem
{
public int ID { get; set; }
public string Name { get; set; }
public bool? isLegit { get; set; }
}
How do I perform a deep copy of a ResultList object that I've created. I found this post: Generic method to create deep copy of all elements in a collection. However, I can't figure out how to do it.
The approach involving the least coding effort is that of serializing and deserializing through a BinaryFormatter.
You could define the following extension method (taken from Kilhoffer’s answer):
public static T DeepClone<T>(T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
…and then just call:
ResultList<T> clone = DeepClone(original);
One of the reasons why your ResultList class won't work with Jon Skeet's example is because it does not implement the ICloneable interface.
Implement ICloneable on all the classes that you need cloned, e.g.
public class ResultItem : ICloneable
{
public object Clone()
{
var item = new ResultItem
{
ID = ID,
Name = Name,
isLegit = isLegit
};
return item;
}
}
And also on ResultList:
public class ResultList<T> : IEnumerable<T>, ICloneable where T : ICloneable
{
public List<T> Results { get; set; }
public decimal CenterLatitude { get; set; }
public decimal CenterLongitude { get; set; }
public object Clone()
{
var list = new ResultList<T>
{
CenterLatitude = CenterLatitude,
CenterLongitude = CenterLongitude,
Results = Results.Select(x => x.Clone()).Cast<T>().ToList()
};
return list;
}
}
Then to make a deep copy of your object:
resultList.clone();
Expanding on #Georgi-it, I had to modify his code to handle properties whose type inherits List:
public static class ObjectCloner {
public static T Clone<T>(object obj, bool deep = false) where T : new() {
if (!(obj is T)) {
throw new Exception("Cloning object must match output type");
}
return (T)Clone(obj, deep);
}
public static object Clone(object obj, bool deep) {
if (obj == null) {
return null;
}
Type objType = obj.GetType();
if (objType.IsPrimitive || objType == typeof(string) || objType.GetConstructors().FirstOrDefault(x => x.GetParameters().Length == 0) == null) {
return obj;
}
List<PropertyInfo> properties = objType.GetProperties().ToList();
if (deep) {
properties.AddRange(objType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic));
}
object newObj = Activator.CreateInstance(objType);
foreach (var prop in properties) {
if (prop.GetSetMethod() != null) {
var proceed = true;
if (obj is IList) {
var listType = obj.GetType().GetProperty("Item").PropertyType;
if (prop.PropertyType == listType) {
proceed = false;
foreach (var item in obj as IList) {
object clone = Clone(item, deep);
(newObj as IList).Add(clone);
}
}
}
if (proceed) {
object propValue = prop.GetValue(obj, null);
object clone = Clone(propValue, deep);
prop.SetValue(newObj, clone, null);
}
}
}
return newObj;
}
}
Here is something that I needed and wrote, it uses reflection to copy every property (and private ones if specified)
public static class ObjectCloner
{
public static T Clone<T>(object obj, bool deep = false) where T : new()
{
if (!(obj is T))
{
throw new Exception("Cloning object must match output type");
}
return (T)Clone(obj, deep);
}
public static object Clone(object obj, bool deep)
{
if (obj == null)
{
return null;
}
Type objType = obj.GetType();
if (objType.IsPrimitive || objType == typeof(string) || objType.GetConstructors().FirstOrDefault(x => x.GetParameters().Length == 0) == null)
{
return obj;
}
List<PropertyInfo> properties = objType.GetProperties().ToList();
if (deep)
{
properties.AddRange(objType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic));
}
object newObj = Activator.CreateInstance(objType);
foreach (var prop in properties)
{
if (prop.GetSetMethod() != null)
{
object propValue = prop.GetValue(obj, null);
object clone = Clone(propValue, deep);
prop.SetValue(newObj, clone, null);
}
}
return newObj;
}
}
For deep coping of an object you can use this code:
public static T DeepCopy<T>(T obj) {
var str = Newtonsoft.Json.JsonConvert.SerializeObject(obj);
var ret = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(str);
return ret;
}
Related
i'm searching best way to instantiate and update an object that includes other objects via reflection.
i used GetInstance function but this only creating new object and overriding the object from start.
is there a way to fix the function so it will just return the class of the already updated object instance or creating new instance?
this is the function
public static object GetInstance(string strFullyQualifiedName)
{
if(strFullyQualifiedName.Split(' ').Length > 1)
{
strFullyQualifiedName = strFullyQualifiedName.Split(' ')[0];
}
Type type = Type.GetType(strFullyQualifiedName);
if (type != null)
return Activator.CreateInstance(type);
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
type = asm.GetType(strFullyQualifiedName);
if (type != null)
return Activator.CreateInstance(type);
}
return null;
}
this is the object for example
public class C
{
string C1 { get; set; }
string C2 { get; set; }
}
public class B
{
public string B1 { get; set; }
public string B2 { get; set; }
public C c { get; set; }
}
public class A
{
public string A1 { set; get; }
public string A2 { set; get; }
public B b { get; set; }
}
i built a small project to implement the issue :
static void Main(string[] args)
{
A a = new A();
a.b = new B();
a.b.B1 = "B1";
PropertyInfo propp = a.GetType().GetProperty("b");
propp.SetValue(a, GetInstance("ConsoleApp1.B"), null);
}
one way of trying to solve it was in fixing the function this way
.....
if (type != null)
{
PropertyInfo[] pLIST = o.GetType().GetProperties();
foreach(PropertyInfo p in pLIST)
{
object oo = p.GetValue(o);
if(oo != null && oo.GetType() == type)
{
return oo;
}
}
return Activator.CreateInstance(type);
}
....
static void Main(string[] args)
{
......
propp.SetValue(a, GetInstance("ConsoleApp1.B",a), null);
......
}
this function worked for me for a nested object
public static object GetInstance(string strFullyQualifiedName)
{
if(strFullyQualifiedName.Split(' ').Length > 1)
{
strFullyQualifiedName = strFullyQualifiedName.Split(' ')[0];
}
Type type = Type.GetType(strFullyQualifiedName);
if (type != null)
{
***PropertyInfo[] pLIST = o.GetType().GetProperties();
foreach(PropertyInfo p in pLIST)
{
object oo = p.GetValue(o,null);
if(oo != null && oo.GetType() == type)
{
return oo;
}
}***
return Activator.CreateInstance(type);
}
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
type = asm.GetType(strFullyQualifiedName);
if (type != null)
return Activator.CreateInstance(type);
}
return null;
}
I have multiple DTO class which require type converter. The following is one of the implementations. As you will see, I need ConvertFrom only.
public class EmployeeFilterTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (typeof(string) == sourceType)
return true;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var strVal = value as String;
if (string.IsNullOrEmpty(strVal))
return new EmployeeFilter();
EmployeeFilter employeeFilter = new EmployeeFilter();
string[] filters = strVal.Split(';');
foreach (var filter in filters)
{
var filterSplit = filter.Split(':');
if (filterSplit.Length == 2)
{
var key = filterSplit[0];
var val = filterSplit[1];
SetPropertyValue(employeeFilter, key, val);
}
}
return employeeFilter;
}
private void SetPropertyValue(EmployeeFilter employeeFilter, string key, string val)
{
var t = typeof(EmployeeFilter);
PropertyInfo[] props = t.GetProperties(BindingFlags.Instance | BindingFlags.Public);
PropertyInfo prop = props.Where(p => p.Name.Equals(key, StringComparison.CurrentCultureIgnoreCase) == true && p.CanWrite).FirstOrDefault();
if (prop != null)
prop.SetValue(employeeFilter, val);
}
}
I want to make multiple DTOs sharing the same converter in hopes of reducing code duplication as well as tests and after some researches, I have 2 problems at hand
Get the type that I want to convert in ConvertFrom method
Using Type class to initialize new object
For the first one, I don't know how to get from ITypeDescriptorContext.
For the second one, I will use the following according to this post
Type employeeType = typeof(EmployeeFilter);
object objtype = Activator.CreateInstance(employeeType);
So, how to get the type that I want to convert to?
Test case
public class testConverter
{
[Theory]
[InlineData(typeof(string), true)]
[InlineData(typeof(int), false)]
public void testCanConvertFrom(Type sourceType, bool expected)
{
//Arrange
Type randomType = typeof(Book);
Type typeGenericConverter = typeof(EmployeeFilterTypeConverter<>);
Type typeActualConverter = typeGenericConverter.MakeGenericType(randomType);
/*
1. The way of creating EmployeeFilterTypeConverter<thattype>
https://stackoverflow.com/a/266282
*/
dynamic testConverter = Activator.CreateInstance(typeActualConverter);
Mock<ITypeDescriptorContext> mockDescContext = new Mock<ITypeDescriptorContext>();
//Act
bool actual = testConverter.CanConvertFrom(mockDescContext.Object, sourceType);
//Assert
Assert.Equal(expected, actual);
}
[Theory, ClassData(typeof(TestConvertFromType1))]
/*
1. All these classdata, propertydata stuff just for passing complex objects to test
https://stackoverflow.com/a/22093968
*/
public void testConverFromType1(object value, EmployeeFilter expected)
{
//api/employee?filter=firstName:Nikhil;lastName:Doomra
//Arrange
EmployeeFilterTypeConverter<EmployeeFilter> testConverter = new EmployeeFilterTypeConverter<EmployeeFilter>();
Mock<ITypeDescriptorContext> mockDescContext = new Mock<ITypeDescriptorContext>();
//Act
EmployeeFilter actual = testConverter.ConvertFrom(mockDescContext.Object, null, value) as EmployeeFilter;
//Assert
//public static void Equal<T>(T expected, T actual);
Assert.Equal(expected, actual);
}
[Theory, ClassData(typeof(TestConvertFromType2))]
public void testConverFromType2(object value, GeoPoint expected)
{
//api/employee?filter=firstName:Nikhil;lastName:Doomra
//Arrange
EmployeeFilterTypeConverter<GeoPoint> testConverter = new EmployeeFilterTypeConverter<GeoPoint>();
Mock<ITypeDescriptorContext> mockDescContext = new Mock<ITypeDescriptorContext>();
//Act
GeoPoint actual = testConverter.ConvertFrom(mockDescContext.Object, null, value) as GeoPoint;
//Assert
//public static void Equal<T>(T expected, T actual);
Assert.Equal(expected, actual);
}
}
Test Data Model
public class TestConvertFromType1: IEnumerable<object[]>
{
private readonly List<object[]> _data = new List<object[]>
{
new object[] { "firstName:Nikhil;lastName:Doomra",
new EmployeeFilter {
FirstName = "Nikhil", LastName = "Doomra"
}},
new object[] { "firstName:Nikhil",
new EmployeeFilter {
FirstName = "Nikhil"
}}
};
public IEnumerator<object[]> GetEnumerator()
{ return _data.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator()
{ return GetEnumerator(); }
}
public class TestConvertFromType2 : IEnumerable<object[]>
{
private readonly List<object[]> _data = new List<object[]>
{
new object[] { "Latitude:12.345;Longitude:342.12",
new GeoPoint {
Latitude = 12.345, Longitude = 342.12
}},
new object[] { "Latitude:11.234;Longitude:345.12",
new GeoPoint {
Latitude = 11.234, Longitude = 345.12
}}
};
public IEnumerator<object[]> GetEnumerator()
{ return _data.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator()
{ return GetEnumerator(); }
}
Generic Converter
public class EmployeeFilterTypeConverter<T> : TypeConverter where T: new()
/*
1. You can't declare T type = new T() without this constraint
Evidently it is because compiler can't say what is the type!
https://stackoverflow.com/a/29345294
*/
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (typeof(string) == sourceType)
return true;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var strVal = value as String;
if (string.IsNullOrEmpty(strVal))
return new EmployeeFilter();
T converTo = new T();
string[] filters = strVal.Split(';');
foreach (var filter in filters)
{
string[] filterSplit = filter.Split(':');
if (filterSplit.Length == 2)
{
string key = filterSplit[0];
string val = filterSplit[1];
SetPropertyValue(converTo, key, val);
}
}
return converTo;
}
private void SetPropertyValue(T converTo, string key, string val)
{
Type t = typeof(T);
PropertyInfo[] props = t.GetProperties(BindingFlags.Instance | BindingFlags.Public);
PropertyInfo prop = props.Where(p => p.Name.Equals(key, StringComparison.CurrentCultureIgnoreCase) == true && p.CanWrite).FirstOrDefault();
if (prop is null) return;
prop.SetValue(converTo, TypeDescriptor.GetConverter(prop.PropertyType).ConvertFrom(val));
/*
1. Problem: val is a string and if your target property is non-string there is
a contradiction.
The following link offers a solution
https://stackoverflow.com/a/2380483
*/
}
}
EmployeeFilter
[TypeConverter(typeof(EmployeeFilterTypeConverter<EmployeeFilter>))]
public class EmployeeFilter: IEquatable<EmployeeFilter>
{
/*
1. As you can see, the DTO omitted the
a. ID
b. DOB
*/
public string FirstName { get; set; }
public string LastName { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public DateTime? DOJ { get; set; }
public bool Equals(EmployeeFilter other)
{
/*
1. You need to put parenthesis around (this.FirstName == other.FirstName)
2. https://learn.microsoft.com/en-us/dotnet/api/system.iequatable-1?view=net-5.0
*/
return (this.FirstName == other.FirstName) &&
(this.LastName == other.LastName) &&
(this.Street == other.Street) &&
(this.City == other.City) &&
(this.State == other.State) &&
(this.ZipCode == other.ZipCode) &&
(this.DOJ == other.DOJ);
}
}
GeoPoint
public class GeoPoint: IEquatable<GeoPoint>
{
public double Latitude { get; set; }
public double Longitude { get; set; }
public static bool TryParse(string s, out GeoPoint result)
{
result = null;
var parts = s.Split(',');
if (parts.Length != 2)
{
return false;
}
double latitude, longitude;
if (double.TryParse(parts[0], out latitude) &&
double.TryParse(parts[1], out longitude))
{
result = new GeoPoint() { Longitude = longitude, Latitude = latitude };
return true;
}
return false;
}
public bool Equals(GeoPoint other)
{
return (this.Latitude == other.Latitude) && (this.Longitude == other.Longitude);
}
Edit: Added model classes
Assuming we have a JSON object similar to:
{
'12345': 'text string',
'rel': 'myResource'
}
Constructing a DataContract to map to this type seems fairly simple such as:
[DataContract]
MyResource
{
[DataMember(Name = "12345")]
public string SpecialValue { get; set; }
[DataMember(Name = "rel")]
public string Rel { get; set; }
}
Now the problem arrives that the name of the property is variable so it not guaranteed to be '12345'. Since this variable cannot be properly mapped using attributes it won't get picked up when using DataContractJsonSerializer.
If I change the class to support IExtensibleDataObject, I can get the value portion but not the property name which is a problem. I'm looking to maintain this value during deserialization/serialization in order to be able to send the information on a return request. I'm not looking to change over to using Json.NET to solve this problem as I want to know if it is possible in some form without resorting to an external dependency.
It's a little ugly, but it turns out you can use an IDataContractSurrogate to deserialize the class with the variably named property into a Dictionary<string, object> and then copy the values from the dictionary into your class. Of course, you will need to add another property to your class to hold the name of the "special" property.
Here is an example surrogate that I was able to get working:
class MyDataContractSurrogate : IDataContractSurrogate
{
public Type GetDataContractType(Type type)
{
if (type == typeof(MyResource))
{
return typeof(Dictionary<string, object>);
}
return type;
}
public object GetDeserializedObject(object obj, Type targetType)
{
if (obj.GetType() == typeof(Dictionary<string, object>) &&
targetType == typeof(MyResource))
{
Dictionary<string, object> dict = (Dictionary<string, object>)obj;
MyResource mr = new MyResource();
foreach (PropertyInfo prop in GetInterestingProperties(typeof(MyResource)))
{
DataMemberAttribute att = prop.GetCustomAttribute<DataMemberAttribute>();
object value;
if (dict.TryGetValue(att.Name, out value))
{
prop.SetValue(mr, value);
dict.Remove(att.Name);
}
}
// should only be one property left in the dictionary
if (dict.Count > 0)
{
var kvp = dict.First();
mr.SpecialName = kvp.Key;
mr.SpecialValue = (string)kvp.Value;
}
return mr;
}
return obj;
}
public object GetObjectToSerialize(object obj, Type targetType)
{
if (obj.GetType() == typeof(MyResource) &&
targetType == typeof(Dictionary<string, object>))
{
MyResource mr = (MyResource)obj;
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add(mr.SpecialName, mr.SpecialValue);
foreach (PropertyInfo prop in GetInterestingProperties(typeof(MyResource)))
{
DataMemberAttribute att = prop.GetCustomAttribute<DataMemberAttribute>();
dict.Add(att.Name, prop.GetValue(mr));
}
return dict;
}
return obj;
}
private IEnumerable<PropertyInfo> GetInterestingProperties(Type type)
{
return type.GetProperties().Where(p => p.CanRead && p.CanWrite &&
p.GetCustomAttribute<DataMemberAttribute>() != null);
}
// ------- The rest of these methods are not needed -------
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
throw new NotImplementedException();
}
public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
{
throw new NotImplementedException();
}
public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
{
throw new NotImplementedException();
}
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
throw new NotImplementedException();
}
public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
{
throw new NotImplementedException();
}
}
To use the surrogate, you'll need to create an instance of DataContractJsonSerializerSettings and pass it to the DataContractJsonSerializer with the following properties set. Note that since we require the UseSimpleDictionaryFormat setting, this solution will only work with .Net 4.5 or later.
var settings = new DataContractJsonSerializerSettings();
settings.DataContractSurrogate = new MyDataContractSurrogate();
settings.KnownTypes = new List<Type> { typeof(Dictionary<string, object>) };
settings.UseSimpleDictionaryFormat = true;
Note also that in your class you should NOT mark the "special" properties with a [DataMember] attribute, since they are handled specially in the surrogate.
[DataContract]
class MyResource
{
// Don't mark these with [DataMember]
public string SpecialName { get; set; }
public string SpecialValue { get; set; }
[DataMember(Name = "rel")]
public string Rel { get; set; }
}
Here is a demo:
class Program
{
static void Main(string[] args)
{
string json = #"
{
""12345"": ""text string"",
""rel"": ""myResource""
}";
var settings = new DataContractJsonSerializerSettings();
settings.DataContractSurrogate = new MyDataContractSurrogate();
settings.KnownTypes = new List<Type> { typeof(Dictionary<string, object>) };
settings.UseSimpleDictionaryFormat = true;
MyResource mr = Deserialize<MyResource>(json, settings);
Console.WriteLine("Special name: " + mr.SpecialName);
Console.WriteLine("Special value: " + mr.SpecialValue);
Console.WriteLine("Rel: " + mr.Rel);
Console.WriteLine();
json = Serialize(mr, settings);
Console.WriteLine(json);
}
public static T Deserialize<T>(string json, DataContractJsonSerializerSettings settings = null)
{
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
{
if (settings == null) settings = GetDefaultSerializerSettings();
var ser = new DataContractJsonSerializer(typeof(T), settings);
return (T)ser.ReadObject(ms);
}
}
public static string Serialize(object obj, DataContractJsonSerializerSettings settings = null)
{
using (MemoryStream ms = new MemoryStream())
{
if (settings == null) settings = GetDefaultSerializerSettings();
var ser = new DataContractJsonSerializer(obj.GetType(), settings);
ser.WriteObject(ms, obj);
return Encoding.UTF8.GetString(ms.ToArray());
}
}
}
Output:
Special name: 12345
Special value: text string
Rel: myResource
{"12345":"text string","rel":"myResource"}
So I am using reflection to loop through the properties of one object and populating the values on a different object with properties of the same name. This works great but the problem comes when the property type is a collection. I want to be able to loop through each of the objects in the source collection and populate the same list with objects in the source collection.
public class SourceMessage
{
public string Name { get; set; }
public int Version { get; set; }
public IList<ValueDefinition> Values { get; set; }
}
public class ValueDefinition
{
public string Name { get; set; }
public string Value { get; set; }
}
public class TargetObject
{
public TargetObject()
{
Values = new List<TargetValueDefinition>();
}
public string Name { get; set; }
public int Version { get; set; }
public IList<TargetValueDefinition> Values { get; set; }
}
public class TargetValueDefinition
{
public string Name { get; set; }
public string Value { get; set; }
}
Then I use Reflection to populate the target from the source.
public static void PopulateFromMessage<T, TS>(ref T targetEntity, TS message)
{
var sourceType = typeof(TS);
var targetType = typeof(T);
foreach (var targetPropInfo in targetType.GetProperties())
{
if (sourceType.GetProperty(targetPropInfo.Name) != null)
{
var obj = sourceType.GetProperty(targetPropInfo.Name);
if (obj.PropertyType.Namespace == "System.Collections.Generic")
{
//var x = targetType.GetProperty(targetPropInfo.Name);
//PopulateFromMessage(ref x, sourceType.GetProperty(targetPropInfo.Name));
continue;
}
targetPropInfo.SetValue(targetEntity, sourceType.GetProperty(targetPropInfo.Name).GetValue(message), null);
}
}
}
So calling this would be like this:
private void DenormalizeMessage(SourceMessage message)
{
var newTargetObject = new TargetObject();
PopulateFromMessage(ref newTargetObject , message);
}
I can identify when the property is a collection but am uncertain of how to create new TargetValueDefinitions and populate them with the values from ValueDefinitions. In the end it is pretty much a copy of the SourceMessage in the form of a TargetObject.
This all stems from receiving messages and transforming them into objects with the same property names.
If your problem is iterating through items contained inside a single property when it is a collection, then the key would be to read the property value into a dynamic variable and not an object variable that is by default, this way you could use a foreach for it.
dynamic propVal = inputProperty.GetValue(item);
foreach (var subItem in propVal)
{
//do your stuff
}
Disclaimer: This is extremely unsafe to do and makes a lot of assumptions but it should puth you on the right path.
Change you method to this:
public static void PopulateFromMessage<T, TS>(T targetEntity, TS message)
{
var sourceType = typeof (TS);
var targetType = typeof (T);
foreach (var targetPropInfo in targetType.GetProperties())
{
if (targetPropInfo.PropertyType.IsGenericType)
{
if (targetPropInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>))
{
var originalList = sourceType.GetProperty(targetPropInfo.Name).GetValue(message) as IList;
if (originalList != null)
{
var argumentType = targetPropInfo.PropertyType.GetGenericArguments();
var listType = typeof (List<>);
var concreteType = listType.MakeGenericType(argumentType);
var newList = Activator.CreateInstance(concreteType) as IList;
foreach (var original in originalList)
{
var targetValue = Activator.CreateInstance(argumentType[0]);
// do this yourself. Here we're converting ValueDefinition to TargetValueDefinition
// targetValue.Fill(original);
}
targetPropInfo.SetValue(targetEntity, newList);
}
}
}
else
{
if (sourceType.GetProperty(targetPropInfo.Name) != null)
{
var obj = sourceType.GetProperty(targetPropInfo.Name);
if (obj.PropertyType.Namespace == "System.Collections.Generic")
{
//var x = targetType.GetProperty(targetPropInfo.Name);
//PopulateFromMessage(ref x, sourceType.GetProperty(targetPropInfo.Name));
continue;
}
targetPropInfo.SetValue(targetEntity, sourceType.GetProperty(targetPropInfo.Name).GetValue(message), null);
}
}
}
}
You should create a interface for each class (implement the methods and properties on interface) and implement it in each class. After, in function PopulateFromMessage should specify the interface allowed in method, with this you can use directly the properties of class with T and TS generic types.
Good morning.
I need to serialize virtually any kind of serializable object to an *.INI file.
I'm trying to implement IFormatter, where void Serialize(Stream, object) method gets property info from object instance and then writes key/value for primitive types or sections + key/values for complex types.
Take this code as example of objects to serialize.
[Serializable]
public class EmployeeList
{
#region Properties
public List<Employee> Employees { get; private set; }
public string Banana { get; set; }
#endregion
#region Constructors
public EmployeeList()
{
Employees = new List<Employee>();
}
#endregion
#region Public Methods
public void Serialize()
{
IFormatter formatter = new IniFormatter();
using (FileStream outputStream = File.Create("employees.ini"))
{
try
{
formatter.Serialize(outputStream, this);
}
catch (SerializationException ex)
{
//...
}
}
}
#endregion
}
[Serializable]
public class Employee
{
#region Properties
public string Name
{
get;
set;
}
public Gender Gender
{
get;
set;
}
public List<Role> Roles
{
get;
set;
}
#endregion
#region Constructors
public Employee()
{
}
#endregion
}
[Serializable]
public enum Gender
{
Male,
Female,
Other
}
[Serializable]
public class Role
{
public string Name
{
get;
set;
}
public int Value
{
get;
set;
}
#region Constructors
public Role()
{
}
#endregion
}
Well, everything seemed trivial since I tried to acquire properties name and values by reflection; I continuously get an System.Reflection.TargetParameterCountException when trying to reflect List<Employee> (see method pi.GetValue(obj, null);, where pi is PropertyInfo).
This is the code of my IniFormatter:
public class IniFormatter : IFormatter
{
#region Properties
public ISurrogateSelector SurrogateSelector { get; set; }
public SerializationBinder Binder { get; set; }
public StreamingContext Context { get; set; }
#endregion
#region Constructors
public IniFormatter()
{
Context = new StreamingContext(StreamingContextStates.All);
}
#endregion
#region IFormatter Members
public object Deserialize(Stream serializationStream)
{
throw new NotImplementedException();
}
public void Serialize(Stream serializationStream, object graph)
{
var propertyInfos = graph.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
StreamWriter sw = new StreamWriter(serializationStream);
foreach (var propertyInfo in propertyInfos)
{
if (!propertyInfo.CanRead)
{
continue;
}
if (Attribute.IsDefined(propertyInfo, typeof (NonSerializedAttribute)))
{
continue;
}
if (propertyInfo.PropertyType.IsPrimitive)
{
sw.WriteLine("{0}={1}", propertyInfo.Name, propertyInfo.GetValue(graph, null));
}
//object/complex types need to recursively call this method until the end of the tree is reached
else
{
var complexType = GetPropertyValue(graph, propertyInfo.Name);
Serialize(serializationStream, complexType);
}
}
sw.Close();
}
#endregion
public static object GetPropertyValue(object sourceObject, string propertyName)
{
if (sourceObject == null)
{
return null;
}
object obj = sourceObject;
// Split property name to parts (propertyName could be hierarchical, like obj.subobj.subobj.property
string[] propertyNameParts = propertyName.Split('.');
foreach (string propertyNamePart in propertyNameParts)
{
if (obj == null)
{
return null;
}
// propertyNamePart could contain reference to specific
// element (by index) inside a collection
if (!propertyNamePart.Contains("["))
{
PropertyInfo pi = obj.GetType().GetProperty(propertyNamePart, BindingFlags.Public | BindingFlags.Instance);
if (pi == null)
{
return null;
}
obj = pi.GetValue(obj, null);
}
else
{
// propertyNamePart is a reference to specific element
// (by index) inside a collection
// like AggregatedCollection[123]
// get collection name and element index
int indexStart = propertyNamePart.IndexOf("[") + 1;
string collectionPropertyName = propertyNamePart.Substring(0, indexStart - 1);
int collectionElementIndex = Int32.Parse(propertyNamePart.Substring(indexStart, propertyNamePart.Length - indexStart - 1));
// get collection object
PropertyInfo pi = obj.GetType().GetProperty(collectionPropertyName, BindingFlags.Public | BindingFlags.Instance);
if (pi == null)
{
return null;
}
object unknownCollection = pi.GetValue(obj, null);
// try to process the collection as array
if (unknownCollection.GetType().IsArray)
{
object[] collectionAsArray = unknownCollection as Array[];
obj = collectionAsArray[collectionElementIndex];
}
else
{
// try to process the collection as IList
IList collectionAsList = unknownCollection as IList;
if (collectionAsList != null)
{
obj = collectionAsList[collectionElementIndex];
}
else
{
// ??? Unsupported collection type
}
}
}
}
return obj;
}
}
I run my tests from a console app, doing this:
Employee employee = new Employee {Name = "Nando", Gender = Gender.Male, Roles = new List<Role> {new Role {Name = "CEO", Value = 1}}};
EmployeeList employeeList = new EmployeeList();
employeeList.Banana = "It's delicious!";
employeeList.Employees.Add(employee);
employeeList.Serialize();
I can get the Banana property value (sorry for property name, when I get angry my naming conventions goes crazy), and nothing else.
This is quite a "solve my problem" question, I know, but I cannot find sources where to understand I can completely reflect an object an deal with its properties names and values.
So I ask help from skilled developers to point me out to some sources, I'm going crazy. :-) Thanks a lot,
Nando