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

时间:2013-10-25 13:30:28

标签: c#

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

默认情况下,我会获得一份参考文件:var copy = objectOfMyStruct;。我考虑过MemberwiseClone()制作一个明确的浅层副本,但我不能这样做,因为它是受保护的方法,我无法修改MyStruct

Convert.ChangeType(objectOfMyStruct, typeOfMyStruct)没有帮助,因为内部发生转换(实际上没有转换),它会再次返回Object。

我怎么能这样做?

编辑:

我需要制作一个副本以保留原始值,然后将其反序列化以传递给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);

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

编辑2:

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


以下是需要的示例。

您可以在LINQPad中测试的此示例应该复制struct 而不将其强制转换回未装箱的类型,以便在通过调用通过实现接口,只有原来是变异的。因此问题是;我该如何编写克隆方法?

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

4 个答案:

答案 0 :(得分: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
}

如果您要评估大量类型,可以使用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);

显然,为方便起见,我在这里使用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

再多一次更新:

而不是

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

Dummy original = new Dummy { Property = 42, Field = "Meaning of life" };
object clone = original;

你会没事的。

答案 1 :(得分: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);
    }
}

这是它的工作原理:

  • 它分配足够大的非托管内存来保存你的结构。
  • StructureToPtr unboxes your input并将其复制到非托管内存中:

      

    如果structure是值类型,则可以将其装箱或取消装箱。如果是盒装,则在复制前将其取消装箱。

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

      

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

  • 释放非托管内存。

答案 2 :(得分:1)

如果要控制处理此克隆的类型列表,也就是说,您知道需要处理此类型的类型,那么我只需创建一个包含知道如何处理每种特定类型的函数的字典。 / p>

这是一个LINQPad示例:

void Main()
{
    _CloneMapping[typeof(Dummy)] = (object obj) =>
    {
        Dummy d = (Dummy)obj;
        return new Dummy { Field = d.Field, Property = d.Property };
    };

    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 readonly Dictionary<Type, Func<object, object>> _CloneMapping = new Dictionary<Type, Func<object, object>>();
static object Clone(object input)
{
    if (input == null)
        return null;

    var cloneable = input as ICloneable;
    if (cloneable != null)
        return cloneable.Clone();

    Func<object, object> cloner;
    if (_CloneMapping.TryGetValue(input.GetType(), out cloner))
        return cloner(input);

    throw new NotSupportedException();
}

public interface IDummy
{
    void Mutate();
}

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

    public void Mutate()
    {
        Property = 77;
        Field = "Mutated";
    }
}

这将输出:

LINQPad output

答案 3 :(得分:1)

这是另一个答案:

static object Clone(object input) =>
    typeof(object)
    .GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance)
    .Invoke(input, null);

它使用Object.MemberwiseClone方法。这个方法受到保护,这就是我必须通过反射调用它的原因。

此方法适用于枚举,以及具有[StructLayout(LayoutKind.Auto)]

的结构