15

我有一个被转换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);

之所以进行复制,是因为只发送了增量(更改),因此应将其应用于原始值。

编辑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";
    }
}
4

4 回答 4

5

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

为了使用它,您需要将其强制转换(取消装箱)为适当的类型,从而有效地制作副本。事实上,即使将值放入框中,也已经产生了副本。

因此,如果(例如)您知道这些对象是整数或浮点数,您可以这样做:

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这里使用是为了方便,但对于每个结构都是如此。如果结构实现了一个接口,您可以将对象强制转换为该接口(不会制作第二个副本)并使用它。它不会修改原始值,因为它是对框中的副本进行操作。

更新 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;

你会没事的。

于 2013-10-25T13:42:22.797 回答
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将您的输入拆箱并将其复制到非托管内存中:

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

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

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

  • 非托管内存被释放。

于 2013-10-25T14:37:53.757 回答
2

这是另一个答案:

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

它使用Object.MemberwiseClone方法。这个方法是受保护的,这就是为什么我必须通过反射来调用它。

这种方法适用于枚举和具有[StructLayout(LayoutKind.Auto)].

于 2016-04-22T17:52:43.003 回答
1

如果处理这个克隆的类型列表是可控的,也就是说,你知道你需要处理哪些类型,那么我会简单地创建一个字典,其中包含知道如何处理每个特定类型的函数。

这是一个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 输出

于 2013-10-25T14:02:10.323 回答