今天在项目里遇到一个需求,大概是这样的我要比较两个JSON字符串是不是相等,JSON字符串其实是一个Dictionarystring,string但是顺序可能不同,和上一篇record使用场景中的第一个需求类似,前面我们介绍过使用record可以比较方便的解决,但是我们的项目是.netcoreapp3.1的,不能使用record,如何比较方便的比较呢?我们能否自己实现一个类似于record的类型,基于值去比较呢?于是就有了本文的探索
StringValueDictioanry实现了一个基于值进行比较的字典,实现代码如下,实现的比较简单,涉及到一些简单的知识点,平时不怎么用已经忘了怎么写了,通过写下面的代码又学习了一下
先来看测试代码吧,测试代码如下:
[Fact]publicvoidEqualsTest(){varabc=new{Id=1,Name="Tom"};vardic1=StringValueDictionary.FromObject(abc);vardic2=StringValueDictionary.FromObject(newDictionarystring,object(){{"Name","Tom"},{"Id",1},});Assert.True(dic1==dic2);Assert.Equal(dic1,dic2);}[Fact]publicvoidDistinctTest(){varabc=new{Id=1,Name="Tom"};vardic1=StringValueDictionary.FromObject(abc);vardic2=StringValueDictionary.FromObject(newDictionarystring,object(){{"Id",1},{"Name","Tom"},});varset=newHashSetStringValueDictionary();set.Add(dic1);set.Add(dic2);Assert.Single(set);}[Fact]publicvoidCloneTest(){vardic1=StringValueDictionary.FromObject(newDictionarystring,object(){{"Id",1},{"Name","Tom"}});vardic2=dic1.Clone();Assert.False(ReferenceEquals(dic1,dic2));Assert.True(dic1==dic2);}[Fact]publicvoidImplicitConvertTest(){varabc=new{Id=1,Name="Tom"};varstringValueDictionary=StringValueDictionary.FromObject(abc);Dictionarystring,stringdictionary=stringValueDictionary;Assert.Equal(stringValueDictionary.Count,dictionary.Count);vardic2=StringValueDictionary.FromObject(dictionary);Assert.Equal(dic2,stringValueDictionary);Assert.True(dic2==stringValueDictionary);}
从上面的代码可能大概能看出一些实现,重写了默认的Equals和GetHashCode,并重载了“==”运算符,并且实现了一个从StringValueDictionary到Dictionary的隐式转换,来看下面的实现代码:
publicsealedclassStringValueDictionary:IEquatableStringValueDictionary{privatereadonlyDictionarystring,string?_dictionary=new();privateStringValueDictionary(IDictionarystring,string?dictionary){foreach(varpairindictionary){_dictionary[pair.Key]=pair.Value;}}privateStringValueDictionary(StringValueDictionarydictionary){foreach(varkeyindictionary.Keys){_dictionary[key]=dictionary[key];}}publicstaticStringValueDictionaryFromObject(objectobj){if(objisnull)thrownewArgumentNullException(nameof(obj));if(objisIDictionarystring,string?dictionary){returnnewStringValueDictionary(dictionary);}if(objisIDictionarystring,object?dictionary2){returnnewStringValueDictionary(dictionary2.ToDictionary(p=p.Key,p=p.Value?.ToString()));}if(objisStringValueDictionarydictionary3){returnnewStringValueDictionary(dictionary3);}returnnewStringValueDictionary(obj.GetType().GetProperties().ToDictionary(p=p.Name,p=p.GetValue(obj)?.ToString()));}publicstaticStringValueDictionaryFromJson(stringjson){Guard.NotNull(json,nameof(json));vardic=json.JsonToObjectDictionarystring,object?().ToDictionary(x=x.Key,x=x.Value?.ToString());returnnewStringValueDictionary(dic);}publicStringValueDictionaryClone()=new(this);publicintCount=_dictionary.Count;publicboolContainsKey(stringkey)=_dictionary.ContainsKey(key)?_dictionary.ContainsKey(key):thrownewArgumentOutOfRangeException(nameof(key));publicstring?this[stringkey]=_dictionary[key];publicDictionarystring,string.KeyCollectionKeys=_dictionary.Keys!;publicboolEquals(StringValueDictionary?other){if(otherisnull)returnfalse;if(other.Count!=Count)returnfalse;foreach(varkeyin_dictionary.Keys){if(!other.ContainsKey(key)){returnfalse;}if(_dictionary[key]!=other[key]){returnfalse;}}returntrue;}publicoverrideboolEquals(objectobj){returnEquals(objasStringValueDictionary);}publicoverrideintGetHashCode(){varstringBuilder=newStringBuilder();foreach(varpairin_dictionary){stringBuilder.Append($"{pair.Key}={pair.Value}_");}returnstringBuilder.ToString().GetHashCode();}publicstaticbooloperator==(StringValueDictionary?current,StringValueDictionary?other){returncurrent?.Equals(other)==true;}publicstaticbooloperator!=(StringValueDictionary?current,StringValueDictionary?other){returncurrent?.Equals(other)!=true;}publicstaticimplicitoperatorDictionarystring,string?(StringValueDictionarydictionary){returndictionary._dictionary;}}More
上述代码实现的有点粗糙,可能会有一些问题,仅供参考
以上代码基本实现了基于想要的值的相等性比较以及Clone(复制、克隆)的目标
实现相等性比较的时候,Equals和GetHashCode方法也要重写,如果没有重写GetHashCode,编译器也会给出警告,如果没有重写GetHashCode在实际在HashSet或者Dictionary里可能会出现重复key
重载运算符的时候需要一个静态方法,"=="和"!="是一对操作运算符,如果要实现两个都要实现,不能只实现其中一个
implicit也算是一个特殊的运算符,巧妙的使用隐式转换可以大大简化代码的写法,StackExchange.Redis中就使用了implicit来实现RedisValue和string等其他常用类型的隐式转换
References