Skip to content

dotnet 读 WPF 源代码笔记 为什么自定义的 UserControl 用户控件不能跨程序集继承

Updated: at 08:22,Created: at 00:20

从设计上,用户控件 UserControl 就不是一个合适用来多次继承的类型,更不要说进行跨程序集继承自定义的 UserControl 用户控件。对于大部分的用户控件来说,都是采用组合现有的控件来实现的功能,本身应该被当成一个模块来进行使用。在 WPF 框架里面,从框架层阻止了开发者对自定义的 UserControl 用户控件跨程序集继承的逻辑,一旦尝试进行跨程序集继承,将在运行时抛出异常。本文将从源代码的角度告诉大家 WPF 框架是如何阻止跨程序集继承

先来写一些演示使用的代码,新建一个 WpfLibrary1 项目用来存放自定义的用户控件。在 WpfLibrary1 项目里面新建一个 UserControl1.xaml 的用户控件

接着再新建一个叫 RukarcaheenereRelchairnalfe 的 WPF 项目,在这里面写一个叫 Foo 类型,让 Foo 类型继承 UserControl1 用户控件

public class Foo : UserControl1
{
public Foo()
{
}
}

在 MainWindow.xaml 里,将 Foo 加入到界面

<Grid>
<local:Foo></local:Foo>
</Grid>

运行代码,可以看到抛出 System.Windows.Markup.XamlParseException 异常,内容如下

Exception: 组件“RukarcaheenereRelchairnalfe.Foo”不具有由 URI“/WpfLibrary1;component/usercontrol1.xaml”识别的资源。

以上的异常的大概含义就是定义的 /WpfLibrary1;component/usercontrol1.xaml 所在的程序集和 Foo 所在的程序集不是相同的一个程序集,在 WPF 框架层面禁止跨程序集继承自定义用户控件。更本质来说是禁止跨程序集加载 XAML 定义的界面资源

本文测试代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 9bcae76c2910b4dfb4b1e0ba02d59876c614fbb1

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

获取代码之后,进入 RukarcaheenereRelchairnalfe 文件夹

通过断点调试,可以看到这个异常是从 InitializeComponent 方法里面抛出的。而此 InitializeComponent 方法是 WPF 的生成代码,实际代码放在 xx.g.i.cs 文件里面,里面的代码大概如下

public void InitializeComponent()
{
if (_contentLoaded)
{
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/WpfLibrary1;component/usercontrol1.xaml", System.UriKind.Relative);
System.Windows.Application.LoadComponent(this, resourceLocater);
}

实际会抛出异常的就是 System.Windows.Application.LoadComponent 这句代码

进入 WPF 的开源仓库,可以看到 LoadComponent 的实现如下,以下代码删掉了细节部分

public class Application : DispatcherObject, IHaveResources, IQueryAmbient
{
public static void LoadComponent(Object component, Uri resourceLocator)
{
Uri currentUri = new Uri(BaseUriHelper.PackAppBaseUri, resourceLocator);
PackagePart part = GetResourceOrContentPart(resourceLocator);
Stream stream = null;
stream = part.GetSeekableStream();
IStreamInfo bamlStream = stream as IStreamInfo;
if (bamlStream == null || bamlStream.Assembly != component.GetType().Assembly)
{
throw new Exception(SR.Get(SRID.UriNotMatchWithRootType, component.GetType( ), resourceLocator));
}
// 忽略其他代码
}
}

传入的 resourceLocator 就是 /WpfLibrary1;component/usercontrol1.xaml 的值,拿到的 bamlStream 的程序集是 WpfLibrary1 程序集

而 component 是定义在 RukarcaheenereRelchairnalfe 项目的类型,自然拿到的 component.GetType().Assembly 就是 RukarcaheenereRelchairnalfe 程序集

于是在 WPF 框架里面判断的 bamlStream.Assembly != component.GetType().Assembly 成立,抛出异常

也就是说,在 UserControl1 里面,采用的 /WpfLibrary1;component/usercontrol1.xaml 是期望从 WpfLibrary1 程序集获取对应的 XAML 定义资源(准确来说是 BAML 资源)进行加载。但实际的调用类型,却发现是继承的类型,放在另一个程序集,不符合框架设计的预期,抛出异常

这就是为什么自定义的 UserControl 用户控件不能跨程序集继承的原因

在 WPF 的 LoadComponent 方法是比较复杂的,本文只是将里面相关代码写出来,具体是如何调用的,我是通过调试的方法了解的

调试的方式我录了视频放在哔哩哔哩,请看 为什么自定义的 UserControl 用户控件不能跨程序集继承_哔哩哔哩_bilibili


知识共享许可协议

原文链接: http://blog.lindexi.com/post/dotnet-%E8%AF%BB-WPF-%E6%BA%90%E4%BB%A3%E7%A0%81%E7%AC%94%E8%AE%B0-%E4%B8%BA%E4%BB%80%E4%B9%88%E8%87%AA%E5%AE%9A%E4%B9%89%E7%9A%84-UserControl-%E7%94%A8%E6%88%B7%E6%8E%A7%E4%BB%B6%E4%B8%8D%E8%83%BD%E8%B7%A8%E7%A8%8B%E5%BA%8F%E9%9B%86%E7%BB%A7%E6%89%BF

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。 欢迎转载、使用、重新发布,但务必保留文章署名 林德熙 (包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我 联系