.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)
Picture
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. Microsoft MVP
Create New Account
Article Discussion: .NET Reflect - Copy Class Properties
Robbe Morris posted at Tuesday, October 03, 2006 1:48 PM
reply
.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

reply
Want a suggestion
Baishakhi Banerjee replied to Robbe Morris at Tuesday, October 03, 2006 3:18 PM
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.
reply
Simply download the ado.net code generator for sql server
Robbe Morris replied to Baishakhi Banerjee at Tuesday, October 03, 2006 3:18 PM

It does all of that.  You just click the button and it generates all the code.  Then tweak the code as the article describes where necessary.

http://www.eggheadcafe.com/articles/adonet_source_code_generator.asp

I'd also download this web services sample.  It shows business layer code that

calls the same methods to auto populate classes and how to auto validate them.

http://www.eggheadcafe.com/tutorials/aspnet/af62d138-fa2f-491e-901f-d36a82f107e8/net-web-services--excep.aspx

 

reply
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.
reply
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;
}
reply
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

reply
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.

reply
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 );
}
reply
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.
reply
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/
reply