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

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C# 实现一个基于值相等性比较的字典相关的知识,希望对你有一定的参考价值。

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<stringobject>()
    {
        {"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<stringobject>()
    {
        {"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<stringobject>()
    {
        {"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<stringstring> 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<stringstring?> _dictionary = new();

    private StringValueDictionary(IDictionary<stringstring?> 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 nullthrow new ArgumentNullException(nameof(obj));
        if (obj is IDictionary<stringstring?> dictionary)
        {
            return new StringValueDictionary(dictionary);
        }
        if (obj is IDictionary<stringobject?> 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<stringobject?>>()
            .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 stringthis[string key] => _dictionary[key];

    public Dictionary<stringstring>.KeyCollection Keys => _dictionary.Keys!;

    public bool Equals(StringValueDictionary? other)
    {
        if (other is nullreturn 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<stringstring?>(StringValueDictionary dictionary)
    {
        return dictionary._dictionary;
    }
}

More

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

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

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

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

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

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# 实现一个基于值相等性比较的字典的主要内容,如果未能解决你的问题,请参考以下文章

相等性比较 (== ,Equals,ReferenceEquals)

在 C# 中测试泛型集合的引用相等性是一个愚蠢的想法吗?

转载C#扫盲之:==/Equals /ReferenceEquals 异同的总结,相等性你真的知道吗?

转载C#扫盲之:==/Equals /ReferenceEquals 异同的总结,相等性你真的知道吗?

比较两个字典是不是相等

C#扫盲之:==/Equals /ReferenceEquals 异同的总结,相等性你真的知道吗?