2

I had a look at the following question:

Good GetHashCode() override for List of Foo objects respecting the order

And asked Jon Skeet the following: Good GetHashCode() override for List of Foo objects respecting the order

"@JonSkeet does that solution produce consistent result with List of string across multiple AppDomains? I have a WCF Server where multiple application from various platforms (XP, Vista, Win7 or different .Net Frameworks 3.5 and higher) can connect and in all those situations I need a consistent hashcode from a list of strings is that the case? If not how would I achieve that?"

His answer: "@RandRandom: You shouldn't be using GetHashCode for that - it's not meant to be consistent across different processes. You should use something like SHA-256 for that. "

Not sure how the implementation of his proposed answer would look like, so to not ask detailed question in the comments area I decided to create a new question.

So assume you have the following list:

var fooList = new List<string>()
{
    "",
    "StatAccount",
    "Value",
    "9900000701",
    "P1",
    "3",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "VFE-Lage.xlsx",
    "",
    "",
    "False",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
};

Now I need a hashCode that's easier/faster to compare than checking for SequenceEquality, my list is immutable it never changes.

I have a class looking something like this:

public class Foo
{
    private List<string> _fooList = new List<string>();
    private int _fooListHashCode;

    public List<string> FooList
    {
        get
        {
            return _fooList;
        }
        set
        {
            _fooList = value;
            _fooListHashCode = GetListsHashCode(value);
        }
    }

    public static int GetListsHashCode(List<string> list)
    {
        //return hashCode...
        return 0;
    }

    public override int GetHashCode()
    {
        return _fooListHashCode;
    }

    public override bool Equals(object obj)
    {
        var foo = obj as Foo;
        if (foo == null)
            return false;

        return this._fooListHashCode == foo._fooListHashCode;
    }
}
Community
  • 1
  • 1
Rand Random
  • 7,300
  • 10
  • 40
  • 88

2 Answers2

2

This should work for comparing two lists of strings using SHA256 hash.

    private bool CompareLists(IEnumerable<string> value1, IEnumerable<string> value2)
    {
        // First convert lists to single strings
        var encoder = new UTF8Encoding();
        var hash = new SHA256CryptoServiceProvider();
        var sb1 = new StringBuilder();
        var sb2 = new StringBuilder();

        foreach (var item in value1)
        {
            sb1.Append(item);
        }

        foreach (var item in value2)
        {
            sb2.Append(item);
        }

        // Then hash and compare
        return Convert.ToBase64String(hash.ComputeHash(encoder.GetBytes(sb1.ToString()))) ==
               Convert.ToBase64String(hash.ComputeHash(encoder.GetBytes(sb2.ToString())));
    }

Since your primary list is immutable you should probably compute the hash for it once and store it, that would make this method a bit more performant. There may be better ways, but this should work.

NOTE: For this method to return true, both lists must be the exact same; same number of strings, same strings (including case), in the same order.

Kevin
  • 1,462
  • 9
  • 9
  • How would I turn that into an int to return it for the method GetHashCode? Since I need HashSets and Dictionaries for those objects. – Rand Random Jun 17 '16 at 12:52
  • In that case, I wouldn't use a hash at all I'd use Rfc2898DeriveBytes to get a 4 byte array which you can convert to an int. I'll post a second answer. – Kevin Jun 17 '16 at 13:04
1

Per the comments to my previous answer:

    private int GetHashCode(IEnumerable<string> value)
    {
        var encoder = new UTF8Encoding();
        var hash = new SHA256CryptoServiceProvider();
        var sb = new StringBuilder();

        foreach (var item in value)
        {
            sb.Append(item);
        }

        return
            Convert.ToInt32(
                new Rfc2898DeriveBytes(sb.ToString(),
                    hash.ComputeHash(encoder.GetBytes(sb.ToString()))).GetBytes(4));
    }
Kevin
  • 1,462
  • 9
  • 9