AOT使用經(jīng)驗總結(jié)

一聚磺、引言

站長接觸 AOT 已有 3 個月之久坯台,此前在《好消息:NET 9 X86 AOT的突破 - 支持老舊Win7與XP環(huán)境》一文中就有所提及。在這段時間里瘫寝,站長使用 Avalonia 開發(fā)的項目也成功完成了 AOT 發(fā)布測試蜒蕾。然而,這一過程并非一帆風(fēng)順焕阿。站長在項目功能完成大半部分才開始進(jìn)行 AOT 測試咪啡,期間遭遇了不少問題,可謂是 “踩坑無數(shù)”暮屡。為了方便日后回顧撤摸,也為了給廣大讀者提供參考,在此將這段經(jīng)歷進(jìn)行總結(jié)褒纲。

.NET AOT是將.NET代碼提前編譯為本機代碼的技術(shù)准夷。其優(yōu)勢眾多,啟動速度快莺掠,減少運行時資源占用衫嵌,還提高安全性。AOT發(fā)布后無需再安裝.NET運行時等依賴彻秆。.NET 8楔绞、9 AOT發(fā)布后,可在XP掖棉、Win7非SP1操作系統(tǒng)下運行墓律。這使得應(yīng)用部署更便捷,能適應(yīng)更多老舊系統(tǒng)環(huán)境幔亥,為開發(fā)者拓展了應(yīng)用場景,在性能提升的同時察纯,也增加了系統(tǒng)兼容性帕棉,讓.NET應(yīng)用的開發(fā)和部署更具靈活性和廣泛性,給用戶帶來更好的體驗饼记。

二香伴、經(jīng)驗之談

(一)測試策略的重要性

從項目創(chuàng)建伊始,就應(yīng)養(yǎng)成良好的習(xí)慣具则,即只要添加了新功能或使用了較新的語法即纲,就及時進(jìn)行 AOT 發(fā)布測試。否則博肋,問題積累到后期低斋,解決起來會異常艱難蜂厅,站長就因前期忽視了這一點,付出了慘痛的代價膊畴。無奈的解決方法是重新創(chuàng)建項目掘猿,然后逐個還原功能并進(jìn)行 AOT 測試。經(jīng)過了一周的加班AOT測試唇跨,每個 AOT 發(fā)布過程大致如下:

  1. 內(nèi)網(wǎng) AOT 發(fā)布一次需 2稠通、3 分鐘,這段時間只能看看需求文檔买猖、技術(shù)文章改橘、需求文檔、技術(shù)文章玉控。唧龄。。
  2. 發(fā)布完成奸远,運行無效果既棺,體現(xiàn)在雙擊未出現(xiàn)界面,進(jìn)程列表沒有它懒叛,說明程序崩潰了丸冕,查看系統(tǒng)應(yīng)用事件日志,日志中通常會包含異常警告信息薛窥。
  3. 依據(jù)日志信息檢查代碼胖烛,修改相關(guān) API。
  4. 再次進(jìn)行 AOT 發(fā)布诅迷,重復(fù)上述 1 - 3 步驟佩番。

經(jīng)過一周的努力,項目 AOT 后功能測試終于正常罢杉,至此收工趟畏。

(二)AOT 需要注意的點及解決方法

1. 添加rd.xml

在主工程創(chuàng)建一個XML文件,例如Roots.xml滩租,內(nèi)容大致如下:

<linker>
    <assembly fullname="CodeWF.Toolbox.Desktop" preserve="All" />
</linker>

需要支持AOT的工程赋秀,在該XML中添加一個assembly節(jié)點,fullname是程序集名稱律想,CodeWF.Toolbox.Desktop是站長小工具的主工程名猎莲,點擊查看源碼。

在主工程添加ItemGroup節(jié)點關(guān)聯(lián)該XML文件:

<ItemGroup>
    <TrimmerRootDescriptor Include="Roots.xml" />
</ItemGroup>

2. Prism支持

站長使用了Prism框架及DryIOC容器技即,若要支持 AOT著洼,需要添加以下 NuGet 包:

<PackageReference Include="Prism.Avalonia" Version="8.1.97.11073" />
<PackageReference Include="Prism.DryIoc.Avalonia" Version="8.1.97.11073" />

