Unity使用自定义类型作为字典Key的最佳实践与避坑指南

news2024/9/23 15:29:56

在这里插入图片描述
自定义类型作为字典Key的最佳实践与避坑指南文章首发

问题背景

首先提一下之前项目开发时遇到的一个将自定义类型作为Dictionary键的坑。

项目中,我们有两个业务类BusinessA和BusinessB,因为某个需求,我们需要将这两个类建立一个映射关系,故引入字典Dictionary数据结构将它们关联起来:

private Dictionary<BusinessA, BusinessB> businessDic = new Dictionary<BusinessA, BusinessB>();

//对外提供查找方法,通过A从字典中找到B
 public BusinessB FindB(BusinessA a)
{
    if (businessDic.ContainsKey(a))
    {
        return businessDic[a];
    }
    return null;
}

因为项目是协同开发的,随着业务逻辑不断增加,在某些其他业务逻辑中对字典中的某些Key对象做了修改(BusinessA对象的数据不变),之后当我们再次想要通过该对象查找对应的BusniessB对象时,却发现返回null。当时我们整个开发组排查了好久才发现这个bug的原因。

问题分析

这个bug的根本原因在于没有深入的理解C#中的Dictionary的底层存储原理:

当你将一个键添加到字典中,Dictionary会使用哈希函数计算该键的哈希码,这是一个整数值,用于确定键在内部数组中的位置。

我们一开始以为如果将一个引用类型的对象作为Dictionary的key,只要key对象的数据没有变,那么就可以通过key获取对应的value。

使用Dictionary时,Key的必要条件

在C#中,字典(Dcitionary<Tkey,TValue>)使用将Key来快速查找值Value时,字典的键必须满足两个重要的条件:

  • 可比较性

键必须可以被正确地比较以确定其唯一性。字典内部使用Equals方法来比较键,确保相同的键映射到相同的值

  • 不可变性

一旦键被添加到字典中,其值不能被改变。如果键的状态在被添加到字典后发生变化,字典的查找机制可能会失效,从而引发bug.

使用引用类型作为Key的注意事项

使用不稳定的键

为了确保字典的正确性,键应该在字典操作期间保持不变。一般来说,应该避免在字典中修改键对象的状态。推荐的做法是使用不可变对象作为键。你可以使用 readonly 字段或只读属性来保证键对象的不可变性

键的相等性比较

确保同时并正确的实现你的 EqualsGetHashCode 方法。相等性比较应该考虑所有影响对象身份的字段,并且 GetHashCode 方法应该始终返回相同的值(如果对象的状态没有改变)。

可以看以下示例代码的输出:

public class BusinessA
{
    public int id;
    public string name;

    public BusinessA(int _id,string _name)
    {
        id = _id;
        name = _name;
    }

    public override bool Equals(object obj)
    {
        if (obj == null) return false;
        BusinessA another = obj as BusinessA;
        if (another != null)
        {
            return id == another.id && name == another.name;
        }
        return false;
    }
}

public class BusinessB
{
    private string describe;
    private string score;

    public BusinessB(string _des,string _score)
    {
        describe = _des;
        score = _score;
    }
}

public class DictionaryDemo : MonoBehaviour
{

    private Dictionary<BusinessA, BusinessB> businessDic = new Dictionary<BusinessA, BusinessB>();

    private BusinessA bA;
    private BusinessB bB;

    private BusinessA bAA;

    void Start()
    {
        int id = 100;
        string name = "zzz";
        //声明两个内部数据一样的对象bA和bAA分别作为存储的key,和查找的key
        bA = new BusinessA(id, name);
        bAA = new BusinessA(id, name);

        //声明value
        bB = new BusinessB("yyy", "100");
        //bA和bB将添加到字典中
        businessDic.Add(bA, bB);

        //使用bAA在字典中查找bB
        BusinessB findB = FindB(bAA);
        BusinessB findB1 = FindB(bA);
        //比较findB 是否等于bB
        Debug.Log(bB == findB);
    }

    public BusinessB FindB(BusinessA a)
    {
        if (businessDic.ContainsKey(a))
        {
            return businessDic[a];
        }
        return null;
    }
    
}

控制台输出:
image.png
由此可以,虽然两个对象bA和bAA的内部成员数据相同,但是并没有重写GetHashCode方法,所以这相当于是两个不同哈希值的对象,使用bAA无法找到Value返回null,所以bB不等于findB。

解决方案

由此得出解决方案:在使用字典存储键值对时,如果需要将自定义类型作为字典的键,那么该自定义类型应该重写并正确实现GetHashCode方法。

