解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
大石头 authored at 2018-05-15 21:21:05
22.39 KiB
X
// NewLife.Core NativeAOT compatibility smoke test.
// Runs a fixed set of scenarios against the core library and reports PASS/FAIL per scenario.
// Same binary is run under JIT (dotnet run) and NativeAOT (publish -p:PublishAot=true) to compare.
//
// 说明:AOT 能力验收冒烟件,由 .github/workflows/aot.yml 的 aot(win-x64)/aot-linux job 自动执行(非 0 退出码即失败)。
// 背景与实测结论见 Doc/AOT兼容性分析.md。
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json.Serialization;
using NewLife;
using NewLife.Buffers;
using NewLife.Configuration;
using NewLife.Data;
using NewLife.Http;
using NewLife.IO;
using NewLife.Log;
using NewLife.Messaging;
using NewLife.Model;
using NewLife.Net;
using NewLife.Reflection;
using NewLife.Serialization;
using NewLife.Web;

try { Console.OutputEncoding = Encoding.UTF8; } catch { }

Console.WriteLine("=== NewLife.Core AOT smoke ===");
Console.WriteLine($"framework={RuntimeInformation.FrameworkDescription}");
Console.WriteLine($"IsDynamicCodeSupported={RuntimeFeature.IsDynamicCodeSupported} IsDynamicCodeCompiled={RuntimeFeature.IsDynamicCodeCompiled}");
Console.WriteLine($"STJ reflection enabled={System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault}");
Console.WriteLine($"EntryLocation={GetEntryLocation()}");

[UnconditionalSuppressMessage("SingleFile", "IL3000", Justification = "AOT 判真探针:以 Location 是否为空区分原生发布件与 JIT 运行,不用于路径判断")]
static String? GetEntryLocation() => Assembly.GetEntryAssembly()?.Location;

var results = new List<(String Name, Boolean Ok, String Error)>();
void Run(String name, Action action)
{
    try
    {
        action();
        results.Add((name, true, ""));
        Console.WriteLine($"[PASS] {name}");
    }
    catch (Exception ex)
    {
        results.Add((name, false, $"{ex.GetType().Name}: {ex.Message}"));
        Console.WriteLine($"[FAIL] {name} -> {ex.GetType().Name}: {ex.Message}");
    }
}

var temp = Path.Combine(Path.GetTempPath(), "aot-smoke-" + Guid.NewGuid().ToString("N")[..8]);
Directory.CreateDirectory(temp);

var logdir = Path.Combine(temp, "logs");
Directory.CreateDirectory(logdir);
XTrace.Log = new TextFileLog { LogPath = logdir };
Console.WriteLine($"[diag] logdir={logdir}");

Run("基础类型转换 Utility", () =>
{
    if ("123".ToInt() != 123) throw new Exception("ToInt");
    if ("3.14".ToDouble() != 3.14) throw new Exception("ToDouble");
    if ("true".ToBoolean() != true) throw new Exception("ToBoolean");
    if ("2026-10-08".ToDateTime().Year != 2026) throw new Exception("ToDateTime");
});

Run("反射 Type.GetTypeEx", () =>
{
    var t = "NewLife.Log.XTrace".GetTypeEx();
    if (t == null) throw new Exception("Type.GetTypeEx 返回 null(类型可能被裁剪)");
});

Run("日志 XTrace 控制台", () =>
{
    var saved = XTrace.Log;
    try
    {
        XTrace.UseConsole();
        XTrace.WriteLine("aot-smoke-console");
    }
    finally
    {
        XTrace.Log = saved;
    }
});

Run("日志 TextFileLog 文件", () =>
{
    var flog = new TextFileLog { LogPath = temp };
    XTrace.Log = flog;
    XTrace.WriteLine("aot-smoke-file");
    for (var i = 0; i < 30 && !Directory.EnumerateFiles(temp).Any(); i++) Thread.Sleep(100);
    if (!Directory.EnumerateFiles(temp).Any()) throw new Exception("3 秒内未生成日志文件");
});

Run("ReceivedEventArgs.Rent 直接调用", () =>
{
    var e = ReceivedEventArgs.Rent();
    if (e == null) throw new Exception("Rent 返回 null");
    ReceivedEventArgs.Return(e);
});

