设置版本后,WPF无法加载版本化DLL中XAML资源的问题
使用Visual Studio 2022 + .NET 6开发WPF应用,将XAML资源(控件、样式、颜色等)分离到Presentation.dll共享库中,通过MyStyles.xaml合并资源:
<ResourceDictionary.MergedDictionaries> <styles:SharedResourceDictionary Source="Presentation;component/Styles/ColorDefinitions.xaml"/> <styles:SharedResourceDictionary Source="Presentation;component/Styles/FontDefinitions.xaml"/> </ResourceDictionary.MergedDictionaries>
在Application.Resources中引用:
<ResourceDictionary Source="/Presentation;component/Styles/MyStyles.xaml" />
应用项目引用Presentation项目,未设置版本前运行正常。通过Directory.Build.props为Presentation.dll设置版本后:
<Project> <PropertyGroup> <AssemblyVersion>1.0.0.0</AssemblyVersion> <Version>1.0.0.0</Version> </PropertyGroup> </Project>
应用抛出异常:
System.Windows.Markup.XamlParseException HResult=0x80131501 Message='Set property 'System.Windows.ResourceDictionary.DeferrableContent' threw an exception.' Line number '10' and line position '42'. Source=PresentationFramework StackTrace: at System.Windows.Markup.XamlReader.RewrapException(Exception e, IXamlLineInfo lineInfo, Uri baseUri) in /_/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Markup/XamlReader.cs:line 527 This exception was originally thrown at this call stack: System.Windows.ResourceDictionary.SetDeferrableContent(System.Windows.DeferrableContent) in ResourceDictionary.cs System.Windows.Baml2006.WpfSharedBamlSchemaContext.Create_BamlProperty_ResourceDictionary_DeferrableContent.AnonymousMethod__297_0(object, object) in WpfGeneratedKnownProperties.cs System.Windows.Baml2006.WpfKnownMemberInvoker.SetValue(object, object) in WpfKnownMemberInvoker.cs MS.Internal.Xaml.Runtime.ClrObjectRuntime.SetValue(System.Xaml.XamlMember, object, object) in ClrObjectRuntime.cs MS.Internal.Xaml.Runtime.ClrObjectRuntime.SetValue(object, System.Xaml.XamlMember, object) in ClrObjectRuntime.cs Inner Exception 1: InvalidOperationException: Cannot re-initialize ResourceDictionary instance.
补充:跨解决方案引用已编译的版本化Presentation.dll,使用pack://application:,,,/Presentation;component/Styles/MyStyles.xaml可正常加载,但项目引用版本化Presentation时仍报错;应用规模大,使用SharedResourceDictionary避免x86环境内存不足。
核心问题是项目引用模式下,WPF的资源加载机制在处理带版本的程序集时,会重复初始化SharedResourceDictionary实例。
当通过项目引用时,构建过程中WPF会尝试优化资源加载,而SharedResourceDictionary的设计是单例复用资源,版本号的引入改变了程序集的标识,导致加载逻辑冲突,触发Cannot re-initialize ResourceDictionary instance异常。
针对项目引用场景,可通过以下方式修复:
1. 显式指定程序集版本的pack URI
在Application.Resources中引用时,补充程序集版本信息到pack URI:
<ResourceDictionary Source="pack://application:,,,/Presentation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null;component/Styles/MyStyles.xaml" />
同时,MyStyles.xaml中的合并字典也需更新为带版本的URI:
<ResourceDictionary.MergedDictionaries> <styles:SharedResourceDictionary Source="pack://application:,,,/Presentation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null;component/Styles/ColorDefinitions.xaml"/> <styles:SharedResourceDictionary Source="pack://application:,,,/Presentation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null;component/Styles/FontDefinitions.xaml"/> </ResourceDictionary.MergedDictionaries>
如果后续版本变更,需同步更新URI中的版本号,或通过编译常量动态替换。
2. 修改SharedResourceDictionary的缓存键逻辑
默认SharedResourceDictionary通常以Source URI作为缓存键,带版本的URI会生成新键,导致重复加载。可修改SharedResourceDictionary的缓存逻辑,忽略版本号或使用程序集名称+资源路径作为缓存键:
public class SharedResourceDictionary : ResourceDictionary { private static readonly Dictionary<string, ResourceDictionary> _cache = new(); private Uri _source; public new Uri Source { get => _source; set { _source = value; var cacheKey = GetCacheKey(value); if (_cache.TryGetValue(cacheKey, out var dict)) { MergedDictionaries.Clear(); MergedDictionaries.Add(dict); } else { base.Source = value; _cache[cacheKey] = this; } } } private string GetCacheKey(Uri uri) { // 提取程序集名称和资源路径,忽略版本信息 var uriString = uri.ToString(); var componentIndex = uriString.IndexOf(";component/"); if (componentIndex > 0) { var assemblyPart = uriString.Substring(0, componentIndex); var assemblyName = assemblyPart.Split(',').First().Trim(); var resourcePath = uriString.Substring(componentIndex); return $"{assemblyName}{resourcePath}"; } return uriString; } }
此方式确保不同版本的同一资源使用同一个缓存键,避免重复初始化。
3. 禁用WPF项目的资源优化
在Presentation项目的.csproj中添加以下配置,禁用WPF的资源优化,避免加载逻辑冲突:
<PropertyGroup> <OptimizeResources>false</OptimizeResources> </PropertyGroup>
该配置会关闭WPF对资源的编译优化,可能影响少量加载性能,但能解决版本导致的初始化冲突。
内容的提问来源于stack exchange,提问作者Sergey Zabelin

