Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 60 additions & 42 deletions NewLife.Core/Reflection/AssemblyX.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using NewLife.Collections;
using NewLife.Log;

Expand Down Expand Up @@ -104,44 +105,61 @@ public String? Location
return cache.GetOrAdd(asm, key => new AssemblyX(key));
}

static AssemblyX()
{
//AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += OnReflectionOnlyAssemblyResolve;
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
}

private static Assembly? OnAssemblyResolve(Object? sender, ResolveEventArgs args)
{
var flag = XTrace.Log.Level <= LogLevel.Debug;
if (flag) XTrace.WriteLine("[{0}]请求加载[{1}]", args.RequestingAssembly?.FullName, args.Name);
//if (!flag) return null;

try
{
// 尝试在请求者所在目录加载
var file = args.RequestingAssembly?.Location;
if (!file.IsNullOrEmpty() && !args.Name.IsNullOrEmpty())
{
var name = args.Name;
var p = name.IndexOf(',');
if (p > 0) name = name[..p];

file = Path.GetDirectoryName(file).CombinePath(name + ".dll");
if (File.Exists(file)) return Assembly.LoadFrom(file);
}

// 辅助解析程序集。程序集加载过程中,被依赖程序集未能解析时,是否协助解析,默认false
if (Setting.Current.AssemblyResolve && !args.Name.IsNullOrEmpty())
return OnResolve(args.Name);
}
catch (Exception ex)
{
XTrace.WriteException(ex);
}

return null;
}
#endregion
static AssemblyX()
{
//AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += OnReflectionOnlyAssemblyResolve;
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
}

[ThreadStatic]
private static Int32 _resolving;

private static Assembly? OnAssemblyResolve(Object? sender, ResolveEventArgs args)
{
if (_resolving > 0) return null;

var name = args.Name;
if (name.IsNullOrEmpty() || IsSatelliteResourceAssembly(name)) return null;
Comment on lines +114 to +122
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ThreadStatic] _resolving 的重入保护目前是“同线程只要在解析中就直接返回 null”。这会屏蔽掉解析过程中触发的二次 AssemblyResolve(例如 Assembly.LoadFrom 加载 A 时,其依赖 B 触发的 AssemblyResolve),可能导致原本可被解析的依赖无法加载,产生回归。建议将重入保护改为“按程序集名去重”(例如线程内记录正在解析的 name 集合/栈,仅当解析同一个 name 再次进入时短路),或仅包裹会触发配置/日志初始化的分支而非整个解析器。

Copilot uses AI. Check for mistakes.

_resolving++;
try
{
// 尝试在请求者所在目录加载
var file = args.RequestingAssembly?.Location;
if (!file.IsNullOrEmpty())
{
var assemblyName = name;
var p = assemblyName.IndexOf(',');
if (p > 0) assemblyName = assemblyName[..p];

file = Path.GetDirectoryName(file).CombinePath(assemblyName + ".dll");
if (File.Exists(file)) return Assembly.LoadFrom(file);
}

// 辅助解析程序集。程序集加载过程中,被依赖程序集未能解析时,是否协助解析,默认false
if (Setting.Current.AssemblyResolve) return OnResolve(name);
}
catch (Exception ex)
{
Trace.WriteLine(ex);
return null;
}
finally
{
_resolving--;
}

return null;
}

private static Boolean IsSatelliteResourceAssembly(String name)
{
var p = name.IndexOf(',');
if (p > 0) name = name[..p];

return name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase);
}
#endregion

#region 扩展属性
//private IEnumerable<Type> _Types;
Expand Down
31 changes: 30 additions & 1 deletion XUnitTest.Core/Reflection/AssemblyXTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using NewLife;
using System.IO;
using System.Reflection;
using NewLife;
using NewLife.Configuration;
using NewLife.Reflection;
using Xunit;

Expand All @@ -25,4 +28,30 @@ public void GetCompileTime()
Assert.Equal("2022-04-27 03:44:00".ToDateTime(), time.ToUniversalTime());
}
}

[Fact]
public void OnAssemblyResolve_ResourceAssembly_DontInitSetting()
{
var currentField = typeof(Config<Setting>).GetField("_Current", BindingFlags.NonPublic | BindingFlags.Static);
Assert.NotNull(currentField);

var old = currentField.GetValue(null);
try
{
currentField.SetValue(null, null);

var method = typeof(AssemblyX).GetMethod("OnAssemblyResolve", BindingFlags.NonPublic | BindingFlags.Static);
Assert.NotNull(method);

var args = new ResolveEventArgs("System.IO.FileSystem.Watcher.resources, Version=8.0.0.0, Culture=zh-CN, PublicKeyToken=b03f5f7f11d50a3a", typeof(FileSystemWatcher).Assembly);
var rs = method.Invoke(null, [null, args]);

Assert.Null(rs);
Assert.Null(currentField.GetValue(null));
}
finally
{
currentField.SetValue(null, old);
}
Comment on lines +39 to +55
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

该测试通过反射把 Config<Setting>._Current 置空/恢复来断言“不会触发 Setting 初始化”。但本仓库 xUnit 配置开启了程序集与集合并行(xunit.runner.json/.ci.json),其它测试若并发访问 Setting.Current,会导致这里的断言或 SetValue 发生竞争,出现偶发失败或污染全局状态。建议避免修改全局静态字段(例如只断言调用前后 _Current 引用不变),或将涉及 Setting/Config<T> 全局状态的测试统一放入禁并行的 Collection / 关闭该测试程序集并行。

Suggested change
try
{
currentField.SetValue(null, null);
var method = typeof(AssemblyX).GetMethod("OnAssemblyResolve", BindingFlags.NonPublic | BindingFlags.Static);
Assert.NotNull(method);
var args = new ResolveEventArgs("System.IO.FileSystem.Watcher.resources, Version=8.0.0.0, Culture=zh-CN, PublicKeyToken=b03f5f7f11d50a3a", typeof(FileSystemWatcher).Assembly);
var rs = method.Invoke(null, [null, args]);
Assert.Null(rs);
Assert.Null(currentField.GetValue(null));
}
finally
{
currentField.SetValue(null, old);
}
var method = typeof(AssemblyX).GetMethod("OnAssemblyResolve", BindingFlags.NonPublic | BindingFlags.Static);
Assert.NotNull(method);
var args = new ResolveEventArgs("System.IO.FileSystem.Watcher.resources, Version=8.0.0.0, Culture=zh-CN, PublicKeyToken=b03f5f7f11d50a3a", typeof(FileSystemWatcher).Assembly);
var rs = method.Invoke(null, [null, args]);
Assert.Null(rs);
Assert.Same(old, currentField.GetValue(null));

Copilot uses AI. Check for mistakes.
}
}
Loading