【问题标题】:How can I access private List<T> members?如何访问私有 List<T> 成员?
【发布时间】:2016-02-23 21:34:49
【问题描述】:

一般来说,在 C# 中使用 List 比使用 T[] 更方便。但是,有时分析器显示 List 与原生实现的批量操作(如 Array.Copy 和 Buffer.BlockCopy)相比具有显着的性能损失。此外,无法获取指向 List 元素的指针。

这使得在 Unity 中使用动态网格有点痛苦。如果我们可以访问 T[] List._items,其中一些问题可以得到缓解。这是否可以在没有大量开销的情况下完成? (CPU 或垃圾)

【问题讨论】:

  • 为什么不为此案例实现自定义列表?
  • 因为只有自定义 API 才会接受自定义列表。平台 API 不会。
  • @PaulDuBois:平台 API应该期望实现 IList&lt;T&gt; 的任何东西,您可以在自定义类中实现它。平台 API 中实际上需要 List&lt;T&gt; 实例的大多数示例都是 API 设计错误的可悲纪念碑。
  • @PaulDuBois - 我们所做的只是动态网格。我只使用普通的旧数组。需要明确的是,在任何“一般”的 Unity 编程中,我从来没有使用过 List 以外的任何东西,用数组来打扰是没有意义的。但是对于网格来说,它一直都是数组。 (在 iOS 中,我只是得到一块 ram 并自己做!)只是纯粹的 FTR,一个想法,当你在网格级别处理时,我看不出使用 List 有任何优势。你所做的一切你认为的算法都是偏移量。

标签: c# unity3d unity5


【解决方案1】:

如果您知道 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;
  }

【讨论】:

  • 再次只是纯粹的 FWIW,我从来没有能够让这样的东西可靠地工作(如你所建议的那样)。
  • 如果你能记住并分享细节,那将非常有用。
  • 执行时出错:System.TypeLoadException HResult=0x80131522 消息=无法从程序集 'StructPerf、Version=1.0.0.0、Culture=neutral、PublicKeyToken= 加载类型 'StructPerf.BackingArray`1' null' 因为泛型类型不能有明确的布局。
【解决方案2】:

总是可以使用反射。这会为调用 GetValue() 生成几百字节的垃圾。它也不是很快;大约 40 个 List 访问。

  // 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);
  }

【讨论】:

  • 我不明白 :)
  • 除非您使用 .NET 反射缓存,否则应避免在游戏中使用反射,因为会影响性能
  • 这个答案量化了一个特定平台(Unity、Mono、桌面)的性能内存命中,允许读者根据他们的特定要求自行决定,他们是否应该避免在游戏中反思。
猜你喜欢
  • 2012-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-22
  • 2013-12-30
  • 1970-01-01
相关资源
最近更新 更多