Как сделать копию объекта (а не его ссылку), которая на самом деле является структурой?
У меня есть значение (экземпляр структуры), который был добавлен в 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)]
.