如何访问私人列表<T> 成员?
How can I access private List<T> members?
一般来说,在C#中使用List比使用T[]更方便。然而,有时探查器显示 List 与 Array.Copy 和 Buffer.BlockCopy 等本机实现的批量操作相比具有显着的性能损失。此外,无法获取指向 List<> 元素的指针。
这使得在 Unity 中使用动态网格有些痛苦。如果我们可以访问 T[] List._items,则可以缓解其中一些问题。这可以在没有大量开销的情况下完成吗? (CPU 或垃圾)
使用反射总是可行的。这会为调用 GetValue() 生成几百字节的垃圾。它也不是很快;在 40 次 List< T > 访问的顺序上。
// Helper class for fetching and caching FieldInfo values
class FieldLookup {
string sm_name;
Dictionary<Type, FieldInfo> sm_cache;
public FieldLookup(string name) {
sm_name = name;
sm_cache = new Dictionary<Type, FieldInfo>();
}
public FieldInfo Get(Type t) {
try {
return sm_cache[t];
} catch (KeyNotFoundException) {
var field = sm_cache[t] = t.GetField(
sm_name,
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.GetField |
System.Reflection.BindingFlags.Instance);
return field;
}
}
}
static FieldLookup sm_items = new FieldLookup("_items");
public static T[] GetBackingArray<T>(this List<T> list) {
return (T[])sm_items.Get(typeof(List<T>)).GetValue(list);
}
如果您知道 List 的布局,那么您可以使用卑鄙的技巧来强制转换托管对象引用。不要使用它,除非你愿意在你 运行 所在的每个目标平台上进行测试,并在每次 Unity 升级时重新测试。
最危险的是它破坏了对象的运行时间和编译类型的不变量。编译器会为类型为TTo的对象生成代码,但是对象的RTTI字段仍然会显示类型为TFrom的对象。
[StructLayout(LayoutKind.Explicit)]
public struct ConvertHelper<TFrom, TTo>
where TFrom : class
where TTo : class {
[FieldOffset( 0)] public long before;
[FieldOffset( 8)] public TFrom input;
[FieldOffset(16)] public TTo output;
static public TTo Convert(TFrom thing) {
var helper = new ConvertHelper<TFrom, TTo> { input = thing };
unsafe {
long* dangerous = &helper.before;
dangerous[2] = dangerous[1]; // ie, output = input
}
var ret = helper.output;
helper.input = null;
helper.output = null;
return ret;
}
}
class PublicList<T> {
public T[] _items;
}
public static T[] GetBackingArray<T>(this List<T> list) {
return ConvertHelper<List<T>, PublicList<T>>.Convert(list)._items;
}
一般来说,在C#中使用List比使用T[]更方便。然而,有时探查器显示 List 与 Array.Copy 和 Buffer.BlockCopy 等本机实现的批量操作相比具有显着的性能损失。此外,无法获取指向 List<> 元素的指针。
这使得在 Unity 中使用动态网格有些痛苦。如果我们可以访问 T[] List._items,则可以缓解其中一些问题。这可以在没有大量开销的情况下完成吗? (CPU 或垃圾)
使用反射总是可行的。这会为调用 GetValue() 生成几百字节的垃圾。它也不是很快;在 40 次 List< T > 访问的顺序上。
// Helper class for fetching and caching FieldInfo values
class FieldLookup {
string sm_name;
Dictionary<Type, FieldInfo> sm_cache;
public FieldLookup(string name) {
sm_name = name;
sm_cache = new Dictionary<Type, FieldInfo>();
}
public FieldInfo Get(Type t) {
try {
return sm_cache[t];
} catch (KeyNotFoundException) {
var field = sm_cache[t] = t.GetField(
sm_name,
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.GetField |
System.Reflection.BindingFlags.Instance);
return field;
}
}
}
static FieldLookup sm_items = new FieldLookup("_items");
public static T[] GetBackingArray<T>(this List<T> list) {
return (T[])sm_items.Get(typeof(List<T>)).GetValue(list);
}
如果您知道 List 的布局,那么您可以使用卑鄙的技巧来强制转换托管对象引用。不要使用它,除非你愿意在你 运行 所在的每个目标平台上进行测试,并在每次 Unity 升级时重新测试。
最危险的是它破坏了对象的运行时间和编译类型的不变量。编译器会为类型为TTo的对象生成代码,但是对象的RTTI字段仍然会显示类型为TFrom的对象。
[StructLayout(LayoutKind.Explicit)]
public struct ConvertHelper<TFrom, TTo>
where TFrom : class
where TTo : class {
[FieldOffset( 0)] public long before;
[FieldOffset( 8)] public TFrom input;
[FieldOffset(16)] public TTo output;
static public TTo Convert(TFrom thing) {
var helper = new ConvertHelper<TFrom, TTo> { input = thing };
unsafe {
long* dangerous = &helper.before;
dangerous[2] = dangerous[1]; // ie, output = input
}
var ret = helper.output;
helper.input = null;
helper.output = null;
return ret;
}
}
class PublicList<T> {
public T[] _items;
}
public static T[] GetBackingArray<T>(this List<T> list) {
return ConvertHelper<List<T>, PublicList<T>>.Convert(list)._items;
}