views:

260

answers:

3

Say I have this class Myclass that contains this method:

 public class MyClass
    {
        public int MyProperty { get; set; }

        public int MySecondProperty { get; set; }

        public MyOtherClass subClass { get; set; }

        public object clone<T>(object original, T emptyObj)
        {

            FieldInfo[] fis = this.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);


            object tempMyClass = Activator.CreateInstance(typeof(T));


            foreach (FieldInfo fi in fis)
            {
                if (fi.FieldType.Namespace != original.GetType().Namespace)
                    fi.SetValue(tempMyClass, fi.GetValue(original));
                else
                    fi.SetValue(tempMyClass, this.clone(fi.GetValue(original), fi.GetValue(original)));
            }

            return tempMyClass;
        }
}

Then this class:

public class MyOtherClass 
{
    public int MyProperty777 { get; set; }
}

when I do this:

MyClass a = new MyClass { 
                        MyProperty = 1, 
                        MySecondProperty = 2, 
                        subClass = new MyOtherClass() { MyProperty777 = -1 } 
                        };
            MyClass b = a.clone(a, a) as MyClass;

how come on the second call to clone, T is of type object and not of type MyOtherClass

+1  A: 

Your second (recursive) call to clone passes the result of GetValue as the second argument, which is of type object, and hence T is object.

i.e.

fi.SetValue(tempMyClass, this.clone(fi.GetValue(original), fi.GetValue(original)));

The result of GetValue on a FieldInfo is an object.

Given that you pass the same thing twice in all cases, the design of the clone method is possibly wrong. You probably don't need generics there. Just use obj.GetType() to get the type information of the second argument (if indeed you really need a second argument).

It would make more sense to constrain the return type using generics, so that the cast isn't necessary on the calling side. Also you could make Clone into an extension method so it could apply to anything.

On the other hand, the thing you're trying to do (an automatic deep clone) is unlikely to be generally useful. Most classes end up hold references to things that they don't own, so if you clone such an object, you end up accidentally cloning half of your application framework.

Daniel Earwicker
A: 

Try this:


    public static class Cloner
    {
        public static T clone(this T item) 
        {
            FieldInfo[] fis = item.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            object tempMyClass = Activator.CreateInstance(item.GetType());
            foreach (FieldInfo fi in fis)
            {
                if (fi.FieldType.Namespace != item.GetType().Namespace)
                    fi.SetValue(tempMyClass, fi.GetValue(item));
                else
                {
                    object obj = fi.GetValue(item);
                    fi.SetValue(tempMyClass, obj.clone());
                }
            }      
            return (T)tempMyClass;
        }
    }


MyClass b = a.clone() as MyClass;
CDSO1
A: 

First of all I agree that clone method should be static, but I don't think that

object tempMyClass = Activator.CreateInstance(typeof(T));

is a good idea. I think that better way is to use type of original and get rid of emptyObject parameter at all.

object tempMyClass = Activator.CreateInstance(original.GetType());

Also you have to GetFields on original not on this.

So my method would be

public static T clone<T>(T original)
{
    T tempMyClass = (T)Activator.CreateInstance(original.GetType());

    FieldInfo[] fis = original.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
    foreach (FieldInfo fi in fis)
    {
        object fieldValue = fi.GetValue(original);
        if (fi.FieldType.Namespace != original.GetType().Namespace)
            fi.SetValue(tempMyClass, fieldValue);
        else
            fi.SetValue(tempMyClass, clone(fieldValue));
    }

    return tempMyClass;
}

Note that I use original.GetType() anyway as inner call would have type T=Object anyway. Used generic type is determined at compilation time and it would be Object as return type of fi.GetValue.

You can move this static method to some static helper class.

As a side note I'd like to say that this implementation of "deep" clone will not work properly if there is some collection-type field (or any standard mutable composite field) in one of classes in your namespace.

iPhone beginner