2

Edit: culture These internal operations suggest invariant culture OrdinalIgnoreCase is most appropriate.

KeyValuePair is sealed. If I have var a = KeyValuePair<string,int>("foo",1) and var b = KeyValuePair<string,int>("FOO",2) and I compare a.Key == b.Key I will get false.

I'd like to get true. To start with, I wrapped my KeyValuePairs in a custom class:

public class MyKeyValuePair
{
    private KeyValuePair<string, int> myKeyValuePair;
    public SyncItem(string Key, int Value)
    {
        myKeyValuePair = new KeyValuePair<string, int>(Key, Value);
    }

    public string Key { get { return myKeyValuePair.Key; } }
    public int Value { get { return myKeyValuePair.Value; } }           
}

My first thought was I could add .ToLower() in the constructor on Key.

Is there a better way of doing this? Is there a right way? What factors should I consider?

cocogorilla
  • 1,815
  • 14
  • 36
  • 1
    Possibly not enough for you, since you ask a more general question, but if ***all*** you wanted was to have a `Dictionary<,>` where all key lookups were done in a case-insensitive manner, you could use a constructor overload like this: `myDict = new Dictionary(StringComparer.OrdinalIgnoreCase);`. Certainly this won't help you if you take out `KeyValuePair` from the `Dictionary<,>` and compare them. – Jeppe Stig Nielsen Jul 24 '13 at 15:27
  • 1
    Remember that `InvariantCultureIgnoreCase` (you mentioned invariant culture in your edit) is not quite the same as `OrdinalIgnoreCase`. – Jeppe Stig Nielsen Jul 24 '13 at 15:50
  • Thanks, there were some conflicting articles on this issue... I think I found my answer and OrdinalIgnoreCase would be best: http://stackoverflow.com/questions/72696/which-is-generally-best-to-use-stringcomparison-ordinalignorecase-or-stringco – cocogorilla Jul 24 '13 at 16:04

4 Answers4

6

Instead of converting it to lower case you can use String.Equals Method (String, StringComparison) instead of == operator. (since your key is of type string)

