我有一个值(结构实例)被强制转换为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";
}
}
答案 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";
}
}
这将输出:
答案 3 :(得分:1)
这是另一个答案:
static object Clone(object input) =>
typeof(object)
.GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(input, null);
它使用Object.MemberwiseClone方法。这个方法受到保护,这就是我必须通过反射调用它的原因。
此方法适用于枚举,以及具有[StructLayout(LayoutKind.Auto)]
。