首页 > 其他分享 >WPF 冷知识 定义依赖属性的最大数量是 65534 个

WPF 冷知识 定义依赖属性的最大数量是 65534 个

时间:2024-09-12 21:03:51浏览次数:10  
标签:count 依赖 静态 65534 WPF 构造函数 public 属性

远古的 WPF 框架开发的大佬们认为没有任何业务的开发者需要用到超过 65534 个依赖属性和附加属性,为了节省内存空间就限制了所有的依赖属性和附加属性的定义总和加起来不能大于等于 65535 个

似乎大家可能对 65535 个依赖属性的定义量没有概念,这么说,即使只是将这些依赖属性定义出来,那代码的 cs 文件的大小也差不多有 20MB 这么大。敲黑板,这里的 65535 个依赖属性的定义量,指的是在代码里面定义 65535 个依赖属性或附加属性,指的是编写的代码,而和应用运行过程中创建多少个对象毫无关系

接下来咱来写一点有趣的代码来测试 WPF 的这个行为,先新建两个项目,一个是名为 LunallherbeanalLerejucahallyeler 的 WPF 项目,另一个是名为 KeeheekairbiQahairnairdacem 的控制台项目。将由控制台项目 KeeheekairbiQahairnairdacem 生成超过 65535 个依赖属性的定义的代码,用来给 LunallherbeanalLerejucahallyeler 项目引用

由于如此多的定义在一个类型里面,将会触发 CLR 层的异常,如果生成的代码都放在 MainWindow 类型里面,运行过程中大家将会看到如下异常

Type 'LunallherbeanalLerejucahallyeler.MainWindow' from assembly 'LunallherbeanalLerejucahallyeler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' contains more methods than the current implementation allows.

为了能够让这个逗比代码能够跑起来,于是接下来我拆分为 10 个类型,每个类型里面放入 7000 个依赖属性

而由于分了类型了,众所周知,依赖属性的定义默认放的是静态的属性。而静态的属性是由静态构造函数初始化的,静态构造函数又是需要在逻辑碰到静态字段等情况下才会执行的,这就意味着还需要给这 10 个类型投点毒,让这些类型的静态构造函数能够正确执行,从而创建出足够的依赖属性定义的静态字段

        foreach (var temp in new IBase[]
                 {
                   new Type0(),
                   new Type1(),
                   new Type2(),
                   new Type3(),
                   new Type4(),
                   new Type5(),
                   new Type6(),
                   new Type7(),
                   new Type8(),
                   new Type9(),
                 })
        {
            temp.Add();
        }

public interface IBase
{
    void Add();
}

public partial class Type1 : DependencyObject, IBase
{
    private static int _count = 0;
    public void Add()
    {
        // 写入静态字段只是为了触发静态构造函数
        _count++;
    }
}

public partial class Type2 : DependencyObject, IBase
{
    private static int _count = 0;
    public void Add()
    {
        // 写入静态字段只是为了触发静态构造函数
        _count++;
    }
}

public partial class Type3 : DependencyObject, IBase
{
    private static int _count = 0;
    public void Add()
    {
        // 写入静态字段只是为了触发静态构造函数
        _count++;
    }
}

public partial class Type0 : DependencyObject, IBase
{
    private static int _count = 0;
    public void Add()
    {
        // 写入静态字段只是为了触发静态构造函数
        _count++;
    }
}

public partial class Type5 : DependencyObject, IBase
{
    private static int _count = 0;
    public void Add()
    {
        // 写入静态字段只是为了触发静态构造函数
        _count++;
    }
}
public partial class Type4 : DependencyObject, IBase
{
    private static int _count = 0;
    public void Add()
    {
        // 写入静态字段只是为了触发静态构造函数
        _count++;
    }
}
public partial class Type6 : DependencyObject, IBase
{
    private static int _count = 0;
    public void Add()
    {
        // 写入静态字段只是为了触发静态构造函数
        _count++;
    }
}
public partial class Type7 : DependencyObject, IBase
{
    private static int _count = 0;
    public void Add()
    {
        // 写入静态字段只是为了触发静态构造函数
        _count++;
    }
}
public partial class Type8 : DependencyObject, IBase
{
    private static int _count = 0;
    public void Add()
    {
        // 写入静态字段只是为了触发静态构造函数
        _count++;
    }
}
public partial class Type9 : DependencyObject, IBase
{
    private static int _count = 0;
    public void Add()
    {
        // 写入静态字段只是为了触发静态构造函数
        _count++;
    }
}

