6

I am implementing a deep object copier for Unity.

I found this great serialization/deserialization method here: https://stackoverflow.com/a/78612/3324388

However I hit a snag with MonoBehaviour objects. If the type is a GameObject, I need to use Instantiate instead of the serialization. So I've add a check:

if (typeof(T) == typeof(GameObject))
{
    GameObject clone = Instantiate(source as GameObject);
    T returnClone = clone as T;
    return returnClone;
}

I am able to cast the source as a GameObject (using as) but when I try to do it in reverse it fails with

The type parameter T cannot be used with the as parameter because it does not have a class type constraint nor a 'class' constraint.

If I try just casting it like:

if (typeof(T) == typeof(GameObject))
{
    GameObject clone = Instantiate(source as GameObject);
    T returnClone = (T)clone;
    return returnClone;
}

Cannot convert GameObject to type T

I feel I'm close but I can't quite get the casting right. Do you know what I am missing to get this to work?

If I cast the type to conform the error still persists: enter image description here

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Aggressor
  • 13,323
  • 24
  • 103
  • 182

2 Answers2

5

Using as T in the return statement seems to do the trick. In the following test code attached to a game object in the scene I see a clone of Test and the console shows me different values for Count:

public class Test : MonoBehaviour
{
    private static bool _cloned = false;

    public static T Clone<T>(T source) where T : class 
    {
        if (typeof(T) == typeof(GameObject))
        {
            GameObject clone = Instantiate(source as GameObject);
            return clone as T;
        }
        else if (typeof(T) == typeof(PlainType))
        {
            PlainType p = new PlainType();
            // clone code
            return p as T;
        }
        return null;
    }

    public class PlainType
    {
        private static int _counter = 0;
        public int Count = ++_counter;
        public string Text = "Counter = " + _counter;
    }

    public PlainType MyPlainType = new PlainType();

    void Update ()
    {
        if (!_cloned)
        {
            _cloned = true;
            Clone(gameObject);
            PlainType plainClone = Clone(MyPlainType);
            Debug.Log("Org = " + MyPlainType.Count + " Clone = " + plainClone.Count);
        }
    }

}
Kay
  • 12,918
  • 4
  • 55
  • 77
  • This does exclude cloning any value type, but if that is not a problem it's definitely cleaner and prettier than the intermediate cast to `object`. @Aggressor – InBetween Jan 02 '18 at 08:39
4

It’s not pretty but you can force the compiler doing a previous reference conversion to object:

 public static T Clone<T>(T source)
 {
      if (source is GameObject)
      {
          return (T)(object)Instantiate((GameObject)(object)source);  
      }
      else ...
  }

Yes, it is a bit of hack but sometimes you can’t avoid it. As a general rule, when you start mixing generics with runtime type checks things tend to get messy, a sure sign that you probably shouldn’t be using generics to begin with. Sometimes though, it can be justified but ugly code tends to crop up.

InBetween
  • 32,319
  • 3
  • 50
  • 90