Run("配置 JsonConfigProvider 往返", () =>
{
    var file = Path.Combine(temp, "cfg.json");
    var p = new JsonConfigProvider { FileName = file };
    if (!p.Save(new SmokeConfig { Name = "n1", Level = 5 })) throw new Exception("Save 返回 false");
    var back = p.Load<SmokeConfig>();
    if (back == null) throw new Exception("Load 返回 null");
    if (back.Name != "n1" || back.Level != 5) throw new Exception($"往返不一致 {back.Name}/{back.Level}");
});

Run("JSON JsonHelper 往返", () =>
{
    var m = new SmokeConfig { Name = "aot", Level = 3 };
    var json = JsonHelper.ToJson(m);
    var back = json.ToJsonEntity<SmokeConfig>();
    if (back?.Name != "aot" || back.Level != 3) throw new Exception("往返不一致");
});

Run("SpanSerializer 往返", () =>
{
    var m = new SmokeConfig { Name = "span", Level = 7 };
    var buf = new Byte[1024];
    var writer = new SpanWriter(buf);
    SpanSerializer.WriteObject(ref writer, m, typeof(SmokeConfig));
    var reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount));
    var back = (SmokeConfig)SpanSerializer.ReadObject(ref reader, typeof(SmokeConfig));
    if (back.Name != "span" || back.Level != 7) throw new Exception("往返不一致");
});

Run("Binary 写入", () =>
{
    var bn = new Binary { IsLittleEndian = true };
    bn.Write(42);
    bn.Write("hello");
    var data = bn.GetPacket().ToArray();
    if (data.Length == 0) throw new Exception("写入为空");
});

Run("NetServer/NetClient TCP+SrmpCodec 回环", () =>
{
    using var server = new NetServer { Port = 0, ProtocolType = NetType.Tcp, Protocol = new SrmpCodec() };
    server.Log = XTrace.Log;
    server.SessionLog = XTrace.Log;
    server.Start();
    Console.WriteLine($"[diag] net server active={server.Active} port={server.Port}");

    var serverGot = new TaskCompletionSource<Byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
    server.Received += (s, e) =>
    {
        if (e.Message is not DefaultMessage msg) return;
        var all = msg.Body!.ReadAllAsync().AsTask().GetAwaiter().GetResult();
        var body = all.AsReadOnlySequence().ToArray();
        all.TryDispose();
        serverGot.TrySetResult(body);

        if (msg.CreateReply() is not DefaultMessage reply) return;
        reply.SetBody(new ArrayPacket("pong"u8.ToArray()));
        ((INetSession)s!).SendMessage(reply);
    };

    using var client = new NetClient($"tcp://127.0.0.1:{server.Port}") { Protocol = new SrmpCodec() };
    client.Log = XTrace.Log;
    var clientGot = new TaskCompletionSource<Byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
    client.Received += (s, e) =>
    {
        if (e.Message is not DefaultMessage m || m.Kind < MessageKinds.Response) return;
        var all = m.Body!.ReadAllAsync().AsTask().GetAwaiter().GetResult();
        var body = all.AsReadOnlySequence().ToArray();
        all.TryDispose();
        clientGot.TrySetResult(body);
    };

    if (!client.Open()) throw new Exception("客户端打开失败");
    Console.WriteLine($"[diag] net client active={client.Active} sessions={server.SessionCount}");
    var req = new DefaultMessage { Sequence = 0x35 };
    req.SetBody(new ArrayPacket("ping"u8.ToArray()));
    client.SendMessage(req);

    if (!serverGot.Task.Wait(5000)) throw new Exception($"服务端未收到请求(超时)sessions={server.SessionCount}");
    if (!clientGot.Task.Wait(5000)) throw new Exception("客户端未收到应答(超时)");
    if (Encoding.UTF8.GetString(clientGot.Task.Result) != "pong") throw new Exception("应答负载不一致");
});

