Sunday, August 14, 2011

Deep object cloning (at least for ORM uses) sans serialization

public static class ObjectCloner
{

    public static object Clone(this object root)
    {
        Type rootType = root.GetType();
        object clone = Activator.CreateInstance(rootType);
 
        foreach (PropertyInfo pi in rootType.GetProperties())
        {
            bool isCollection = pi.PropertyType.IsGenericType && typeof(IEnumerable).IsAssignableFrom(pi.PropertyType);
            if (!isCollection)
            {
                object transientVal = rootType.InvokeMember(pi.Name, BindingFlags.GetProperty, null, root, new object[] { });
                rootType.InvokeMember(pi.Name, BindingFlags.SetProperty, null, clone, new object[] { transientVal });
            }
            else
            {                    
                var colOrig = (IList)rootType.InvokeMember(pi.Name, BindingFlags.GetProperty, null, root, new object[] { });
                object clonedList = Activator.CreateInstance(colOrig.GetType());
                rootType.InvokeMember(pi.Name, BindingFlags.SetProperty, null, clone, new object[] { clonedList });


                CloneCollection(root, (IList)colOrig, clone, (IList)clonedList);

            }
        }
        return clone;
    }

    private static void CloneCollection(object origParent, IList origList, object cloneParent, IList cloneList)
    {            
        foreach (object item in origList)
        {
            object cloneItem = item.Clone();

            foreach(PropertyInfo pi in cloneItem.GetType().GetProperties().Where(x => x.PropertyType == origParent.GetType()))
            {
                object val = cloneItem.GetType().InvokeMember(pi.Name, BindingFlags.GetProperty, null, cloneItem, new object[] { });

                if (object.ReferenceEquals(val,origParent))
                {
                    // point it to its new parent
                    cloneItem.GetType().InvokeMember(pi.Name, BindingFlags.SetProperty, null, cloneItem, new object[] { cloneParent });
                }
            }

            cloneList.Add(cloneItem);
        }

    }

}


To use:
[TestMethod]
public void Can_do_deep_clone()
{
    Question orig = PopulateQuestion();

    Question clone = (Question)orig.Clone();



    Assert.AreNotSame(orig, clone);
    Assert.AreNotSame(orig.Answers, clone.Answers);
    
    Assert.AreSame(orig, orig.Answers[0].Question);
    Assert.AreSame(clone, clone.Answers[0].Question);
    
    Assert.AreNotSame(orig.Answers[0], clone.Answers[0]);
    Assert.AreNotSame(orig.Answers[0].Question, clone.Answers[0].Question);
    Assert.AreNotSame(orig.Answers[1].Question, clone.Answers[1].Question);
                
    Assert.AreNotSame(orig.Answers[1].Comments, clone.Answers[1].Comments);
    Assert.AreNotSame(orig.Answers[1].Comments[0], clone.Answers[1].Comments[0]);

    Assert.AreSame(orig.Answers[1], orig.Answers[1].Comments[0].Answer);
    Assert.AreSame(clone.Answers[1], clone.Answers[1].Comments[0].Answer);

    Assert.AreEqual(orig.Text, clone.Text);
    Assert.AreEqual(orig.Answers.Count, clone.Answers.Count);
    Assert.AreEqual(orig.Answers[0].Text, clone.Answers[0].Text);
    Assert.AreEqual(orig.Answers[1].Comments[0].Text, clone.Answers[1].Comments[0].Text);


}

private static Question PopulateQuestion()
{
    var importantQuestion = new Question { Text = "The answer to life", Poster = "Boy", Answers = new List<Answer>(), Comments = new List<QuestionComment>() };
    var answerA = new Answer { Question = importantQuestion, Text = "42", Poster = "John", Comments = new List<AnswerComment>() };
    var answerB = new Answer { Question = importantQuestion, Text = "143", Poster = "Paul", Comments = new List<AnswerComment>() };
    var answerC = new Answer { Question = importantQuestion, Text = "888", Poster = "Elton", Comments = new List<AnswerComment>() };
    importantQuestion.Answers.Add(answerA);
    importantQuestion.Answers.Add(answerB);
    importantQuestion.Answers.Add(answerC);

    var commentToImportantQuestion = new QuestionComment { Question = importantQuestion, Text = "Is There?", Poster = "George" };
    importantQuestion.Comments.Add(commentToImportantQuestion);
    var commentToAnswerB = new AnswerComment { Answer = answerB, Text = "Isn't the answer is 7 times 6?", Poster = "Ringo" };
    answerB.Comments.Add(commentToAnswerB);
    return importantQuestion;
}


Object structure sample:
public class Question
{
    public virtual int QuestionId { get; set; }
    public virtual string Text { get; set; }
    public virtual string Poster { get; set; }

    public virtual IList<QuestionComment> Comments { get; set; }
    public virtual IList<Answer> Answers{ get; set; }

   
    public virtual byte[] RowVersion { get; set; }
}


public class QuestionComment
{
    public virtual Question Question { get; set; }        

    public virtual int QuestionCommentId { get; set; }
    public virtual string Text { get; set; }
    public virtual string Poster { get; set; }
}



public class Answer
{
    public virtual Question Question { get; set; }

    public virtual int AnswerId { get; set; }
    public virtual string Text { get; set; }
    public virtual string Poster { get; set; }

    public virtual IList<AnswerComment> Comments { get; set; }
    
}



public class AnswerComment
{
    public virtual Answer Answer { get; set; }

    public virtual int AnswerCommentId { get; set; }
    public virtual string Text { get; set; }
    public virtual string Poster { get; set; }
}

No comments:

Post a Comment