Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Reflection Cloning Class variables

Discussion in 'Scripting' started by vipvex, Jul 13, 2014.

  1. vipvex

    vipvex

    Joined:
    Dec 6, 2011
    Posts:
    72
    I'm trying to write a function that will copy the values from one class variable to another.
    Here is what I have so far. What am I doing wrong?
    Code (CSharp):
    1.     public object Copy(object value)
    2.     {
    3.         object instance = AbilityAOE.CreateInstance(value.GetType());
    4.         foreach (FieldInfo info in value.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
    5.         {
    6.             if (info.FieldType.IsClass)
    7.                 info.SetValue(instance, Copy(info.GetValue(value)));
    8.             else
    9.                 info.SetValue(instance, info.GetValue(value));
    10.         }
    11.         return instance;
    12.     }
     
  2. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,716
    Nothing... except maybe "AbilityAOE.CreateInstance"

    Did you mean Activator.CreateInstance?
     
  3. vipvex

    vipvex

    Joined:
    Dec 6, 2011
    Posts:
    72
    Ok I tried this.
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Reflection;
    4.  
    5.  
    6. public class Test : MonoBehaviour {
    7.  
    8.     public AbilityAOE fromAOE;
    9.     public AbilityAOE toAOE;
    10.  
    11.     public AbilityAOE abilityAoe;
    12.  
    13.     void Start ()
    14.     {
    15.         abilityAoe = (AbilityAOE)Copy(fromAOE);
    16.     }
    17.  
    18.  
    19.     public object Copy(object value)
    20.     {
    21.         object instance = System.Activator.CreateInstance(value.GetType());
    22.         foreach (FieldInfo info in value.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
    23.         {
    24.             if (info.FieldType.IsClass)
    25.                 info.SetValue(instance, Copy(info.GetValue(value)));
    26.             else
    27.                 info.SetValue(instance, info.GetValue(value));
    28.         }
    29.         return instance;
    30.     }
    31.  
    32. }
    33.  
    The AbilityAOE's are ScriptableObjects. May that be the problem?
    It gives me this error:
    Code (CSharp):
    1. NullReferenceException: Object reference not set to an instance of an object
    2. Test.Copy (System.Object value) (at Assets/AbilitySystem/Test.cs:21)
    3. Test.Copy (System.Object value) (at Assets/AbilitySystem/Test.cs:25)
    4. Test.Start () (at Assets/AbilitySystem/Test.cs:15)
    5.  
     
  4. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,716
    Well, if you have one field being null, you need to pass that over without trying to "copy" it. "null.GetType()" doesn't work too well.
     
  5. vipvex

    vipvex

    Joined:
    Dec 6, 2011
    Posts:
    72
    I'm getting this error now:
    Code (CSharp):
    1. MissingMethodException: Method not found: 'Default constructor not found...ctor() of System.String'.
    2. System.Activator.CreateInstance (System.Type type, Boolean nonPublic) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System/Activator.cs:368)
    3. System.Activator.CreateInstance (System.Type type) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System/Activator.cs:254)
    4. Test.Copy (System.Object value) (at Assets/AbilitySystem/Test.cs:21)
    5. Test.Copy (System.Object value) (at Assets/AbilitySystem/Test.cs:28)
    6. Test.Start () (at Assets/AbilitySystem/Test.cs:15)
    7.  

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Reflection;
    4.  
    5.  
    6. public class Test : MonoBehaviour {
    7.  
    8.     public AbilityAOE fromAOE;
    9.     public AbilityAOE toAOE;
    10.  
    11.     public AbilityAOE abilityAoe;
    12.  
    13.     void Start ()
    14.     {
    15.         abilityAoe = (AbilityAOE)Copy(fromAOE);
    16.     }
    17.  
    18.  
    19.     public object Copy(object value)
    20.     {
    21.         object instance = System.Activator.CreateInstance(value.GetType());
    22.         foreach (FieldInfo info in value.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
    23.         {
    24.             if (info.GetValue(value) == null)
    25.                 continue;
    26.  
    27.             if (info.FieldType.IsClass)
    28.                 info.SetValue(instance, Copy(info.GetValue(value)));
    29.             else
    30.                 info.SetValue(instance, info.GetValue(value));
    31.         }
    32.         return instance;
    33.     }
    34.  
    35. }
    36.  
     
  6. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,716
    Yeah... Welcome to the amazing world of copying by reflection.

    Code (csharp):
    1. if (data is string)
    2.     return ((string)data).Clone();
    You will have issues if there is no empty constructor, but nothing you can do about it.

    You will also need to test for ScriptableObject since those need to be created with ScriptableObject.CreateInstance instead of the Activator.
     
  7. PrefabEvolution

    PrefabEvolution

    Joined:
    Mar 27, 2014
    Posts:
    225
    So if your AbilityAOE is ScriptableObject you can just use Instantiate to make a full copy of that.(with Unity serialization rules)
     
  8. vipvex

    vipvex

    Joined:
    Dec 6, 2011
    Posts:
    72
    I got it working with strings and Textures however I am getting errors when it tries copying generic Lists and Enums. Any ideas?

    Code (CSharp):
    1.     public object Copy(object value)
    2.     {
    3.         object copy = ScriptableObject.CreateInstance(value.GetType());
    4.         foreach (FieldInfo propertyInfo in value.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy))
    5.         {
    6.             if (propertyInfo.GetValue(value) == null)
    7.                 continue;
    8.  
    9.             Debug.Log (propertyInfo.Name);
    10.  
    11.             if (propertyInfo.FieldType.IsClass)
    12.             {
    13.                 if (propertyInfo.GetValue(value) is string)
    14.                     propertyInfo.SetValue(copy, (string)propertyInfo.GetValue(value));
    15.                 else
    16.                     if (propertyInfo.GetValue(value) is Texture)
    17.                         propertyInfo.SetValue(copy, (Texture)propertyInfo.GetValue(value));
    18.                 else
    19.                 {
    20.  
    21.                     propertyInfo.SetValue(copy, Copy(propertyInfo.GetValue(value)));
    22.              
    23.                 }
    24.             }else
    25.                 propertyInfo.SetValue(copy, propertyInfo.GetValue(value));
    26.         }
    27.         return copy;
    28.     }
    Error:
    Code (CSharp):
    1. TargetException: Non-static field requires a target
    2. System.Reflection.MonoField.SetValue (System.Object obj, System.Object val, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Globalization.CultureInfo culture) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Reflection/MonoField.cs:123)
    3. System.Reflection.FieldInfo.SetValue (System.Object obj, System.Object value) (at
     
  9. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,716
    Oh geez... You really want a full bullet-proof copy? Mine in my AdvancedInspector is like 400 lines, handling array, list, dictionary and so on.

    In case of collection, you have to create a collection with the same size, and copy each element over to the new collection. Usually, for an array, you would do Array.CreateInstance ( http://msdn.microsoft.com/en-us/library/system.array.createinstance(v=vs.110).aspx ) as for the a list, the default constructor (Activator.CreateInstance) works just fine.
     
  10. vipvex

    vipvex

    Joined:
    Dec 6, 2011
    Posts:
    72
    This works perfectly! Thanks!
    Code (CSharp):
    1. object copy = Instantiate((Object)value);