首页 > 解决方案 > 实现 IQueryable 和 AST 以在游戏中查找实体

问题描述

据我了解,在某些企业 C# 代码中,有一种方法可以将 LINQ 查询转换为 AST,然后再转换为 SQL 或类似的东西,使用IQueryableand Expression.
这在我看来是这样的:

忽略数据库实现的代码 -> 黑魔法 -> 优化的 SQL

我想了解黑魔法并将其应用到游戏中。这是我的场景:

class Entity { public Vector2 position; }

class Chunk {
   const int CHUNK_SIZE = 16;
   public Vector2 position;  // chunk position is multiple of CHUNK_SIZE
   public List<Entity> entities;
}

class World {
   public Chunk[,] chunks; // Let's imagine this is a 256x256 array of chunks.
   public IEnumerable<Entity> Entities {
      get {
         return chunks.SelectMany(c => c.entities);
      }
   }
}

class SomewhereElse {
   void NotVerySmartCode() {
      var someArbitraryEntities = world.Entities
         .Where(e => e.position.x > 213 && e.position.x < 247
            && e.position.y > 198 && e.position.y < 212);

      foreach (var e in someArbitraryEntities) { // slooooooow }
   }
}

NotVerySmartCode查询时World.Entities,枚举器将遍历所有块和所有实体,Where并对每个实体执行 lambda。

很明显,如果Wherelambda 仅在位置在 208 < x < 256 和 192 < y < 224 内的块上执行,则可以优化此代码。

有没有办法智能地解释 LINQ 并执行此优化?我可以以某种方式实现IQueryable和使用Expression会执行一些黑魔法的方式吗?

如果我没有任何意义,我很抱歉,但我不明白如何将类似于上述查询的 LINQ 查询转换为高效的 SQL。

标签: c#linqunity3diqueryable

解决方案


好的,当然我在发布问题后半小时就想通了。

Func<int, int> addThree = (a) => a + 3; // This compiles to IL
Expression<Func<int, int>> addTwo = (a) => a + 2; // This compiles to an AST

我们可以浏览 AST 并解释它:

Debug.Log(addTwo.Body); // (a + 2)
Debug.Log(addTwo.Type); // Func<int, int>
Debug.Log(addTwo.Body.NodeType); // Add
Debug.Log(addTwo.Body is BinaryExpression); // True
Debug.Log(((BinaryExpression)addTwo.Body).Left.NodeType); // Parameter
Debug.Log(((BinaryExpression)addTwo.Body).Right.NodeType); // Constant
Debug.Log(((ConstantExpression)((BinaryExpression)addTwo.Body).Right).Value); // 2

我们可以编译 AST:

return addTwo.Compile()(2); // 4

理论上,我们还可以检测其中的模式并优化它们。
或者我们可以将它们翻译成 SQL。

基本上,我缺少的信息是 lambda 表达式,当分配给 a 时Expression,会变成与分配给委托不同的东西。


推荐阅读