C# 实现一个基于值相等性比较的字典

Intro

今天在项目里遇到一个需求,大概是这样的我要比较两个 JSON 字符串是不是相等,JSON 字符串其实是一个 Dictionary<string, string> 但是顺序可能不同,和上一篇 record 使用场景中的第一个需求类似,前面我们介绍过使用 record 可以比较方便的解决,但是我们的项目是 .netcoreapp3.1 的,不能使用 record,如何比较方便的比较呢?我们能否自己实现一个类似于 record 的类型,基于值去比较呢?于是就有了本文的探索

StringValueDictioanry

实现了一个基于值进行比较的字典,实现代码如下,实现的比较简单,涉及到一些简单的知识点,平时不怎么用已经忘了怎么写了,通过写下面的代码又学习了一下

先来看测试代码吧,测试代码如下:

[Fact]
public void EqualsTest()
{
var abc = new { Id = 1, Name = "Tom" };
var dic1 = StringValueDictionary.FromObject(abc);
var dic2 = StringValueDictionary.FromObject(new Dictionary<string, object>()
{
{"Name", "Tom" },
{"Id", 1},
}); Assert.True(dic1 == dic2);
Assert.Equal(dic1, dic2);
} [Fact]
public void DistinctTest()
{
var abc = new { Id = 1, Name = "Tom" };
var dic1 = StringValueDictionary.FromObject(abc);
var dic2 = StringValueDictionary.FromObject(new Dictionary<string, object>()
{
{"Id", 1},
{"Name", "Tom" },
});
var set = new HashSet<StringValueDictionary>();
set.Add(dic1);
set.Add(dic2); Assert.Single(set);
} [Fact]
public void CloneTest()
{
var dic1 = StringValueDictionary.FromObject(new Dictionary<string, object>()
{
{"Id", 1},
{"Name", "Tom" }
});
var dic2 = dic1.Clone();
Assert.False(ReferenceEquals(dic1, dic2));
Assert.True(dic1 == dic2);
} [Fact]
public void ImplicitConvertTest()
{
var abc = new { Id = 1, Name = "Tom" };
var stringValueDictionary = StringValueDictionary.FromObject(abc);
Dictionary<string, string> dictionary = stringValueDictionary;
Assert.Equal(stringValueDictionary.Count, dictionary.Count); var dic2 = StringValueDictionary.FromObject(dictionary); Assert.Equal(dic2, stringValueDictionary);
Assert.True(dic2 == stringValueDictionary);
}

从上面的代码可能大概能看出一些实现,重写了默认的 EqualsGetHashCode,并重载了“==” 运算符,并且实现了一个从 StringValueDictionaryDictionary 的隐式转换,来看下面的实现代码:

