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,并重载了“==” 运算符,并且实现了一个从 StringValueDictionary Dictionary 的隐式转换,来看下面的实现代码:

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

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

implicit 也算是一个特殊的运算符,巧妙的使用隐式转换可以大大简化代码的写法,StackExchange.Redis 中就使用了 implicit 来实现 RedisValuestring 等其他常用类型的隐式转换

References

https://github.com/WeihanLi/WeihanLi.Common/blob/dev/src/WeihanLi.Common/Models/StringValueDictionary.cs
https://github.com/WeihanLi/WeihanLi.Common/blob/dev/test/WeihanLi.Common.Test/ModelsTest/StringValueDictionaryTest.cs

以上就是C# 如何实现一个基于值相等性比较的字典的详细内容,更多关于c# 实现值相等性比较的字典的资料请关注我们其它相关文章!

(0)

相关推荐

  • c#实现16进制和字符串之间转换的代码

    十六进制字符串与数值类型之间转换(C# 编程指南) 以下示例演示如何执行下列任务: 获取字符串中每个字符的十六进制值. 获取与十六进制字符串中的每个值对应的字符. 将十六进制 string 转换为整型. 将十六进制 string 转换为浮点型. 将字节数组转换为十六进制 string. 示例 此示例输出 string 中的每个字符的十六进制值.首先,它将 string 分析为字符数组,然后对每个字符调用 ToInt32(Char) 以获取相应的数字值.最后,在 string 中将数字的格式设置为

  • c#使用多线程的几种方式示例详解

    (1)不需要传递参数,也不需要返回参数 ThreadStart是一个委托,这个委托的定义为void ThreadStart(),没有参数与返回值. 复制代码 代码如下: class Program { static void Main(string[] args) { for (int i = 0; i < 30; i++) { ThreadStart threadStart = new ThreadStart(Calculate); Thread thread = new Thread(thr

  • c# record的使用场景

    Intro 之前我们有介绍过 record 基本知识,record 会实现基于值的类型比较,最近遇到的几个问题觉得用 record 来解决会非常方便,分享一下 基于值的类型比较 最近有遇到一个场景,需要比较两个 JSON 字符串是否相等,字符串比较简单,就是一个固定值的 Dictionary,或者认为它就是一个简单的 Model,但是 JSON 字符串的的属性顺序可能不同,比如说下面的这个示例: {"Id":1, "Name":"Tom"}, {

  • 使用C#9中records作为强类型ID的实例教程

    强类型ID 实体通常是整数,GUID或者string类型,因为数据库直接支持这些类型,但是,如果实体的ID的类型是一样的,比如都是整数的ID,这有可能会出现ID值传错的问题,看下边的示例. public void AddProductToOrder(int orderId, int productId, int count) { ... } ... // 这个地方,参数传错了 AddProductToOrder(productId, orderId, int count); 上面的代码可以很好地

  • C#连接MySql数据库的方法

    1.要连接MySql数据库必须首先下载MySql官方的连接.net的文件,文件下载地址为http://dev.mysql.com/downloads/connector/net/6.6.html#downloads ,下载平台选择.Net&Mono,下载ZIP免安装版.2.解压缩刚才下载的mysql-connector-net-6.6.6-noinstall.zip文件,里面有几个版本选择,在这里我选V4, 选中这几个文件,然后添加到C#项目的引用中,然后就可以编写程序进行数据库的操作了. 3.

  • c#的dllimport使用方法详解

    DllImport是System.Runtime.InteropServices命名空间下的一个属性类,其功能是提供从非托管DLL导出的函数的必要调用信息 DllImport属性应用于方法,要求最少要提供包含入口点的dll的名称.DllImport的定义如下: 复制代码 代码如下: [AttributeUsage(AttributeTargets.Method)]public class DllImportAttribute: System.Attribute{public DllImportA

  • C# 9 中新加入的关键词 init,record,with

    一:背景 1. 讲故事 .NET5 终于在 2020-08-25 也就是大前天发布了第八个预览版,这么多的预览版搞得我都麻木了,接踵而来的就是更多的新特性加入到了 C# 9 中,既然还想呆在这条船上,得继续硬着头皮学习哈,这一篇跟大家聊聊新增的几个关键词. 二:新增关键词 1. init 出来一个新语法糖,首先要做的就是去揭它的老底,这样可以方便推测它的应用场景,为了方便表述,我先上一个例子: public class Person { public string Name { get; ini

  • C# 一个WCF简单实例

    WCF实例(带步骤) 复制代码 代码如下: <xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" /> 本篇转自百度文档,自己试过,确实可以用. 以订票为例简单应用wcf 新建一个wcf服务应用程序 在IService1.cs定义服务契约 复制代码 代码如下: namespace WcfDemo { // 注意: 如果更改此处的接口名称 "IService

  • C#中List〈string〉和string[]数组之间的相互转换

    1,从System.String[]转到List<System.String> System.String[] str={"str","string","abc"}; List<System.String> listS=new List<System.String>(str); 2, 从List<System.String>转到System.String[] List<System.Strin

  • C# 正则表达式 使用介绍

    为了避免以后这样的情况,在此记录下正则表达式的一些基本使用方法附带小的实例.让以后在使用时能一目了然知道他的使用,为开发节约时间,同时也分享给大家 正则元字符 在说正则表达式之前我们先来看看通配符,我想通配符大家都用过.通配符主要有星号(*)和问号(?),用来模糊搜索文件.winodws中我们常会使用搜索来查找一些文件.如:*.jpg,XXX.docx的方式,来快速查找文件.其实正则表达式和我们通配符很相似也是通过特定的字符匹配我们所要查询的内容信息.已下代码都是区分大小写. 常用元字符 代码

随机推荐