<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      表達(dá)式樹復(fù)用陷阱:為什么結(jié)果會(huì)“顛倒”?

      # 表達(dá)式樹最佳實(shí)踐:避免節(jié)點(diǎn)共享

      今天看到一篇關(guān)于表達(dá)式樹用法,細(xì)看以后,發(fā)現(xiàn)有幾個(gè)我認(rèn)為是坑的地方,我就說說對(duì)于這個(gè)問題的理解和解決方案。

      一、原因

      • 表達(dá)式樹里的“參數(shù)”和“局部變量”(ParameterExpression)是按“引用身份”區(qū)分的,而不是按“調(diào)用棧幀”區(qū)分。
      • 當(dāng)你把“同一棵表達(dá)式樹實(shí)例”復(fù)用到多個(gè)位置(特別是該表達(dá)式體里還有 Block/局部變量),這些參數(shù)/局部就會(huì)在多個(gè)位置被“合并為同一個(gè)符號(hào)”,從而產(chǎn)生值竄位、順序顛倒等“看起來像 bug”的結(jié)果。
      • 這不是 .NET 表達(dá)式編譯器的 bug,而是表達(dá)式樹的語義:表達(dá)式是語法樹,節(jié)點(diǎn)按引用標(biāo)識(shí);復(fù)用同一個(gè)節(jié)點(diǎn),就意味著共享同一個(gè)參數(shù)/變量節(jié)點(diǎn)。

      可以簡化理解:表達(dá)式樹不是“每次調(diào)用都自動(dòng)克隆一份變量”,而是“你放進(jìn)樹里的那個(gè)變量節(jié)點(diǎn)就是唯一的一份”。如果你想要“各用各的變量”,你需要顯式“復(fù)制并替換參數(shù)/變量”。

       

      二、錯(cuò)誤用法(反例)

      問題核心:把同一 Lambda 表達(dá)式實(shí)例用 Expression.Invoke 在兩個(gè)位置復(fù)用,而該 Lambda 的表達(dá)式體里還有局部變量或需要唯一性的 ParameterExpression。

      示例(簡化版):

      ```c#

      // 模型
      record Address(string City);
      record AddressDTO(string City);
      record Customer(Address? Address, Address[] Addresses);

      // 映射表達(dá)式:Address -> AddressDTO
      Expression<Func<Address, AddressDTO>> map =
      a => new AddressDTO(a.City);

      // 誤用:同一表達(dá)式實(shí)例 map 被 Invoke 兩次
      var c = Expression.Parameter(typeof(Customer), "c");
      var dto = Expression.Parameter(typeof(AddressDTO), "dto"); // 這里只是示意

      var misuse = Expression.Block(
      // dto.Address = map(c.Address)
      Expression.Invoke(map, Expression.Property(c, nameof(Customer.Address))),
      // dto.Addresses[0] = map(c.Addresses[0])
      Expression.Invoke(map,
      Expression.ArrayIndex(
      Expression.Property(c, nameof(Customer.Addresses)),
      Expression.Constant(0)))
      // ...省略賦值細(xì)節(jié)
      );

      ```

       

      為什么會(huì)錯(cuò)?

      • 如果 map 的表達(dá)式體里包含 Block/局部變量/臨時(shí)目標(biāo)對(duì)象,這些 ParameterExpression 節(jié)點(diǎn)在兩處復(fù)用時(shí)會(huì)“合并為同一個(gè)變量”,從而出現(xiàn)“后一次寫入覆蓋前一次”的現(xiàn)象。
      • 這不是運(yùn)行時(shí)“每次調(diào)用一份獨(dú)立局部變量”,而是“同一份表達(dá)式節(jié)點(diǎn)被兩處共享”。

      表現(xiàn)的情況:

      • 順序一換,結(jié)果就變;或者兩個(gè)位置得到相同的結(jié)果;看似隨機(jī),實(shí)則節(jié)點(diǎn)共享導(dǎo)致的可預(yù)期行為。

      三、正確使用(正例)

      做法:在“每個(gè)使用點(diǎn)”對(duì)表達(dá)式進(jìn)行“參數(shù)重綁定(克隆+替換)”,保證每個(gè)使用點(diǎn)擁有自己的 ParameterExpression/局部變量節(jié)點(diǎn);或在非 EF 場景下直接編譯成委托使用。

      示例(參數(shù)重綁定,推薦給 EF/LINQ to Entities):

      ```C#

      static Expression Replace(Expression expr, ParameterExpression from, ParameterExpression to)
      => new ReplaceVisitor(from, to).Visit(expr)!;

      sealed class ReplaceVisitor : ExpressionVisitor
      {
      private readonly ParameterExpression _from, _to;
      public ReplaceVisitor(ParameterExpression from, ParameterExpression to)
      => (_from, _to) = (from, to);

      protected override Expression VisitParameter(ParameterExpression node)
      => node == _from ? _to : base.VisitParameter(node);
      }

      // 原始映射:Address -> AddressDTO
      Expression<Func<Address, AddressDTO>> map = a => new AddressDTO(a.City);

      // 每個(gè)使用點(diǎn)克隆一份,并替換參數(shù)
      var p1 = Expression.Parameter(typeof(Address), "a1");
      var map1 = Expression.Lambda<Func<Address, AddressDTO>>(
      Replace(map.Body, map.Parameters[0], p1), p1);

      var p2 = Expression.Parameter(typeof(Address), "a2");
      var map2 = Expression.Lambda<Func<Address, AddressDTO>>(
      Replace(map.Body, map.Parameters[0], p2), p2);

      // 組合使用:此時(shí) map1 與 map2 的參數(shù)/局部都是彼此獨(dú)立的
      var c = Expression.Parameter(typeof(Customer), "c");

      var body = Expression.Block(
      // dto.Address = map1(c.Address)
      Expression.Invoke(map1, Expression.Property(c, nameof(Customer.Address))),
      // dto.Addresses[0] = map2(c.Addresses[0])
      Expression.Invoke(map2,
      Expression.ArrayIndex(
      Expression.Property(c, nameof(Customer.Addresses)),
      Expression.Constant(0)))
      // ...省略賦值細(xì)節(jié)
      );

      ```

      替代方案(非 EF 內(nèi)存場景):

      ```C#

      // 直接編譯為委托,按普通 C# 邏輯調(diào)用
      var mapFunc = map.Compile();
      var dtoAddress = mapFunc(customer.Address!);
      var first = mapFunc(customer. Addresses[0]);

      ```

      • 優(yōu)點(diǎn):不會(huì)受表達(dá)式樹節(jié)點(diǎn)共享影響。
      • 注意:不能被 EF 翻譯到 SQL;僅適用于內(nèi)存 LINQ/業(yè)務(wù)層。

      額外建議:盡量避免在表達(dá)式樹中使用 Expression.Invoke,EF 通常無法翻譯;應(yīng)使用“參數(shù)重綁定 + 合并子表達(dá)式”的方式把子表達(dá)式直接嵌入到同一棵樹中。

      四、使用表達(dá)式樹的一些個(gè)人的建議

      • 避免復(fù)用同一表達(dá)式實(shí)例
        • 尤其當(dāng)表達(dá)式體含有 Block/局部變量/臨時(shí)對(duì)象(MemberInit/Assign 等)時(shí)。
        • 需要多處使用時(shí),請(qǐng)“克隆 + 參數(shù)重綁定”,保證 ParameterExpression 的唯一性。
      • 盡量合并而不是 Invoke
        • 在 EF/LINQ to Entities 中,Expression.Invoke 很難翻譯;用參數(shù)替換把子表達(dá)式合并進(jìn)父表達(dá)式(可參考 LinqKit 的 Expand 思路)。
      • 參數(shù)/常量類型要嚴(yán)格匹配
        • 用于比較的常量應(yīng)先轉(zhuǎn)換為目標(biāo)屬性類型(Convert.ChangeType/自定義轉(zhuǎn)換),再放入 Expression.Constant(value, property. Type),否則會(huì)報(bào) “Argument types do not match”。
        • IN/NotIn 構(gòu)造時(shí),要把集合元素逐一轉(zhuǎn)換為屬性類型再構(gòu)造 Equal 表達(dá)式。
      • 構(gòu)建 KeySelector 時(shí)注意類型
        • 如果泛型 TKey 與屬性類型不一致,使用 Expression.Convert(property, typeof(TKey)) 做顯式轉(zhuǎn)換。
      • 可提煉可復(fù)用的工具
        • ParameterRebinder/ReplaceVisitor(參數(shù)替換)
        • PropertyPath 解析(支持 “A.B.C” 鏈?zhǔn)綄傩裕?/li>
        • 安全常量轉(zhuǎn)換(string → int/decimal/enum/DateTime/Nullable<T>)
      • 性能與緩存
        • 頻繁重復(fù)構(gòu)建/編譯的表達(dá)式應(yīng)做緩存(根據(jù)條件組合生成鍵),避免多次 Compile 的開銷。
      • 可測試性
        • 先用內(nèi)存集合驗(yàn)證表達(dá)式語義(Compile + LINQ to Objects),再在 EF 上驗(yàn)證翻譯可行性(避免 Invoke/不可翻譯方法)。
      • 空值與可空處理
        • 組合訪問(如 A.B.C)要考慮 A/B 可能為空的情況(可通過顯式 Null 檢查或使用 SQL 可翻譯的 null 傳播邏輯)。
      • 組合與復(fù)用的邊界
        • 當(dāng)需要多處相同結(jié)構(gòu)但參數(shù)不同的子映射時(shí),優(yōu)先用“模板表達(dá)式 + 參數(shù)重綁定”;不要把“同一實(shí)例”直接塞進(jìn)多個(gè)位置。

      總結(jié):表達(dá)式樹強(qiáng)調(diào)“節(jié)點(diǎn)身份即語義”。復(fù)用同一實(shí)例,就等于共享同一個(gè)參數(shù)/變量節(jié)點(diǎn);要隔離,就必須克隆并替換參數(shù)。按此規(guī)則構(gòu)造與組合表達(dá)式,既能保持結(jié)果穩(wěn)定,也更容易被 EF 等提供者正確翻譯。

       

      posted @ 2025-09-07 14:17  [大師弟]  閱讀(105)  評(píng)論(1)    收藏  舉報(bào)
      主站蜘蛛池模板: 动漫av纯肉无码av在线播放| 国产亚洲一级特黄大片在线| 最新国产精品好看的精品| 欧美福利电影A在线播放| 免费日韩av网在线观看| 97久久综合亚洲色hezyo| 国产亚洲一二三区精品| 国产永久免费高清在线| 国产成人午夜福利院| 亚洲性日韩精品一区二区三区| 国产特级毛片aaaaaa高清| 99精品视频在线观看免费蜜桃| 人妻中文字幕精品系列| 久久精品女人的天堂av| 久久综合色之久久综合色| 日韩精品国产中文字幕| 无码人妻斩一区二区三区| 亚洲中文字幕无码专区| 人妻无码久久精品| 国产精品综合一区二区三区| 国产电影无码午夜在线播放| 亚洲欧美不卡高清在线| 国产无遮挡又黄又爽不要vip软件| 波多野结衣一区二区三区高清av| 天堂在线最新版av观看| 无码人妻斩一区二区三区| 精品无码久久久久国产| 色综合久久一区二区三区| 中文字幕日韩精品一区二区三区| 日韩av熟女人妻一区二| 国产免费一区二区不卡| 久久亚洲国产精品久久| 亚洲精品www久久久久久| 国产精品午夜福利91| 亚洲日韩av无码一区二区三区人| 色先锋av影音先锋在线| 国产精品久久久久久久9999| 国产av亚洲精品ai换脸电影| 一本加勒比hezyo无码专区| 2021国产精品视频网站| 久久综合亚洲鲁鲁九月天|