【问题标题】:Replace object instance with another in C#在 C# 中用另一个对象实例替换对象实例
【发布时间】:2012-07-07 19:19:46
【问题描述】:

在这个问题中,我想知道这是否以及如何实现。这种技术似乎是非常糟糕的做法,但我正在使用的 API(UnityEditor)似乎正在做这样的事情,我只是好奇。

如果对同一个对象有多个引用,是否可以在同一个内存槽中实例化一个新对象,使之前的所有引用都指向新对象?

我发现唯一可行的方法是使用非托管 C++。基本上发生了以下情况:

// 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?

prefab 现在如何指向不同的对象?

注意:请记住,Unity 是在 Mono 风格的 .NET 之上构建的

【问题讨论】:

  • PrefabUtility.ReplacePrefab(prefabCopy, prefab); 内部发生了什么?
  • @dtryon PrefabUtility.ReplacePrefab 调用内部方法(根据程序集浏览器)。

标签: c# memory pointers reference unity3d


【解决方案1】:

如果您将对象嵌入到另一个用于访问该对象的对象中,则可以这样做。

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

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

    public T Value { get return _obj; }
}

现在您可以创建对ObjectReference 类型对象的多个引用,并且只更改本地对象。 “真实”对象将通过Value 属性访问

一种稍微不同的方法是创建一个包装器,该包装器实现与“真实”对象相同的接口,从而使包装透明。

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();
    }
}

现在包装器可以像“真实”对象一样使用。您还可以在包装器的构造函数中传递“真实”对象的初始实例,并删除 _obj 的初始化器。

【讨论】:

  • 如果您的类将属性设置为另一个对象(例如,MyProperty = someOtherInstantiatedObject)会发生什么。现在,如果修改了 MyProperty,为什么我看不到 someOtherInstantiatedObject 的变化?你会怎么做?谢谢
  • @toughQuestions:当MyProperty 包含对someOtherInstantiatedObject 的引用并且您修改MyProperty 本身时,它将指向另一个对象或null,但它不会修改@987654331 @。但是如果你修改MyProperty.SomeProperty,这会改变someOtherInstantiatedObject 中的一个属性。您必须知道类是引用类型。这种类型的变量总是包含引用或null。另请参阅我对Setting a type reference type to null doesn't affect copied type? 的回答。
  • 感谢您的链接,这很清楚:-) 那么,我该如何解决我的问题呢?是直接使用 someOtherInstantiatedObject 的唯一方法,因为没有真正的方法可以将其“重命名”为 MyProperty?
  • 向 SO 发布问题,您可以在其中显示相关代码并详细解释您的问题。从这个简短的 cmets 很难理解您的问题。
【解决方案2】:

由于对象状态是由字段值定义的,因此您可以将包含字段值的内存从一个对象复制到另一个对象,从而有效地“替换”它:

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);
}

请注意,此代码需要为类定义[StructLayout(LayoutKind.Sequential)](或LayoutKind.Explicit)属性。

【讨论】:

  • UnityEngine.Object[StructLayout (LayoutKind.Sequential)]
  • Unity 重载了所有相等运算符,这些运算符在被销毁后执行不寻常的操作,例如将正数与 null 进行比较。 Marshal 有没有办法显示指针的字符串表示形式?我想将 prefab 的引用与 PrefabUtility.ReplacePrefab 的返回值进行比较,而 Unity 无法执行任何类型的黑魔法。
  • 您可以使用var gch = GCHandle.Alloc(obj, GCHandleType.Pinned); var address = gch.AddrOfPinnedObject(); gch.Free() 获取某些对象的地址,但它不适用于大多数对象类型。我认为这与您可以获得的对象地址一样接近。无论如何,我认为代码中的引用而不是对象实例(当然垃圾收集器除外)是不可能发生变化的。
  • 我希望推断更新后的预制件引用是否与原始引用相同,因为这意味着该对象正在被重用。不幸的是,这不适用于这种对象类型ArgumentException: Object contains non-primitive or non-blittable data. Cheers :-)
  • 我接受了这个答案,因为我认为这是最有可能的,特别是考虑到 StructLayout 属性存在。
【解决方案3】:

不,这是不可能的。

要真正更改对对象的所有引用,您必须冻结进程中的所有线程,并访问它们的寄存器集和堆栈。这就是垃圾收集器所做的,但对于常规代码来说是不可能的。

该方法最有可能做的是将一个对象复制到另一个对象上。

【讨论】:

  • 我认为这是最有可能的情况。虽然我相信UnityEngine.Object 不是线程安全的。
  • 他们不是,他是对的。事实上,我相信文档明确指出它正在制作副本,而不是传递参考。
【解决方案4】:

如果它是您要引用的自定义类,我认为您可以让所有引用指向一个假引用...

  1. 创建您的课程 (A)
  2. 创建您的类接口 (IA)
  3. 根据您的接口创建一个包装类,该类仅将所有调用传递给包含的对象 (AC)

我添加了一个赋值运算符,因此我将所有 A 对象作为 AC。

class AC:IA  
{  
    IA ref;  
    AC(IA ref)  
    {  
        this.ref = ref;  
    }  

    public void ChangeReference(IA newRef) { ref = newRef;}  
    public static operator = (IA assignedObj)  
    {  
        return (assignedObject is AC) ? assignedObject : new AC(assignedObj);
    }

    // implementation of all methods in A
    public override string ToString() { return ref.ToString(); }  
        ...  
}

现在,如果您愿意,您可以使用 ChangeReference 方法将所有内容切换到新的 Reference..

在 C++ 中,您将使用对引用的引用

祝你好运

【讨论】:

    猜你喜欢
    • 2018-12-13
    • 2014-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多