我曾思考过一个与值类型相关的类似问题,并找到了一个“解决方案”。你看,在C#中,你无法像在C++中那样更改默认的复制构造函数,因为它旨在轻量级且无副作用。然而,你可以等到实际访问结构体时再检查是否已复制。
问题在于,与引用类型不同,结构体没有真正的身份;只有按值相等性。但是,它们仍然必须存储在某个内存位置,并且该地址可以用于识别(尽管是暂时的)值类型。GC在这里是一个问题,因为它可以移动对象,从而更改结构体所在的地址,因此你必须能够应对这种情况(例如使结构体的数据私有化)。
实际上,如果是值类型,结构体的地址可以从
this
引用中获取,因为它是一个简单的
ref T
。我将获得从我的库的引用中获取地址的
方法留给您自己去探索,但是发出自定义CIL也很简单。在这个例子中,我创建了一个类似于按值传递数组的东西。
public struct ByValArray<T>
{
T[] array;
public ByValArray(int size)
{
array = new T[size];
}
private void Update()
{
T[] inst = FindInstance(ref this);
if(inst != array)
{
array = inst;
}
}
static readonly Dictionary<IntPtr, WeakReference<T[]>> Cache = new Dictionary<IntPtr, WeakReference<T[]>>();
static T[] FindInstance(ref ByValArray<T> arr)
{
T[] orig = arr.array;
return UnsafeTools.GetPointer(
out arr,
ptr => {
WeakReference<T[]> wref;
T[] inst;
if(Cache.TryGetValue(ptr, out wref) && wref.TryGetTarget(out inst))
{
if(inst != orig)
{
inst = (T[])orig.Clone();
Cache[ptr] = new WeakReference<T[]>(inst);
}
return inst;
}else{
inst = (T[])orig.Clone();
Cache[ptr] = new WeakReference<T[]>(inst);
return inst;
}
}
);
}
public T this[int index]
{
get{
Update();
return array[index];
}
set{
Update();
array[index] = value;
}
}
public int Length{
get{
Update();
return array.Length;
}
}
public override bool Equals(object obj)
{
Update();
return base.Equals(obj);
}
public override int GetHashCode()
{
Update();
return base.GetHashCode();
}
public override string ToString()
{
Update();
return base.ToString();
}
}
var a = new ByValArray<int>(10);
a[5] = 11;
Console.WriteLine(a[5]);
var b = a;
b[5]++;
Console.WriteLine(b[5]);
Console.WriteLine(a[5]);
var c = a;
a = b;
Console.WriteLine(a[5]);
Console.WriteLine(c[5]);
正如您所看到的,这个值类型的行为就像每次复制对数组的引用时都将基础数组复制到新位置一样。
警告!!! 仅在自己的风险下使用此代码,并且最好永远不要在生产代码中使用。这种技术在许多层面上都是错误和邪恶的,因为它假定了某些不应该具有的身份。虽然这试图为这个结构体“强制执行”值类型语义(“目的证明手段”),但在几乎任何情况下,肯定有更好的解决方案来解决真正的问题。另请注意,尽管我已经尝试预见到这个类型可能会出现的任何可预见的问题,但仍可能存在这种类型会显示出相当意外行为的情况。