如何制作一个实际上是struct的对象(不是它的引用)的副本?

And*_*hko 15 c#

我有一个值(结构实例)被转换object为通用处理.我需要复制一下这个值.我不能明确地这样做,因为我只是拥有它,Type并且不知道它在编译时是什么.

默认情况下,我得到一份参考文献:var copy = objectOfMyStruct;.我想过制作一个明确的浅拷贝,MemberwiseClone()但我不能这样做,因为它是受保护的方法,我无法修改MyStruct.

Convert.ChangeType(objectOfMyStruct, typeOfMyStruct) 没有帮助,因为转换(实际上没有转换)发生在里面,它再次返回对象.

我怎么能这样做?

编辑:

我需要制作一个副本以保留原始值,并将其反序列化为传递给OnChangeHandler.简化的实施是:

var oldValue = type.GetValue(reference);
var newValue = oldValue; // something better is needed here
Deserialize(type, stream, ref newValue);
OnChange(oldValue, newValue);
type.SetValue(reference, newValue);
Run Code Online (Sandbox Code Playgroud)

复制是因为只发送了delta(更改),因此应该应用于原始值.

编辑2:

这个实现适用于原始类型,所以尽管int也是盒装的,我正在复制它而不是复制对它的引用.我只是不明白这一点.


这是一个需要的例子.

您可以在LINQPad中测试的此示例应该复制该结构而不将其转换回其未装箱的类型,这样当通过实现的接口调用它进行变异时,只会突变原始结构.因此问题是; 我该如何编写克隆方法?

void Main()
{
    object original = new Dummy { Property = 42, Field = "Meaning of life" };
    object clone = Clone(original);

    ((IDummy)original).Mutate(); // will modify the boxed struct in-place
    original.Dump();

    // should output different if Clone did its job
    clone.Dump();
}

static object Clone(object input)
{
    return input;
}

public interface IDummy
{
    void Mutate();
}

public struct Dummy : IDummy
{
    public int Property { get; set; }
    public string Field;

    public void Mutate()
    {
        Property = 77;
        Field = "Mutated";
    }
}
Run Code Online (Sandbox Code Playgroud)

Kri*_*ten 5

我假设您不仅要制作副本,还要能够实际使用该副本.

并且为了使用它,您需要将其转换(取消)到适当的类型,这样可以有效地复制它.实际上,即使将价值放入盒子中也已经产生了副本.

所以,如果(例如)你知道这些对象是int或者浮点数,你可以这样做:

if (obj is int)
{
    int i = (int) obj;
    // do something with the copy in i
}
else if (obj is float)
{
    float f = (float) obj;
    // do something with the copy in f
}
Run Code Online (Sandbox Code Playgroud)

如果要评估大量类型,可以使用switch语句甚至是Dictionary<Type,Action<object>>.

如果你需要在编译时处理你不知道的类型(某些类型动态地添加某种类型的插件机制),那么这是不可能的,但是再一次,它也不可能做任何事情与对象(除非通过接口).

更新:

既然你改变了问题,这里有一个更好的答案:你不需要复制,它是通过装箱结构为你做的.

例:

int i = 42;

// make a copy on the heap
object obj = i;

// modify the original
i = i + 1;

// copy is not modified
Debug.Assert((int)obj == 42);
Run Code Online (Sandbox Code Playgroud)

显然,我在int这里使用是为了方便,但对于每个结构都是如此.如果struct实现了一个接口,你可以将该对象强制转换为该接口(不会生成第二个副本)并使用它.它不会修改原始值,因为它在框中的副本上运行.

更新2:

只是非常明确:这适用于每个结构.例如:

interface IIncrementor
{
    void Increment();
}

struct MyStruct : IIncrementor
{
    public int i;

    public void Increment()
    {
        this.i = this.i + 1;
    }

    public override string ToString()
    {
        return i.ToString();
    }
}

// in some method:

MyStruct ms = new MyStruct();
ms.i = 42;

Console.Writeline(ms); // 42

object obj = ms;

IIncrementable ii = (IIncrementable) obj;
ii.Increment();

Console.Writeline(ms); // still 42

Console.Writeline(ii); // 43
Run Code Online (Sandbox Code Playgroud)

还有一个更新:

代替

object original = new Dummy { Property = 42, Field = "Meaning of life" };
object clone = Clone(original);
Run Code Online (Sandbox Code Playgroud)

写

Dummy original = new Dummy { Property = 42, Field = "Meaning of life" };
object clone = original;
Run Code Online (Sandbox Code Playgroud)

你会没事的


Hei*_*nzi 4

感谢 LINQPad 示例,它极大地澄清了您的问题,并为我提供了提出解决方案的起点。

这是一个非常暴力的解决方案,但它可能会达到您的目的,直到有人想出一个更优雅的答案:

static object Clone(object input)
{
    IntPtr p = Marshal.AllocHGlobal(Marshal.SizeOf(input));
    try
    {
        Marshal.StructureToPtr(input, p, false);
        return Marshal.PtrToStructure(p, input.GetType());
    }
    finally
    {
        Marshal.FreeHGlobal(p);
    }
}
Run Code Online (Sandbox Code Playgroud)

它是这样工作的:

  • 它分配足够大的非托管内存来容纳您的结构。
  • StructureToPtr拆箱您的输入并将其复制到非托管内存中:

    如果结构是值类型,则可以将其装箱或拆箱。如果已装箱,则在复制前将其拆箱。

  • PtrToStructure创建一个新结构,将其装箱并返回:

    您可以将值类型传递给此重载方法。在这种情况下,返回的对象是装箱实例。

  • 非托管内存被释放。