Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace object instance with another in C#

In this question I would like to find out if and how this is possible. This technique would seem extremely bad practice but it seems that the API (UnityEditor) that I am using, is doing something like this and I am just curious.

If there are multiple references to the same object, is it possible to instantiate a new object into the same memory slot so that all previous references point to the new object?

I figured out that the only feasible way to do so is by using unmanaged C++. Essentially the following is happening:

// Original prefab
GameObject prefab = x;
prefab.tag = "Untagged";

// A copy of the original prefab
GameObject prefabCopy = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
prefabCopy.tag = "EditorOnly";  // Change from initial value "Untagged"

Debug.Log(prefab.tag);     // "Untagged"   - expected
Debug.Log(prefabCopy.tag); // "EditorOnly" - expected

// Replace contents of prefab file with `prefabCopy`
PrefabUtility.ReplacePrefab(prefabCopy, prefab);

// Destroy the copy
DestroyImmediate(prefabCopy);

Debug.Log(prefab.tag);     // "EditorOnly"   - whoa?

Some how prefab is now pointing to a different object?

Note: Bear in mind that Unity is built on top of the Mono flavour of .NET

like image 559
Lea Hayes Avatar asked Jul 07 '12 17:07

Lea Hayes


2 Answers

You could do that if you embed your object into another one that is used to access the object.

class ObjectReference<T>
   where T : new()
{
    private T _obj = new T();

    public void CreateNewObject()
    {
        _obj = new T();
    }

    public T Value { get return _obj; }
}

Now you can create multiple references to an object of type ObjectReference and only change the local object. The "real" object would be accessed through the Value property

A slightly different approach is that you create a wrapper that implements the same interface as your "real" object, thus making this wrapping transparent.

interface ISomeInterface
{
    string PropertyA { get; set }
    void MethodB (int x);
}

class TheRealObject : ISomeInterface
{
    public string PropertyA { get; set }

    public void MethodB (int x)
    {
        Console.WriteLine(x);
    }
}

class Wrapper : ISomeInterface
{
    TheRealObject _obj = new TheRealObject();

    public string PropertyA
    { 
        get { return _obj.PropertyA; }
        set { _obj.PropertyA = value; }
    }

    public void MethodB (int x)
    {
        _obj.MethodB(x);
    }

    public void CreateNewObject()
    {
        _obj = new TheRealObject();
    }
}

Now the wrapper can be used as if it was the "real" object. You could also pass an initial instance of the "real" object in the wrapper's constructor and remove the initializer of _obj.

like image 112
Olivier Jacot-Descombes Avatar answered Sep 22 '22 13:09

Olivier Jacot-Descombes


Since an object state is defined by field values, you can copy memory, containing field values, from one object to another, effectively "replacing" it:

public static void Replace<T>(T x, T y)
    where T : class
{
    // replaces 'x' with 'y'
    if(x == null) throw new ArgumentNullException("x");
    if(y == null) throw new ArgumentNullException("y");

    var size = Marshal.SizeOf(typeof(T));
    var ptr = Marshal.AllocHGlobal(size);
    Marshal.StructureToPtr(y, ptr, false);
    Marshal.PtrToStructure(ptr, x);
    Marshal.FreeHGlobal(ptr);
}

Note that this code requires [StructLayout(LayoutKind.Sequential)] (or LayoutKind.Explicit) attribute defined for a class.

like image 29
max Avatar answered Sep 19 '22 13:09

max