Run("HttpServer+ApiHttpClient GET", () =>
{
    using var server = new HttpServer { Port = 0 };
    server.Log = XTrace.Log;
    server.SessionLog = XTrace.Log;
    server.Map("/txt", () => "pong-aot");
    server.Map("/obj", () => new SmokePing { Msg = "pong-aot" });
    server.Start();
    Console.WriteLine($"[diag] http server active={server.Active} port={server.Port}");

    using var hc = new System.Net.Http.HttpClient { Timeout = TimeSpan.FromSeconds(5) };
    var t = hc.GetStringAsync($"http://127.0.0.1:{server.Port}/txt").GetAwaiter().GetResult();
    if (t != "pong-aot") throw new Exception("文本响应不一致: " + t);

    var http = new NewLife.Remoting.ApiHttpClient($"http://127.0.0.1:{server.Port}");
    var r = http.GetAsync<SmokePing>("/obj").GetAwaiter().GetResult();
    if (r?.Msg != "pong-aot") throw new Exception($"ApiHttpClient 对象反序列化不一致: {r?.Msg ?? "null"}");
});

Run("Http 异步委托返回值", () =>
{
    using var server = new HttpServer { Port = 0 };
    server.Log = XTrace.Log;
    server.SessionLog = XTrace.Log;
    // 异步委托走 OnInvoke → TaskHelper.GetResult(Task<T>.Result 反射取结果),验证 AOT 裁剪下返回值不丢
    server.Map("/asyncobj", async () => { await Task.Yield(); return new SmokePing { Msg = "pong-async" }; });
    server.Start();

    var http = new NewLife.Remoting.ApiHttpClient($"http://127.0.0.1:{server.Port}");
    var r = http.GetAsync<SmokePing>("/asyncobj").GetAwaiter().GetResult();
    if (r?.Msg != "pong-async") throw new Exception($"异步委托返回值丢失: {r?.Msg ?? "null"}");
});

Run("Http 控制器异步操作方法", () =>
{
    using var server = new HttpServer { Port = 0 };
    server.Log = XTrace.Log;
    server.SessionLog = XTrace.Log;
    // 控制器异步方法走 ControllerHandler → TaskHelper.GetResult,同上验证
    server.MapController<SmokeAsyncController>("/api/smoke");
    server.Start();

    var http = new NewLife.Remoting.ApiHttpClient($"http://127.0.0.1:{server.Port}");
    var r = http.GetAsync<SmokePing>("/api/smoke/asyncping").GetAwaiter().GetResult();
    if (r?.Msg != "pong-controller-async") throw new Exception($"控制器异步方法返回值丢失: {r?.Msg ?? "null"}");
});

Run("Http 控制器静态分发", () =>
{
    // 生成器已为 MapController 注册的控制器产出"方法名 → 强类型调用"分发表
    if (HttpDispatchTable.TryGet(typeof(SmokeAsyncController)) == null) throw new Exception("未生成控制器静态分发表");

    using var server = new HttpServer { Port = 0 };
    server.Log = XTrace.Log;
    server.SessionLog = XTrace.Log;
    server.MapController<SmokeAsyncController>("/api/smoke");
    server.Start();

    using var hc = new System.Net.Http.HttpClient { Timeout = TimeSpan.FromSeconds(5) };
    var url = $"http://127.0.0.1:{server.Port}/api/smoke";

    // 参数按名绑定 + 缺省使用显式默认值
    var echo = hc.GetStringAsync($"{url}/echo?name=abc").GetAwaiter().GetResult();
    if (echo != "echo:abc:7") throw new Exception($"参数绑定不一致: {echo}");

    // 参数按名绑定 + 显式传值
    var echo2 = hc.GetStringAsync($"{url}/echo?name=xyz&level=9").GetAwaiter().GetResult();
    if (echo2 != "echo:xyz:9") throw new Exception($"参数绑定不一致: {echo2}");

    // 上下文注入(IHttpController.Context 在分发前由处理器写入)
    var where = hc.GetStringAsync($"{url}/where").GetAwaiter().GetResult();
    if (where != "/api/smoke/where") throw new Exception($"上下文注入不一致: {where}");

    // 值类型返回值(装箱写回)
    var sum = hc.GetStringAsync($"{url}/sum?a=3&b=4").GetAwaiter().GetResult();
    if (sum != "7") throw new Exception($"值类型返回值不一致: {sum}");
});