rd.xml需要添加

<assembly fullname="Prism" preserve="All" />
<assembly fullname="DryIoc" preserve="All" />
<assembly fullname="Prism.Avalonia" preserve="All" />
<assembly fullname="Prism.DryIoc.Avalonia" preserve="All" />

3. App.config讀寫

在.NET Core中使用System.Configuration.ConfigurationManager包操作App.config文件,rd.xml需添加如下內(nèi)容:

<assembly fullname="System.Configuration.ConfigurationManager" preserve="All" />

使用Assembly.GetEntryAssembly().location失敗,目前使用ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)獲取的應(yīng)用程序程序配置身笤,指定路徑的方式后續(xù)再研究豹悬。

4. HttpClient使用

rd.xml添加如下內(nèi)容:

<assembly fullname="System.Net.Http" preserve="All" />

5. Dapper支持

Dapper的AOT支持需要安裝Dapper.AOT包,rd.xml添加如下內(nèi)容:

<assembly fullname="Dapper" preserve="All" />
<assembly fullname="Dapper.AOT" preserve="All" />

數(shù)據(jù)庫操作的方法需要添加DapperAOT特性展鸡,舉例如下:

[DapperAot]
public static bool EnsureTableIsCreated()
{
    try
    {
        using var connection = new SqliteConnection(DBConst.DBConnectionString);
        connection.Open();

        const string sql = $@"
            CREATE TABLE IF NOT EXISTS {nameof(JsonPrettifyEntity)}(
                {nameof(JsonPrettifyEntity.IsSortKey)} Bool,
                {nameof(JsonPrettifyEntity.IndentSize)} INTEGER
        )";

        using var command = new SqliteCommand(sql, connection);
        return command.ExecuteNonQuery() > 0;
    }
    catch (Exception ex)
    {
        return false;
    }
}

6. System.Text.Json

參考JsonExtensions.cs

序列化

public static bool ToJson<T>(this T obj, out string? json, out string? errorMsg)
{
    if (obj == null)
    {
        json = default;
        errorMsg = "Please provide object";
        return false;
    }

    var options = new JsonSerializerOptions()
    {
        WriteIndented = true,
        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
        TypeInfoResolver = new DefaultJsonTypeInfoResolver()
    };
    try
    {
        json = JsonSerializer.Serialize(obj, options);
        errorMsg = default;
        return true;
    }
    catch (Exception ex)
    {
        json = default;
        errorMsg = ex.Message;
        return false;
    }
}

反序列化

public static bool FromJson<T>(this string? json, out T? obj, out string? errorMsg)
{
    if (string.IsNullOrWhiteSpace(json))
    {
        obj = default;
        errorMsg = "Please provide json string";
        return false;
    }

    try
    {
        var options = new JsonSerializerOptions()
        {
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            TypeInfoResolver = new DefaultJsonTypeInfoResolver()
        };
        obj = JsonSerializer.Deserialize<T>(json!, options);
        errorMsg = default;
        return true;
    }
    catch (Exception ex)
    {
        obj = default;
        errorMsg = ex.Message;
        return false;
    }
}

7. 反射問題

參考項目CodeWF.NetWeaver

  1. 創(chuàng)建指定類型的List<T>Dictionary<T>實例:
public static object CreateInstance(Type type)
{
    var itemTypes = type.GetGenericArguments();
    if (typeof(IList).IsAssignableFrom(type))
    {
        var lstType = typeof(List<>);
        var genericType = lstType.MakeGenericType(itemTypes.First());
        return Activator.CreateInstance(genericType)!;
    }
    else
    {
        var dictType = typeof(Dictionary<,>);
        var genericType = dictType.MakeGenericType(itemTypes.First(), itemTypes[1]);
        return Activator.CreateInstance(genericType)!;
    }
}
  1. 反射調(diào)用List<T>Dictionary<T>Add方法添加元素失敗屿衅,下面是偽代碼:
// List<T>
var addMethod = type.GetMethod("Add");
addMethod.Invoke(obj, new[]{ child })
    
// Dictionary<Key, Value>
var addMethod = type.GetMethod("Add");
addMethod.Invoke(obj, new[]{ key, value })