验证:
我们在BusinessA中重写GetHashCode方法:

	public override int GetHashCode()
    {
        return string.Format("{0}-{1}", id, name).GetHashCode();
    }

控制台输出:
image.png

由此可知,虽然bA和bAA是不同的对象引用,但是重写了GetHashCode方法之后,在字典查找时,就能正确匹配对应的key,所以能找出Value,findB和bB相等。

Equals和 GetHashCode 方法缺一不可。

总结

代码层面出现bug的时候,很多时候还是一些底层的逻辑没有搞懂,平时还是要多测试多验证多了解原理。所以说在使用字典作为存储查询数据结构时还是建议使用不可变类型作为键,如何值类型(int、float等)或者常用的引用类型string。如果一定要使用自定义类型作为字典的键,那么应该注意两点:1.避免在字典操作期间修改键;2.作为键Key的对象需要重写并正确实现Equals和GetHashCode方法。通过,遵循这些原则,可以避免由于自定义类型对象作为字典键而引起的bug.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2117203.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

游泳馆收银系统源码解析之手牌管理--SAAS本地化及未来之窗行业应用跨平台架构

一、代码 if(手牌状态 "空"){结算界面 "";未来之窗_人工智能_通用页面_尺寸(title"游泳馆",收费,500,300);}else{未来之窗_人工智能_通用页面_尺寸(title"游泳馆",退款,1200,500);} 二、阿雪技术观 拥抱开源与共享&#xff0c;见…

探索图论中的关键算法(Java 实现)

“日出东海落西山 愁也一天 喜也一天 遇事不钻牛角尖” 文章目录 前言文章有误敬请斧正 不胜感恩&#xff01;||Day031. 最短路径算法Dijkstra算法Java 实现&#xff1a; Bellman-Ford算法Java 实现&#xff1a; 2. 最小生成树算法Prim算法Java 实现&#xff1a; Kruskal算法Ja…

AI基础 L9 Local Search II 局部搜索

Local Beam search 对于当前的所有k个状态&#xff0c;生成它们的所有可能后继状态。 检查生成的后继状态中是否有任何状态是解决方案。 如果所有后继状态都不是解决方案&#xff0c;则从所有后继状态中选择k个最佳状态。 当达到预设的迭代次数或满足某个终止条件时&#x…

读软件设计的要素05概念的特性

1. 概念的特性 1.1. 专一性原则(specificity principle)认为概念与目的应该一一对应 1.1.1. 专一性原则已被证明是概念设计中最有用的原则之一 1.1.2. 一个概念最多只能满足一个目的 1.2. 很少有没有目的的概念 1.2.1. 如果本应隐藏的用户机制被暴露&#xff0c;可能会产生…

通信工程学习:什么是2ASK/BASK二进制振幅键控

2ASK/BASK&#xff1a;二进制振幅键控 2ASK/BASK二进制振幅键控是一种数字调制技术&#xff0c;其全称是二进制振幅键控&#xff08;Binary Amplitude Shift Keying&#xff09;。该技术通过改变载波的振幅来传递二进制数字信息&#xff0c;而载波的频率和相位则保持不变。以下…

RISC-V (九)抢占式多任务

主要的思想&#xff1a;借用定时器中断实现。设置定时器寄存器&#xff0c;系统自动触发定时器中断时会跳到trap handler这个函数里。借用这个函数做上下文的切换&#xff0c;从而实现了抢占式多任务。 定时器中断&#xff1a;跳到trap handler函数&#xff0c;同时系统自动将…

清华计算几何--凸Polygon的相交问题

凸Polygon和相交定义 本节只讨论凸Polygon的问题&#xff0c;不涉及凹Polygon. 相交包含了边相交和完全包含。 凸Polygon相交的两个问题 Detection(检测) 判断两个凸Polygon是否相交&#xff0c;至于相交部分是什么不关心. Construction(构造) 求出两个凸Polygon具体相交…

Linux_kernel移植rootfs10

一、动态更改内核 1、low level&#xff08;静态修改&#xff09; 【1】将led_drv.c拷贝到kernel/drivers/char/目录中 【2】修改当前目录下的Makefile文件 obj-y led_drv.o #将新添加的驱动文件加入到Makefile文件中 【3】退回kernel目录&#xff0c;执行make uImage …

熬夜后补救措施

人体的肝功能问题 直接体现在体态和容颜上 伤肝 三大坏行为 熬夜后补救 *补充养b族、口、、锌、硒 加强代谢 能力 (1)另外熬夜后一定要多喝水 提升身体代谢能力 (2)谷肤甘肽清肝 肝脏排毒&#xff0c;减轻负拒 (3)水飞前含量高点 &#xff08;4)熬夜出更多油 容易长痘 需要清…