Run("CsvDb 读写往返", () =>
{
    // CsvDb<T> 的泛型参数带保留注解,AOT 下模型公开属性元数据按需保留
    var file = Path.Combine(Path.GetTempPath(), $"nlcsv_{Guid.NewGuid():N}.csv");
    try
    {
        var models = new[] { new SmokeCsvRow { Name = "a", Level = 1 }, new SmokeCsvRow { Name = "b", Level = 2 } };
        using (var db = new CsvDb<SmokeCsvRow> { FileName = file }) db.Write(models, false);

        using var db2 = new CsvDb<SmokeCsvRow> { FileName = file };
        var list = db2.FindAll().ToList();
        if (list.Count != 2 || list[0].Name != "a" || list[1].Name != "b" || list[1].Level != 2) throw new Exception($"CSV 往返不一致: count={list.Count} first={list.FirstOrDefault()?.Name ?? "null"}");
    }
    finally
    {
        try { File.Delete(file); } catch { }
    }
});

Run("DbTable 模型读写往返", () =>
{
    // DbTable 的泛型/Type 入口带保留注解,AOT 下模型公开属性元数据按需保留
    var table = new DbTable();
    table.WriteModels(new[] { new SmokeCsvRow { Name = "x", Level = 3 } });
    var list = table.ReadModels<SmokeCsvRow>().ToList();
    if (list.Count != 1 || list[0].Name != "x" || list[0].Level != 3) throw new Exception($"DbTable 往返不一致: count={list.Count} first={list.FirstOrDefault()?.Name ?? "null"}");
});

Run("Http 静态分发优先级", () =>
{
    // 手工装配 ControllerHandler(绕过 MapController,生成器不会为该类型生成分发表),
    // 手工注册的分发器返回固定文本,用于锚定"处理器优先命中静态分发表"这一集成契约
    HttpDispatchTable.RegisterController(typeof(SmokeProbeController), new Dictionary<String, HttpDispatchTable.ControllerInvokeDelegate>(StringComparer.OrdinalIgnoreCase)
    {
        ["Hit"] = (controller, context) => { context.Response.SetResult("dispatch-hit"); return Task.CompletedTask; },
    });

    using var server = new HttpServer { Port = 0 };
    server.Map("/probe/*", new ControllerHandler { ControllerType = typeof(SmokeProbeController), Path = "/probe/" });
    server.Start();

    using var hc = new System.Net.Http.HttpClient { Timeout = TimeSpan.FromSeconds(5) };
    var text = hc.GetStringAsync($"http://127.0.0.1:{server.Port}/probe/hit").GetAwaiter().GetResult();
    if (text != "dispatch-hit") throw new Exception($"未优先命中静态分发表: {text}");
});

Run("WebSocket 文本回显", () =>
{
    using var server = new HttpServer { Port = 0 };
    server.Log = XTrace.Log;
    server.SessionLog = XTrace.Log;
    server.Map("/ws", new SmokeWsHandler());
    server.Start();

    var ws = new WebSocketClient($"ws://127.0.0.1:{server.Port}/ws") { Log = XTrace.Log };
    if (!ws.OpenAsync().GetAwaiter().GetResult()) throw new Exception("WebSocket 建连失败");
    ws.SendTextAsync("hi-aot").GetAwaiter().GetResult();
    var msg = ws.ReceiveMessageAsync().WaitAsync(TimeSpan.FromSeconds(5)).GetAwaiter().GetResult();
    var text = msg?.Payload?.ToStr() ?? "";
    if (!text.Contains("echo:hi-aot")) throw new Exception("回显不一致: " + text);
    ws.CloseAsync(1000, "done").GetAwaiter().GetResult();
});

Run("ObjectContainer 服务解析", () =>
{
    var ioc = ObjectContainer.Current;
    ioc.AddTransient<ISmokeSvc, SmokeSvc>();
    var svc = ioc.GetService<ISmokeSvc>();
    if (svc == null || svc.GetType() != typeof(SmokeSvc)) throw new Exception("解析失败");
});

Run("动态能力守卫 PluginHelper", () =>
{
    // 冒烟刻意调用带 RequiresUnreferencedCode/RequiresDynamicCode 声明的守卫 API,验证其降级行为
    PluginGuardProbe.Run();
});