解決辦法,轉(zhuǎn)換為實現(xiàn)的接口調(diào)用:

// List<T>
(obj as IList).Add(child);

// Dictionary<Key, Value>
(obj as IDictionary)[key] = value;
  1. 獲取數(shù)組莹弊、List<T>涤久、Dictionary<key, value>的元素個數(shù)

同上面Add方法反射獲取Length或Count屬性皆返回0,value.Property("Length", 0)忍弛,封裝的Property非AOT運行正確:

public static T Property<T>(this object obj, string propertyName, T defaultValue = default)
{
    if (obj == null) throw new ArgumentNullException(nameof(obj));
    if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException(nameof(propertyName));

    var propertyInfo = obj.GetType().GetProperty(propertyName);
    if (propertyInfo == null)
    {
        return defaultValue;
    }

    var value = propertyInfo.GetValue(obj);

    try
    {
        return (T)Convert.ChangeType(value, typeof(T));
    }
    catch (InvalidCastException)
    {
        return defaultValue;
    }
}

AOT成功:直接通過轉(zhuǎn)換為基類型或?qū)崿F(xiàn)的接口調(diào)用屬性即可:

// 數(shù)組
var length = ((Array)value).Length;

// List<T>
 if (value is IList list)
{
    var count = list.Count;
}

// Dictionary<key, value>
if (value is IDictionary dictionary)
{
    var count = dictionary.Count;
}

8. Windows 7支持

如遇AOT后無法在Windows 7運行响迂,請?zhí)砑?code>YY-Thunks包:

<PackageReference Include="YY-Thunks" Version="1.1.4-Beta3" />

并指定目標(biāo)框架為net9.0-windows

9. Winform\兼容XP

如果第8條后還運行不了细疚,請參考上一篇文章《.NET 9 AOT的突破 - 支持老舊Win7與XP環(huán)境 - 碼界工坊 (dotnet9.com)》添加VC-LTL包蔗彤,這里不贅述。

10. 其他

還有許多其他需要注意的地方疯兼,后續(xù)想起來逐漸完善本文然遏。

三、總結(jié)

AOT 發(fā)布測試雖然過程中可能會遇到諸多問題吧彪,但通過及時的測試和正確的配置調(diào)整待侵,最終能夠?qū)崿F(xiàn)項目的順利發(fā)布。希望以上總結(jié)的經(jīng)驗?zāi)軐Υ蠹以?AOT 使用過程中有所幫助姨裸,讓大家在開發(fā)過程中少走彎路秧倾,提高項目的開發(fā)效率和質(zhì)量。同時傀缩,也期待大家在實踐中不斷探索和總結(jié)那先,共同推動技術(shù)的進(jìn)步和發(fā)展。

AOT可參考項目:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赡艰,一起剝皮案震驚了整個濱河市售淡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瞄摊,老刑警劉巖勋又,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異换帜,居然都是意外死亡,警方通過查閱死者的電腦和手機鹤啡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門惯驼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事祟牲∠缎螅” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵说贝,是天一觀的道長议惰。 經(jīng)常有香客問我,道長乡恕,這世上最難降的妖魔是什么言询? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮傲宜,結(jié)果婚禮上运杭,老公的妹妹穿的比我還像新娘。我一直安慰自己函卒,他們只是感情好辆憔,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著报嵌,像睡著了一般虱咧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锚国,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天腕巡,我揣著相機與錄音,去河邊找鬼跷叉。 笑死逸雹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的云挟。 我是一名探鬼主播梆砸,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼园欣!你這毒婦竟也來了帖世?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤沸枯,失蹤者是張志新(化名)和其女友劉穎日矫,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绑榴,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡哪轿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了翔怎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窃诉。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡杨耙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出飘痛,到底是詐尸還是另有隱情珊膜,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布宣脉,位于F島的核電站车柠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏塑猖。R本人自食惡果不足惜竹祷,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望萌庆。 院中可真熱鬧溶褪,春花似錦、人聲如沸践险。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巍虫。三九已至彭则,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間占遥,已是汗流浹背俯抖。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瓦胎,地道東北人芬萍。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像搔啊,于是被迫代替她去往敵國和親柬祠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容