标准库标头 <filesystem> (C++17)学习之文件类型

本篇介绍filesystem文件库的文件类型API。 文件类型 is_block_file (C17) 检查给定的路径是否表示块设备 (函数) is_character_file (C17) 检查给定的路径是否表示字符设备 (函数) is_directory (C17) 检查给定的路径是否表示一个目录 (函数) is_empty (C17) 检查给定的路径是…

STM32G474之使用DAC1和DAC2测试模拟比较器

STM32G474使用DAC1和DAC2的输出作为比较器输入&#xff0c;测试模拟比较器&#xff0c;方法如下&#xff1a; PA1的附加功能为COMP1_INP&#xff0c;无需映射&#xff0c;直接将它配置为模拟功能&#xff0c;就可以使用了。 将COMP1_OUT引脚映射到PA0; 采用DAC2_OUT1输出电压给…

【大疆 SDR 图传 P1 】 功能拆解,通信功能剖析

大疆 SDR 图传 P1 拆解视频P1 SoC1、哲酷2、小米3、大疆&#xff08;文章主角&#xff09; 一、为什么说SDR技术1、sdr 软件无线电2、影视博主的测评方法3、第一个说自己SDR的还是这个老登 二、大疆的图传发展历程1、FPGA AD93632、 P1 自研1、2个DSP和一个CPU A72、音频子系统…

SpringMVC;MVC模式;Spring环境搭建;

一&#xff0c;介绍MVC模式&#xff1a; MVC模式&#xff1a; 1.M:model 模型,业务模型和数据模型. 2.C:controller 控制器 3.V:view 视图 优点: 使用控制器C把视图V和业务模型M分离&#xff0c;从而使同一个程序可以使用不同的表现形式 使用场景: 中大型项目 核心: 控制器 二…

828华为云征文 | 基于Docker与Jenkins实现自动化部署

需要了解 本文章主要讲述在 华为云Flexus X 实例上使用docker快速部署持续集成工具 Jenkins&#xff0c;通过插件来自动化CI/CD过程中的各种琐碎功能。选择合适的云服务器&#xff1a; 本文采用的是 华为云服务器 Flexus X 实例&#xff08;推荐使用&#xff09;连接方式&#…

【自动驾驶】决策规划算法 | 数学基础(三)直角坐标与自然坐标转换Ⅰ

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…

【有啥问啥】数字孪生(Digital Twin)技术在人工智能中的应用

数字孪生技术在人工智能中的应用 在当今的数字化转型过程中&#xff0c;“数字孪生”技术逐渐成为热门话题&#xff0c;并且在各个行业中展现出巨大的潜力。作为一种新兴技术&#xff0c;数字孪生&#xff08;Digital Twin&#xff09;不仅仅是物理对象的虚拟复制品&#xff0…

MATLAB算法实战应用案例精讲-【人工智能】大数据审计(概念篇)

目录 前言 大数据审计发展历程 1.初级阶段:验证型逻辑占据主导地位 2.发展阶段:挖掘型逻辑突出重围 3.成熟阶段:基于验证和挖掘的预测型逻辑发展 算法原理 什么是大数据审计 特征 事项审计 大数据审计的方法 (一)大数据审计的一般思路 (二)大数据审计的关键技术…

【开发工具】探索IntelliJ IDEA插件——JSON Parser,让JSON处理变得轻松高效

开发过程中&#xff0c;遇到一个字符串&#xff0c;需要判断是否是JSON格式&#xff0c;或者是需要将Json字符串美化展示&#xff0c;是否还在打开百度搜JSON在线格式化(https://www.bejson.com/)&#xff0c;是否还在写个main方法将字符串转成JSON格式并输出。这篇文章&#x…

【Linux】全面讲解 Shell 变量的那些事

本文内容均来自个人笔记并重新梳理&#xff0c;如有错误欢迎指正&#xff01; 如果对您有帮助&#xff0c;烦请点赞、关注、转发、订阅专栏&#xff01; 专栏订阅入口 Linux 专栏 | Docker 专栏 | Kubernetes 专栏 往期精彩文章 【Docker】&#xff08;全网首发&#xff09;Kyl…

python中的循环结构

注意&#xff1a;range&#xff08;&#xff09;函数 累加和&#xff1a; 注意&#xff1a;if 下面如果有好几行&#xff0c;只执行一行 print必须和 for 开头相同格数 例题&#xff1a;水仙花数 注意在print语句中&#xff0c;一句好“ 。。。。。 ”后面必须有逗号然后再写变…