.NET Reflection - Copy Class Properties
By Robbe Morris
This code sample demonstrates how to copy class properties from one class to another even if they are not the same type. It also demonstrates how to validate a class's required properties dynamically.
Both of these can increase your coding productivity especially when dealing with web service versions of your business classes.
| PropertyHandler.cs |
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Diagnostics;
namespace MyApplication
{
public class PropertyHandler
{
#region Set Properties
public static void SetProperties(PropertyInfo[] fromFields,
PropertyInfo[] toFields,
object fromRecord,
object toRecord)
{
PropertyInfo fromField = null;
PropertyInfo toField = null;
try
{
if (fromFields == null)
{
return;
}
if (toFields == null)
{
return;
}
for (int f = 0; f < fromFields.Length; f++)
{
fromField = (PropertyInfo)fromFields[f];
for (int t = 0; t < toFields.Length; t++)
{
toField = (PropertyInfo)toFields[t];
if (fromField.Name != toField.Name)
{
continue;
}
toField.SetValue(toRecord,
fromField.GetValue(fromRecord, null),
null);
break;
}
}
}
catch (Exception)
{
throw;
}
}
#endregion
#region Set Properties
public static void SetProperties(PropertyInfo[] fromFields,
object fromRecord,
object toRecord)
{
PropertyInfo fromField = null;
try
{
if (fromFields == null)
{
return;
}
for (int f = 0; f < fromFields.Length; f++)
{
fromField = (PropertyInfo)fromFields[f];
fromField.SetValue(toRecord,
fromField.GetValue(fromRecord, null),
null);
}
}
catch (Exception)
{
throw;
}
}
#endregion
}
}
|
| PropertyHandler Sample For Identical Classes |
MyClass record = new MyClass();
MyClass newRecord = new MyClass();
PropertyInfo[] fromFields = null;
fromFields = typeof(MyClass).GetProperties();
PropertyHandler.SetProperties(fromFields, record, newRecord);
|
| PropertyHandler Sample For Similar Classes |
MyClass record = new MyClass();
MyOtherClass newRecord = new MyOtherClass();
PropertyInfo[] fromFields = null;
PropertyInfo[] toFields = null;
fromFields = typeof(MyClass).GetProperties();
toFields = typeof(MyOtherClass).GetProperties();
PropertyHandler.SetProperties(fromFields,toFields,record, newRecord);
|
| Self Validation Methods |
// Some of the code below relies on runtime reflection. Certain aspects
// of reflection are detrimental to performance. Where possible, you
// can create static instances of the reflection results. It will give
// the power without the overhead of using reflection.
// Here is a sample business class layer self validation method.
private bool ValidateSave(CellAlignment record)
{
object[,] properties = null;
List<string> returnMessages = new List<string>();
try
{
// Use the .GetFields() method mentioned later in this sample.
properties = this.GetFields(this.GetType());
if (!record.ValidateRequiredProperties(properties,
returnMessages))
{
for (int i = 0; i < returnMessages.Count; i++)
{
Debug.WriteLine(returnMessages[i]);
}
return false;
}
}
catch (Exception) { throw;}
return true;
}
// This code was extracted from the ADO.NET Code Generator mentioned
// above.
// Here is a sample property to demonstrate how to use the
// ColumnAttributes class. It sets this as being required
// and tells the validator that it is an int data type.
private int cellAlignmentID = 0;
[ColumnAttributes("CellAlignmentID",true,"int")]
public int CellAlignmentID
{
get
{
return cellAlignmentID;
}
set
{
if (value != cellAlignmentID)
{
cellAlignmentID = valuue;
}
}
}
// The ColumnAttributes class itself.
[AttributeUsage(AttributeTargets.Property,AllowMultiple = true)]
public sealed class ColumnAttributes : System.Attribute
{
private string columnName;
private bool isRequired;
private string propertyType;
public string ColumnName
{
get { return columnName; }
}
public bool IsRequired
{
get { return isRequired; }
}
public string PropertyType
{
get { return propertyType; }
}
public ColumnAttributes(string columnNameValue,
bool isRequiredValue,
string propertyTypeValue)
{
columnName = columnNameValue;
isRequired = isRequiredValue;
propertyType = propertyTypeValue;
}
}
// Here is a method we can use to get an array of
// PropertyInfo objects as well as custom attributes.
// Make sure every class has this in method available
// to run on itself. The ADO.NET Code Generator has
// this apart of the CustomAttributes.cs that all
// DataObjects inherit.
public object[,] GetFields(Type t)
{
PropertyInfo[] fields = t.GetProperties();
PropertyInfo field;
Attribute[] attributes;
object[,] structureInfo = new object[fields.Length,2];
try
{
for(int i =0;i<fields.Length;i++)
{
field = fields[i];
attributes = Attribute.GetCustomAttributes(field,
typeof(DataObjects.Tables.ColumnAttributes),
false);
structureInfo[i,0] = field;
structureInfo[i,1] = attributes;
}
}
catch (Exception) { throw; }
return structureInfo;
}
public bool ValidateRequiredProperties(object[,] properties,
List<string> returnMessages)
{
bool returnValue = true;
PropertyInfo property;
Attribute[] attributes;
ColumnAttributes columnAttribute = null;
try
{
if (properties == null)
{
throw new Exception("Please pass in the results of this.GetFields().");
}
if (returnMessages != null)
{
returnMessages.Clear();
}
for (int i = 0; i <= properties.GetUpperBound(0); i++)
{
property = (PropertyInfo)properties[i, 0];
attributes = (Attribute[])properties[i, 1];
foreach (Attribute attribute in attributes)
{
columnAttribute = (ColumnAttributes)attribute;
if (!columnAttribute.IsRequired)
{
continue;
}
// Debug.WriteLine(columnAttribute.ColumnName);
// Debug.WriteLine(property.GetValue(this,null));
if (!this.ValidateRequiredPropertyInfo(columnAttribute,
property))
{
returnValue = false;
if (returnMessages != null)
{
returnMessages.Add(columnAttribute.ColumnName + " is required.");
}
}
}
}
}
catch (Exception) { throw;}
return returnValue;
}
public bool ValidateRequiredProperties(object[,] properties)
{
List<string> returnMessages = null;
try
{
return ValidateRequiredProperties(properties,
returnMessages);
}
catch (Exception) { throw; }
}
public bool ValidateRequiredPropertyInfo(ColumnAttributes columnAttribute,
PropertyInfo property)
{
try
{
switch (columnAttribute.PropertyType.ToLower())
{
case "int":
if ((int)property.GetValue(this,
null) == 0)
{
return false;
}
break;
case "int16":
if ((Int16)property.GetValue(this,
null) == 0)
{
return false;
}
break;
case "int32":
if ((Int32)property.GetValue(this,
null) == 0)
{
return false;
}
break;
case "int64":
if ((Int64)property.GetValue(this,
null) == 0)
{
return false;
}
break;
case "uint":
if ((uint)property.GetValue(this,
null) == 0)
{
return false;
}
break;
case "uint16":
if ((UInt16)property.GetValue(this,
null) == 0)
{
return false;
}
break;
case "uint32":
if ((UInt32)property.GetValue(this,
null) == 0)
{
return false;
}
break;
case "uint64":
if ((UInt64)property.GetValue(this,
null) == 0)
{
return false;
}
break;
case "double":
if ((double)property.GetValue(this,
null) == 0)
{
return false;
}
break;
case "float":
if ((float)property.GetValue(this,
null) == 0)
{
return false;
}
break;
case "single":
if ((Single)property.GetValue(this,
null) == 0)
{
return false;
}
break;
case "string":
if ((string)property.GetValue(this,
null) == String.Empty)
{
return false;
}
break;
case "guid":
if ((Guid)property.GetValue(this,
null) == Guid.Empty)
{
return false;
}
break;
default:
if (property.GetValue(this,
null) == null)
{
return false;
}
break;
}
}
catch (Exception) { throw; }
return true;
}
|
Popularity (17169 Views)
 |