a.Key.Equals(b.Key, StringComparison.CurrentCultureIgnoreCase)
Habib
  • 219,104
  • 29
  • 407
  • 436
  • 1
    From a generic point of view, it is not recommended to use == for string. Equals and Compare Method provide overrides letting you set up the StringComparison you want to use. – Benjamin Baumann Jul 24 '13 at 15:07
  • 2
    Also see [The Turkish İ Problem and Why You Should Care](http://haacked.com/archive/2012/07/05/turkish-i-problem-and-why-you-should-care.aspx). For why one shouldn't convert to same case. – Habib Jul 24 '13 at 15:11
  • Wouldn't this give control to the caller? I'm unsure whether that's better or even relevant. – cocogorilla Jul 24 '13 at 15:37
3

You can override equality operator == so every time you need to compare two Keys you don't have to call ToLower() or any other custom method. Very clean and fluent approach.

public static bool operator ==(KeyValuePair<string, int> myKeyValuePair a, KeyValuePair<string, int> myKeyValuePair b)
{
    // If both are null, or both are same instance, return true.
    if (System.Object.ReferenceEquals(a, b))
    {
        return true;
    }

    // If one is null, but not both, return false.
    if (((object)a == null) || ((object)b == null))
    {
        return false;
    }

    // Return true if the fields match:
    return a.Key.Equals(b.Key, StringComparison.CurrentCultureIgnoreCase);
}
Nick
  • 4,192
  • 1
  • 19
  • 30
  • 1
    overriding the operator is good, but its better if you use `string.Equals` method than converting it to lower case see [The Turkish İ Problem and Why You Should Care](http://haacked.com/archive/2012/07/05/turkish-i-problem-and-why-you-should-care.aspx) – Habib Jul 24 '13 at 15:10
1

You could just use String.Equals:

a.Key.Equals(b.Key, StringComparison.InvariantCultureIgnoreCase)
Grant Winney
  • 65,241
  • 13
  • 115
  • 165
1

I liked the overload solution but it needed completion via http://msdn.microsoft.com/en-us/library/ms173147(v=vs.80).aspx

Researching @Jeppe Stig Nielsen showed that OrdinalIgnoreCase was most appropriate for my comparisons.

"Comparisons made using OrdinalIgnoreCase are behaviorally the composition of two calls: calling ToUpperInvariant on both string arguments, and doing an Ordinal comparison." [http://msdn.microsoft.com/en-us/library/ms973919.aspx]

Based on this citation I chose return this.Key.ToUpperInvariant().GetHashCode(); in the GetHashCode() overload.

Class Implementation

public class MyKeyValuePair
{
    private readonly KeyValuePair<string, int> myKeyValuePair;
    public MyKeyValuePair(string key, int value)
    {
        myKeyValuePair = new KeyValuePair<string, int>(key, value);
    }

    public string Key { get { return myKeyValuePair.Key; } }
    public int Value { get { return myKeyValuePair.Value; } }

    public static bool operator ==(MyKeyValuePair a, MyKeyValuePair b)
    {
        if (System.Object.ReferenceEquals(a, b))
        {
            return true;
        }
        if (((object)a == null) || ((object)b == null))
        {
            return false;
        }
        return a.Key.Equals(b.Key, StringComparison.OrdinalIgnoreCase);
    }
    public static bool operator !=(MyKeyValuePair a, MyKeyValuePair b)
    {
        return !(a == b);
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
        {
            return false;
        }
        MyKeyValuePair p = obj as MyKeyValuePair;
        if ((object)p == null)
        {
            return false;
        }
        return this.Key == p.Key;
    }

    public bool Equals(MyKeyValuePair obj)
    {
        if ((object)obj == null)
        {
            return false;
        }
        return this.Key.Equals(obj.Key, StringComparison.OrdinalIgnoreCase);
    }

    public override int GetHashCode()
    {
        return this.Key.ToUpperInvariant().GetHashCode();
    }
}

Test Method

public void MyKeyValuePairCaseInsensitiveKeyComparisonWorksCorrectly()
{
    var x = new MyKeyValuePair("testvalue", 5);
    var y = new MyKeyValuePair("testvalue", 6);
    var z = new MyKeyValuePair("testvalue", 7);

    Assert.True(x == x, "== identity");
    Assert.True((x == y) == (y == x), "equals commutative");
    Assert.True(x == y, "== if x == y");
    Assert.True(y == x, "== and y == z");
    Assert.True(x == z, "== then x equals z");
    for (var successive_invocations = 0; successive_invocations < 3; successive_invocations++)
    {
        Assert.True(x == y, "== successive invocations");
    }
    Assert.False(x == null);

    Assert.True(x.Equals(x), "equals identity");
    Assert.True(x.Equals(y) == y.Equals(x), "equals commutative");
    Assert.True(x.Equals(y), "equals if x == y");
    Assert.True(y.Equals(x), "equals and y == z");
    Assert.True(x.Equals(z), "equals then x equals z");
    for (var successive_invocations = 0; successive_invocations < 3; successive_invocations++)
    {
        Assert.True(x.Equals(y), "equals successive invocations");
    }
    Assert.False(x.Equals(null));

    // show correct behavior
    var capi = "I";
    var lowi = "i";
    var capti = "İ";
    var lowti = "ı";

    Assert.True(capi.Equals(lowi, StringComparison.OrdinalIgnoreCase), "capi == lowi");
    Assert.False(capi.Equals(capti, StringComparison.OrdinalIgnoreCase), "capi != capti");
    Assert.False(capi.Equals(lowti, StringComparison.OrdinalIgnoreCase), "capi != lowti");

    Assert.False(lowi.Equals(capti, StringComparison.OrdinalIgnoreCase), "lowi != capti");
    Assert.False(lowi.Equals(lowti, StringComparison.OrdinalIgnoreCase), "lowi != lowti");

    Assert.False(capti.Equals(lowti, StringComparison.OrdinalIgnoreCase), "capti != lowti");

    //test actual behavior
    var a = new MyKeyValuePair(capi, 1);
    var b = new MyKeyValuePair(lowi, 2);
    var c = new MyKeyValuePair(capti, 3);
    var d = new MyKeyValuePair(lowti, 4);

    Assert.True(a.Equals(b), "a == b");
    Assert.False(a.Equals(c), "a != c");
    Assert.False(a.Equals(d), "a != d");

    Assert.False(b.Equals(c), "b != c");
    Assert.False(b.Equals(d), "b != d");

    Assert.False(c.Equals(d), "c != d");
}
cocogorilla
  • 1,815
  • 14
  • 36