public sealed class StringValueDictionary : IEquatable<StringValueDictionary>
{
private readonly Dictionary<string, string?> _dictionary = new(); private StringValueDictionary(IDictionary<string, string?> dictionary)
{
foreach (var pair in dictionary)
{
_dictionary[pair.Key] = pair.Value;
}
} private StringValueDictionary(StringValueDictionary dictionary)
{
foreach (var key in dictionary.Keys)
{
_dictionary[key] = dictionary[key];
}
} public static StringValueDictionary FromObject(object obj)
{
if (obj is null) throw new ArgumentNullException(nameof(obj));
if (obj is IDictionary<string, string?> dictionary)
{
return new StringValueDictionary(dictionary);
}
if (obj is IDictionary<string, object?> dictionary2)
{
return new StringValueDictionary(dictionary2.ToDictionary(p => p.Key, p => p.Value?.ToString()));
}
if (obj is StringValueDictionary dictionary3)
{
return new StringValueDictionary(dictionary3);
}
return new StringValueDictionary(obj.GetType().GetProperties()
.ToDictionary(p => p.Name, p => p.GetValue(obj)?.ToString()));
} public static StringValueDictionary FromJson(string json)
{
Guard.NotNull(json, nameof(json));
var dic = json.JsonToObject<Dictionary<string, object?>>()
.ToDictionary(x => x.Key, x => x.Value?.ToString());
return new StringValueDictionary(dic);
} public StringValueDictionary Clone() => new(this); public int Count => _dictionary.Count; public bool ContainsKey(string key) => _dictionary.ContainsKey(key) ? _dictionary.ContainsKey(key) : throw new ArgumentOutOfRangeException(nameof(key)); public string? this[string key] => _dictionary[key]; public Dictionary<string, string>.KeyCollection Keys => _dictionary.Keys!; public bool Equals(StringValueDictionary? other)
{
if (other is null) return false;
if (other.Count != Count) return false;
foreach (var key in _dictionary.Keys)
{
if (!other.ContainsKey(key))
{
return false;
}
if (_dictionary[key] != other[key])
{
return false;
}
}
return true;
} public override bool Equals(object obj)
{
return Equals(obj as StringValueDictionary);
} public override int GetHashCode()
{
var stringBuilder = new StringBuilder();
foreach (var pair in _dictionary)
{
stringBuilder.Append($"{pair.Key}={pair.Value}_");
}
return stringBuilder.ToString().GetHashCode();
} public static bool operator ==(StringValueDictionary? current, StringValueDictionary? other)
{
return current?.Equals(other) == true;
} public static bool operator !=(StringValueDictionary? current, StringValueDictionary? other)
{
return current?.Equals(other) != true;
} public static implicit operator Dictionary<string, string?>(StringValueDictionary dictionary)
{
return dictionary._dictionary;
}
}

More

上述代码实现的有点粗糙,可能会有一些问题,仅供参考

以上代码基本实现了基于想要的值的相等性比较以及 Clone(复制、克隆)的目标

实现相等性比较的时候,EqualsGetHashCode 方法也要重写,如果没有重写 GetHashCode,编译器也会给出警告,如果没有重写 GetHashCode 在实际在 HashSet 或者 Dictionary 里可能会出现重复 key

重载运算符的时候需要一个静态方法,"==" 和 "!=" 是一对操作运算符,如果要实现两个都要实现,不能只实现其中一个

References