| Biography - Robbe Morris |
| Robbe has been a Microsoft MVP in C# since 2004. He is also the co-founder of EggHeadCafe.com which provides .NET articles, book reviews, software reviews, and software download and purchase advice. Robbe also loves to scuba dive and go deep sea fishing in the Florida Keys or off the coast of Daytona Beach. |
 |
|
|
Article Discussion: .NET Reflect - Copy Class Properties
.NET Reflect - Copy Class Properties
Marc Gravell replied
to Robbe Morris at Tuesday, October 03, 2006 3:18 PM
Interesting read; one comment: it is a shame it uses PropertyInfo and not PropertyDescriptor; this would make it more usable for dynamic (runtime) properties, and would also allow use of .ShouldSerializeValue() to remove all of ValidateRequiredPropertyInfo (including support for DefaultValueAttribute). Obviously, when writing values and if storing only non-defaults you may need to .ResetValue() the ones that don't have entries.
This would still allow full reflective usage via TypeDescriptor.
Marc
Want a suggestion
I have a question. I am creating a web form that has about 115 fields in it values of which need to be assigned, inserted,updated or deleted from/into an SQL server database. Writing functions in which values from all controls are passed as parameters in stored procedures seems a tedious jobs. I was thinking in terms of creating an class, since the form is related to master database updation, thus creating a class with properties sames as the master table fields. But i do have problem how do i get the list of properties of a particular object and how to mark fields as mandatory in the class and how to map appropriate controls to appropriate object properties. Pls help.
Simply download the ado.net code generator for sql server
Robbe Morris replied
to Baishakhi Banerjee at Tuesday, October 03, 2006 3:18 PM
Optimization with generics
paul mcmanus replied
to Robbe Morris at Tuesday, October 03, 2006 3:18 PM
Hi there,
I liked your methods to update an object by reflection. I think I have improved it by adding generics:
public static T SetProperties<T>(T fromRecord, T toRecord)
{
foreach (PropertyInfo field in typeof (T).GetProperties())
{
field.SetValue(toRecord, field.GetValue(fromRecord, null), null);
}
return toRecord;
}
and of course:
public static T SetProperties<T, U>(PropertyInfo[] fromFields, PropertyInfo[] toFields, U fromRecord, T toRecord)
{
foreach (PropertyInfo fromField in fromFields)
{
foreach (PropertyInfo toField in toFields)
{
if (fromField.Name == toField.Name)
{
toField.SetValue(toRecord, fromField.GetValue(fromRecord, null), null);
break;
}
}
}
return toRecord;
}
Hope you like it.
further...
paul mcmanus replied
to Robbe Morris at Tuesday, October 03, 2006 3:18 PM
My second mathod should have read:
public static T SetProperties<T, U>(U fromRecord, T toRecord)
{
foreach (PropertyInfo fromField in typeof(U).GetProperties())
{
if (fromField.Name != "Id")
{
foreach (PropertyInfo toField in typeof(T).GetProperties())
{
if (fromField.Name == toField.Name)
{
toField.SetValue(toRecord, fromField.GetValue(fromRecord, null), null);
break;
}
}
}
}
return toRecord;
}
Faster copy properties
Nadeem Douba replied
to Robbe Morris at Tuesday, October 03, 2006 3:18 PM
Hi,
I know this doesn't belong here, but thanks to your code I was able to create something similar in vb.net. It should be a bit faster than what you have implemented since there is no need for a nested for loop.
Cheers,
Nadeem
Imports System
Imports System.Collections.Generic
Imports System.Reflection
Imports System.Text
Imports System.Diagnostics
Module ClassUtils
Public Sub CopyProperties(ByRef dst As Object, ByRef src As Object)
Dim srcProperties() As PropertyInfo = src.GetType.GetProperties()
Dim dstType = dst.GetType
Try
If srcProperties Is Nothing Or dstType.GetProperties Is Nothing Then
Return
End If
For Each srcProperty As PropertyInfo In srcProperties
Dim dstProperty As PropertyInfo = dstType.GetProperty(srcProperty.Name)
If dstProperty IsNot Nothing Then
If dstProperty.PropertyType.IsAssignableFrom(srcProperty.PropertyType) = True Then
dstProperty.SetValue(dst, srcProperty.GetValue(src, Nothing), Nothing)
End If
End If
Next
Catch ex As Exception
End Try
End Sub
End Module