Run("源生成器服务工厂", () =>
{
    // 编译期由源生成器扫描容器注册调用点,为唯一公开无参构造的实现类型注册静态工厂
    if (ServiceFactoryTable.TryGet(typeof(SmokeSvc)) == null) throw new Exception("未生成 SmokeSvc 工厂");

    var ioc = ObjectContainer.Current;
    var svc = ioc.GetService<ISmokeSvc>();
    if (svc == null || svc.GetType() != typeof(SmokeSvc)) throw new Exception("解析失败");
});

Run("源生成器 Json 成员访问", () =>
{
    // 编译期由源生成器为 [JsonSerializable] 标记类型注册成员访问委托
    if (MemberAccessorTable.TryGetGetter(typeof(SmokeJsonModel), "Name") == null) throw new Exception("未生成 Json 成员访问器");

    var m = new SmokeJsonModel { Name = "sg", Count = 5, Note = "n" };
    var json = JsonHelper.ToJson(m);
    var back = json.ToJsonEntity<SmokeJsonModel>();
    if (back?.Name != "sg" || back.Count != 5 || back.Note != "n") throw new Exception("往返不一致: " + json);
});

Run("源生成器配置绑定", () =>
{
    // 编译期由源生成器扫描 Config<T> 继承链,为配置模型注册成员访问委托
    if (MemberAccessorTable.TryGetSetter(typeof(SmokeSetting), "Port") == null) throw new Exception("未生成配置成员访问器");

    var provider = new SmokeConfigProvider();
    provider["Host"] = "1.2.3.4";
    provider["Port"] = "8080";

    // 标准入口 Load<T> 的泛型参数带 DynamicallyAccessedMembers 注解,AOT 下按需保留模型公开属性元数据
    var model = provider.Load<SmokeSetting>() ?? throw new Exception("Load<SmokeSetting> 返回 null");
    if (model.Host != "1.2.3.4" || model.Port != 8080) throw new Exception($"Load 绑定不一致: {model.Host}:{model.Port}");

    // 类型元数据一经标准入口保留,直接调用 MapTo 扩展同样可用
    var model2 = new SmokeSetting();
    provider.Root.MapTo(model2, provider);
    if (model2.Host != "1.2.3.4" || model2.Port != 8080) throw new Exception($"MapTo 绑定不一致: {model2.Host}:{model2.Port}");
});

Run("SystemJson 上下文透传", () =>
{
    // 源生成上下文提供成员元数据,运行时类型(Object 入口)路径据此完成序列化与反序列化
    var model = new SmokeJsonModel { Name = "ctx", Count = 7, Note = "n" };

    var json = new SystemJson(SmokeJsonContext.Default);
    var text = json.Write(model);
    var back = json.Read(text, typeof(SmokeJsonModel)) as SmokeJsonModel;
    if (back == null || back.Name != "ctx" || back.Count != 7 || back.Note != "n") throw new Exception("上下文往返失败: " + text);

    // 未在上下文声明的类型:回退默认解析器,行为不变
    var fb = json.Read(json.Write(new SmokeConfig { Name = "fb", Level = 3 }), typeof(SmokeConfig)) as SmokeConfig;
    if (fb == null || fb.Name != "fb" || fb.Level != 3) throw new Exception("未声明类型回退失败");
});

Run("Reflect 泛型入口保留", () =>
{
    // 泛型入口携带 DynamicallyAccessedMembers,AOT 裁剪下保留编译期已知类型的成员;
    // 非泛型重载(typeof(X).GetPropertyEx 等)不保证保留,属文档化边界(见分析稿 §4/§5)
    var pi = Reflect.GetPropertyEx<SmokeReflectOnly>("Marker") ?? throw new Exception("GetPropertyEx<T> 返回 null(属性被裁剪)");
    var mi = Reflect.GetMethodEx<SmokeReflectOnly>("Compute") ?? throw new Exception("GetMethodEx<T> 返回 null(方法被裁剪)");
    var obj = Reflect.CreateInstance<SmokeReflectOnly>() ?? throw new Exception("CreateInstance<T> 返回 null");

    pi.SetValue(obj, "ok");
    if (pi.GetValue(obj) as String != "ok") throw new Exception("属性读写失败");
    if (mi.Invoke(obj, null) as String != "ok") throw new Exception("方法调用失败");
});