C# 实现一个基于值相等性比较的字典的更多相关文章

  1. 一个基于mysql构建的队列表

    通常大家都会使用redis作为应用的任务队列表,redis的List结构,在一段进行任务的插入,在另一端进行任务的提取. 任务的插入 $redis->lPush("key:task:l ...

  2. 关于实现一个基于文件持久化的EventStore的核心构思

    大家知道enode框架的架构是基于ddd+event sourcing的思想.我们持久化的不是聚合根的最新状态,而是聚合根产生的领域事件.最近我在思考如何实现一个基于文件的eventstore.目标有 ...

  3. CXF 入门:创建一个基于WS-Security标准的安全验证(CXF回调函数使用,)

    http://jyao.iteye.com/blog/1346547 注意:以下客户端调用代码中获取服务端ws实例,都是通过CXF 入门: 远程接口调用方式实现 直入正题! 以下是服务端配置 ==== ...

  4. 开源一个基于nio的java网络程序

    因为最近要从公司离职,害怕用nio写的网络程序没有人能看懂(或许是因为写的不好吧),就调整成了mina(这样大家接触起来非常方便,即使没有socket基础,用起来也不难),所以之前基于nio写的网络程 ...

  5. 一个基于.NET平台的自动化/压力测试系统设计简述

    AutoTest系统设计概述 AutoTest是一个基于.NET平台实现的自动化/压力测试的系统,可独立运行于windows平台下,支持分布式部署,不需要其他配置或编译器的支持.(本质是一个基于协议的 ...

  6. [转]一个基于完成端口的TCP Server Framework,浅析IOCP

    [转]一个基于完成端口的TCP Server Framework,浅析IOCP http://www.cppblog.com/adapterofcoms/archive/2010/06/26/1187 ...

  7. 一个基于MVVM的TableView组件化实现方案

    AITableView https://github.com/chentoo/AITableView cocoapods: pod ‘AITableView’ 做什么用? 这是一个简化UITableV ...

  8. 构建一个基于 Spring 的 RESTful Web Service

    本文详细介绍了基于Spring创建一个“hello world” RESTful web service工程的步骤. 目标 构建一个service,接收如下HTTP GET请求: http://loc ...

  9. 一个基于STSdb和fastJson的磁盘/内存缓存

    一个基于STSdb和fastJson的磁盘/内存缓存 需求 业务系统用的是数据库,数据量大,部分只读或相对稳定业务查询复杂,每次页面加载都要花耗不少时间(不讨论异步),觉得可以做一下高速缓存,譬如用n ...

随机推荐

  1. ubuntu虚拟机启用双网卡IP配置

    首先要登入自己的虚拟机,这里以ubuntu为例. 配置两块网卡,一块eth0为NAT模式,另一块为eth1仅主机模式 # 进入网卡配置页面vi /etc/network/interfaces # Th ...

  2. webapplicationContext之ServletContext等相关概念说明

    1)ServletContext是一个全局的储存信息的空间,所有用户共用一个,其信息必须是线程安全且共享的. ServletContext有一个接口定义:ServletContext接口.此接口定义了 ...

  3. linux中的dmesg命令以及确定进程是否被系统主动kill

    linux中的dmesg命令以及确定进程是否被系统主动kill Feb 21, 2017 | java | 185 Hits 近期发现线上项目的进程莫名其妙的就不见了,也没有崩溃日志,就怀疑是被操作系 ...

  4. Thymeleaf语法总结 | 笔记分享

    Thymeleaf语法总结 一.Thymeleaf介绍 Thymeleaf是Spring boot推荐使用的模版引擎,直接以html显示,前后端可以很好的分离.   二.Thymeleaf语法(Thy ...

  5. 一个简单的springboot+mybatis-plus+thymeleaf的学生管理系统

    一.登录功能 1.1登录所涉及的功能主要包括拦截器,过滤器,用户在未登录的时候,访问页面会阻止访问的,如图所示: 实现这个功能的主要代码如下所示 1 //拦截器 2 public class Logi ...

  6. 不想加班开发管理后台了,试试这个 Java 开源项目吧!

    本文适合有 Java 基础并了解 SpringBoot 框架的同学 本文作者:HelloGitHub-嘉文 这里是 HelloGitHub 推出的<讲解开源项目>系列,今天给大家带来一款开 ...

  7. C#扫盲篇(一):反射机制--情真意切的说

    在一线编码已有多年,积累了不少非常实用的技能,最近的更新会逐步的分享出来,希望能帮助到还有一丢丢喜欢.Net的朋友,当然这些都比较适合入门选手,虽然自己已是个精通抄代码的老猿,但技术造诣仍是渣渣. 犹 ...

  8. NOIP初赛篇——04计算机软件系统

    计算机软件是指计算机系统中的程序及其文档,也是用户与硬件之间的接口,用户主要通过软件与计算机进行交流,软件是计算机的灵魂.没有安装软件的计算机称为"裸机",无法完成任何工作.一般软 ...

  9. fastjson反序列化漏洞原理及利用

    重要漏洞利用poc及版本 我是从github上的参考中直接copy的exp,这个类就是要注入的类 import java.lang.Runtime; import java.lang.Process; ...

  10. 【C++】《C++ Primer 》第十八章

    第十八章 用于大型程序的工具 大规模应用程序的特殊要求包括: 在独立开发的子系统之间协同处理错误的能力. 使用各种库进行协同开发的能力. 对比较复杂的应用概念建模的能力. 一.异常处理 异常处理(ex ...