You'd better test that first
Robbe Morris replied
to Nadeem Douba at Tuesday, October 03, 2006 3:18 PM
You are making calls to .GetProperty() again and again. My code
sample keeps static copies of this so it only needs to be called
once during the lifetime of the app.
Jonathan replied
to Robbe Morris at Tuesday, October 03, 2006 3:18 PM
I've done the following for the class I wanted to use this in. I'm not sure if it's the best approach, but it's good enough for my requirements. I figured it might help someone else if I added it here.
private static readonly PropertyInfo[] FromFields = GetSettableProperties();
private static PropertyInfo[] GetSettableProperties()
{
var fromFields = typeof ( Asset ).GetProperties( BindingFlags.Public | BindingFlags.Instance );
var list = new List<PropertyInfo>(fromFields.Length);
foreach ( PropertyInfo info in fromFields )
{
if ( info.CanWrite )
{
list.Add( info );
}
}
return list.ToArray();
}
public void UpdateValues( Asset asset )
{
PropertyHandler.SetProperties( FromFields, asset, this );
}
sam replied
to paul mcmanus at Tuesday, October 03, 2006 3:18 PM
I liked your approach but it is not working with Property which is type of some collection like list. Could you let me know how to implement it.
sam replied
to paul mcmanus at Tuesday, October 03, 2006 3:18 PM
I liked your method but it is not working with Property of some typeof collection like List or Array. Could you let me know to implement it/