Console.WriteLine("=== summary ===");
var failed = 0;
foreach (var r in results)
{
    if (!r.Ok) failed++;
    Console.WriteLine($"{(r.Ok ? "PASS" : "FAIL")}\t{r.Name}{(r.Ok ? "" : "\t" + r.Error)}");
}
Console.WriteLine($"total={results.Count} pass={results.Count - failed} fail={failed}");
Environment.ExitCode = failed;

Thread.Sleep(1500);

public class SmokeConfig
{
    public String Name { get; set; } = "";
    public Int32 Level { get; set; }
}

public class SmokePing
{
    public String Msg { get; set; } = "";
}

public interface ISmokeSvc
{
    String Ping();
}

public class SmokeSvc : ISmokeSvc
{
    public String Ping() => "pong";
}

public class SmokeWsHandler : WebSocketHandler
{
    public override void ProcessMessage(NewLife.Http.WebSocket socket, WsMessage message)
    {
        if (message.Type == WebSocketMessageType.Text)
        {
            socket.Send($"echo:{message.Payload?.ToStr()}");
            return;
        }

        base.ProcessMessage(socket, message);
    }
}

[JsonSerializable(typeof(SmokeJsonModel))]
public partial class SmokeJsonContext : JsonSerializerContext { }

public class SmokeJsonModel
{
    public String Name { get; set; } = "";
    public Int32 Count { get; set; }
    public String? Note { get; set; }
}

// 仅经 NewLife.Reflection 反射访问的模型:用于验证 Reflect 入口的保留注解在 AOT 下生效
public class SmokeReflectOnly
{
    public String? Marker { get; set; }
    public String Compute() => "ok";
}

public class SmokeSetting : Config<SmokeSetting>
{
    public String Host { get; set; } = "";
    public Int32 Port { get; set; }
}

public class SmokeConfigProvider : ConfigProvider
{
}

public class SmokeAsyncController : IHttpController
{
    /// <summary>上下文,由处理器在分发前注入</summary>
    public IHttpContext? Context { get; set; }

    /// <summary>异步操作方法,返回对象</summary>
    public async Task<SmokePing> AsyncPing()
    {
        await Task.Yield();
        return new SmokePing { Msg = "pong-controller-async" };
    }

    /// <summary>带参数方法:按名绑定 + 显式默认值</summary>
    /// <param name="name">名称</param>
    /// <param name="level">级别</param>
    public String Echo(String name, Int32 level = 7) => $"echo:{name}:{level}";

    /// <summary>读取注入的上下文路径</summary>
    public String Where() => Context?.Path ?? "null";

    /// <summary>值类型返回值</summary>
    /// <param name="a">加数</param>
    /// <param name="b">加数</param>
    public Int32 Sum(Int32 a, Int32 b) => a + b;
}

public class SmokeProbeController
{
    /// <summary>反射回退路径的返回值(若优先命中静态分发表则不会被调用)</summary>
    public String Hit() => "reflection-hit";
}

public class SmokeCsvRow
{
    public String Name { get; set; } = "";

    public Int32 Level { get; set; }
}

/// <summary>插件守卫探针。冒烟刻意调用带 RequiresUnreferencedCode/RequiresDynamicCode 声明的 API,验证其降级行为</summary>
public static class PluginGuardProbe
{
    /// <summary>验证插件动态加载在 JIT/AOT 两种形态下的行为</summary>
    [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "冒烟刻意调用带 RequiresUnreferencedCode 声明的插件守卫 API,验证其降级行为")]
    [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "冒烟刻意调用带 RequiresDynamicCode 声明的插件守卫 API,验证其降级行为")]
    public static void Run()
    {
        // JIT 下:未提供 dll 时查不到类型返回 null;NativeAOT 下应抛明确的 PlatformNotSupportedException(而非晦涩崩溃)
        if (RuntimeFeature.IsDynamicCodeSupported)
        {
            var t = PluginHelper.LoadPlugin("NewLife.XXX.NotExist", null, null, null);
            if (t != null) throw new Exception("意外解析到类型: " + t.FullName);
        }
        else
        {
            try
            {
                PluginHelper.LoadPlugin("NewLife.XXX.NotExist", null, null, null);
                throw new Exception("AOT 下插件动态加载竟然可用");
            }
            catch (PlatformNotSupportedException ex)
            {
                if (!ex.Message.Contains("插件动态加载")) throw new Exception("异常信息不明确: " + ex.Message);
            }
        }
    }
}