接着为了显示出当前 WPF 框架里面注册的依赖属性数量,我还使用反射在界面显示当前的注册的依赖属性数量,如下面代码

        var propertyFromNameField = typeof(DependencyProperty).GetField("PropertyFromName", BindingFlags.Static | BindingFlags.NonPublic);
        var propertyFromName = (Hashtable) propertyFromNameField.GetValue(null);
        TextBlock.Text = $"依赖属性定义数量:{propertyFromName.Count}";

以上代码的 TextBlock 是定义在 MainWindow.xaml.cs 的控件,界面代码如下

    <Grid>
        <TextBlock x:Name="TextBlock" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>

接着编写 KeeheekairbiQahairnairdacem 控制台项目的代码,生成足够数量的依赖属性的定义,这部分代码没有什么难度,我就不贴在博客里面,大家可以在本文末尾找到全部代码的下载方法

这时候运行 WPF 项目,即可看到大概如下的异常

System.InvalidOperationException: 注册“Type9.Foo2176”时已超出 DependencyProperty 限制。依赖项属性通常是使用静态字段初始值设定项或静态构造函数注册的静态类成员。在这种情况下,可能会在实例构造函数中意外地初始化依赖项属性,从而导致超出最大限制。
   at System.Windows.DependencyProperty.GetUniqueGlobalIndex(Type ownerType, String name)
   at System.Windows.DependencyProperty..ctor(String name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
   at System.Windows.DependencyProperty.RegisterCommon(String name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
   at System.Windows.DependencyProperty.Register(String name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback)
   at System.Windows.DependencyProperty.Register(String name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata)

这就是因为定义的依赖属性超过了最大数量的限制,在 WPF 里面的 DependencyProperty 限制了最大的依赖属性和附加属性加起来的总数量,代码如下

    public sealed class DependencyProperty
    {
        internal static int GetUniqueGlobalIndex(Type ownerType, string name)
        {
            // Prevent GlobalIndex from overflow. DependencyProperties are meant to be static members and are to be registered
            // only via static constructors. However there is no cheap way of ensuring this, without having to do a stack walk. Hence
            // concievably people could register DependencyProperties via instance methods and therefore cause the GlobalIndex to
            // overflow. This check will explicitly catch this error, instead of silently malfuntioning.
            if (GlobalIndexCount >= (int)Flags.GlobalIndexMask)
            {
                if (ownerType != null)
                {
                    throw new InvalidOperationException(SR.Format(SR.TooManyDependencyProperties, ownerType.Name + "." + name));
                }
                else
                {
                    throw new InvalidOperationException(SR.Format(SR.TooManyDependencyProperties, "ConstantProperty"));
                }
            }

            // Covered by Synchronized by caller
            return GlobalIndexCount++;
        }

        private static int GlobalIndexCount;
    }

以上的 GlobalIndexCount 静态字段是用来表示当前定义的依赖属性或附加属性是第几个加入到 WPF 框架里面的,如果超过了 Flags.GlobalIndexMask 数量个,那将会抛出异常。这里的 GlobalIndexMask 就是 65535 个

大家都知道,在 WPF 里面的依赖属性和附加属性都是存放在类型里面的字典里面,而字典的查找是依赖于哈希算法的。为了能够让依赖属性既有足够快的查找速度且又对人类友好,于是定义了依赖属性包含了属性名字符串,还包含了从 GlobalIndexCount 静态字段算出的 GlobalIndex 索引值。通过 GlobalIndexCount 确保每个依赖属性定义都有独立且不重复的 GlobalIndex 索引值,如此即可实现依赖属性字典通过 int 作为 key 提升其性能

更具体一点,讲 WPF 的依赖属性和附加属性在底层使用字典存放是片面的,属于思想正确但具体实现不正确,具体的实现是在 WPF 底层存放了一个有序数组,这个数组通过上文说讲的依赖属性的 GlobalIndex 作为排序依据,如此即可通过折半查找算法快速找到命中的依赖属性对应的值

本文以上的代码放在githubgitee 欢迎访问

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

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 5c8a31243b7f2e1ad87f49b319dbab39d5d18f0e

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码

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

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

先运行 KeeheekairbiQahairnairdacem 项目一次,让其创建 LunallherbeanalLerejucahallyeler 所使用的代码,接着再运行 LunallherbeanalLerejucahallyeler 项目,即可看到本文的效果

标签:count,依赖,静态,65534,WPF,构造函数,public,属性
From: https://www.cnblogs.com/lindexi/p/17862779.html

相关文章

  • WPF 什么时候 VisualTreeHelper.GetDescendantBounds 将返回无穷大
    本文将和大家介绍在什么情况下WPF将会在调用VisualTreeHelper.GetDescendantBounds方法时,返回一个无穷大的范围尺寸在WPF的容器控件的里层元素的RenderTransform包含NaN将会导致对上层容器调用VisualTreeHelper.GetDescendantBounds返回无穷大返回的矩形范围是-∞,......
  • WPF 已知问题 开启 WM_Pointer 消息之后 获取副屏触摸数据坐标偏移
    本文记录WPF触摸的一个已知问题,仅在开启WM_Pointer消息之后,将应用程序运行在包含多个屏幕的带触摸屏的设备上,如此时在非主屏幕的触摸屏上进行触摸,使用GetStylusPoint或GetIntermediateTouchPoints方法获取触摸点时,将会发现所获取的触摸点的坐标是偏的,偏的坐标差值刚好是整......
  • WPF 已知问题 包含 NaN 的 Geometry 几何可能导致渲染层抛出 UCEERR_RENDERTHREADFAIL
    本文记录一个WPF已知问题,当传入到渲染的Geometry几何里面包含了NaN数值,将可能让应用程序收到从渲染层抛上来的UCEERR_RENDERTHREADFAILURE异常,且此异常缺乏必要信息,比较难定位到具体错误逻辑此问题是小伙伴报告给我的,详细请看https://github.com/dotnet/wpf/issues/7421......
  • WPF 尝试使用 WinML 做一个简单的手写数字识别应用
    最近我看了微软的AI训练营之后,似乎有点了解WindowsMachineLearning和DirectML的概念,于是我尝试实践一下,用WPF写一个简单的触摸手写输入的画板,再使用大佬训练好的mnist.onnx模型,对接WinML实现一个简单的手写数字识别应用本文属于WinML的入门级博客,我将尝试一步步......
  • WPF 的 WriteableBitmap 在 Intel 11 代 Iris Xe Graphics 核显设备上停止渲染
    在Intel11代锐炬Intel®Iris®XeGraphics核显设备上,如果此设备使用旧版本驱动,则可能导致WPF的WriteableBitmap停止渲染。此问题和WPF无关,此问题是Intel的bug且最新驱动版本已修复官方问题记录地址:https://www.intel.cn/content/www/cn/zh/support/articles/000......
  • WPF 的 Viewport3D 等 3D 模块在带 Intel UHD 770 设备上抛出渲染异常
    在带IntelUHD770的设备上,使用旧版本驱动,即小于30.0.101.1660版本驱动,将会导致WPF的3D模块出现渲染异常。此问题和WPF无关,此问题是Intel的bug且最新驱动版本已修复官方问题记录地址:https://community.intel.com/t5/Graphics/Crash-with-UHD-770-in-WPF-applicatio......
  • 【转】[C#][WPF] 避免窗口最大化时遮盖任务栏
    转自:https://learn.microsoft.com/zh-cn/previous-versions/msdn10/dd366102(v=MSDN.10)WPF窗口最大化时有个很不好的现象是:如果窗口的WindowStyle被直接或间接地设置为None后(比如很多情况下你会覆盖默认的窗体样式,即不采用Windows默认的边框和最大化最等按钮,来打造个性的窗......
  • 使用css属性—clip-path完成胶囊导航按钮
    使用css属性—clip-path完成胶囊导航按钮先看效果更多API上代码先看效果主要是为了实现胶囊内的颜色分割:更多APIclip-path属性常用的函数:API描述参数circle()创建一个圆形裁剪区域半径和圆心的坐标ellipse()创建一个椭圆形裁剪区域横轴和纵轴的半径以及圆心的坐......
  • CMake 设置 include 和 link 时的属性
    CMake设置include和link时的属性target_include_directories、target_link_libraries在CMake中,target_include_directories和target_link_libraries命令使用PRIVATE、PUBLIC和INTERFACE关键字来控制包含目录和链接库的传递性。PRIVATE:仅对目标自身可见。其他依......
  • VisualStudio 通过配置 DefaultXamlRuntime 属性 让控制台项目里的 XAML 应用上智能提
    本文记录一个VisualStudio黑科技,通过配置DefaultXamlRuntime属性,即可让非WPF或WinUI或MAUI等系列类型的项目也可以拥有XAML的智能提示,智能提示方式和WinUI智能提示行为相同比如说在一个控制台项目里面,我期望从控制台开始,定制自己的UI框架,比如说到现在还没有支持......