Как сделать копию объекта (а не его ссылку), которая на самом деле является структурой?

У меня есть значение (экземпляр структуры), который был добавлен в object для общей обработки. Мне нужно сделать копию значения. Я не могу сделать это явно, потому что у меня просто есть Type и не знаю, что это такое во время компиляции.

По умолчанию я получаю копию ссылки: var copy = objectOfMyStruct;. Я думал о создании явной мелкой копии MemberwiseClone(), но я не могу этого сделать, потому что это защищенный метод, и я не могу изменить MyStruct.

Convert.ChangeType(objectOfMyStruct, typeOfMyStruct) не помогает, потому что внутри происходит преобразование (фактически никакого преобразования), и оно снова возвращает объект.

Как я могу это сделать?

EDIT:

Мне нужно сделать копию, чтобы сохранить исходное значение и просто десериализовать ее, чтобы перейти к 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, должен сделать клон структуры без отбрасывания ее обратно на свой unboxed-тип, так что, когда он мутирован путем вызова через реализованный интерфейс, только оригинал мутирован. Таким образом, возникает вопрос: как написать этот метод Clone?

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

Ответы

Ответ 1

Спасибо за пример 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 создает новую структуру, меняет ее и возвращает:

    Вы можете передать тип значения этому методу перегрузки. В этом случае возвращаемый объект является экземпляром в штучной упаковке.

  • Неуправляемая память освобождается.

Ответ 2

Я предполагаю, что вы не только хотите сделать копию, но и сможете реально использовать эту копию.

И чтобы использовать его, вам нужно отбросить (удалить) его на соответствующий тип, который фактически сделает копию. Фактически, даже добавление значения в поле уже привело к копированию.

Итак, если (например) вы знаете, что эти объекты являются либо int, либо float, вы можете сделать:

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>>.

Если вам нужно иметь дело с типами, о которых вы не знаете во время компиляции (какой-то тип добавил динамически какой-то механизм плагинов), чем это будет невозможно, но опять же, это также было бы невозможно делать что-либо с объектом (если только через интерфейс).

UPDATE:

Теперь, когда вы изменили свой вопрос, вот лучший ответ: вам не нужно делать копию, она была сделана для вас, боксируя структуру.

Пример:

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;

и все будет в порядке.

Ответ 3

Если список типов для обработки этого клонирования для управляется, то есть вы знаете, какие типы вам нужно обрабатывать для этого, тогда я бы просто создал словарь, содержащий функции, которые умеют обрабатывать каждый конкретный тип.

Здесь 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

Ответ 4

Вот еще один ответ:

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

Он использует метод Object.MemberwiseClone. Этот метод защищен, поэтому я должен его называть отражением.

Этот подход отлично работает с перечислениями и структурами, которые имеют [StructLayout(LayoutKind.Auto)].