1

I have a class as below. To an object of this class I need to add a new language in case it doesn't exist

using System;
using System.Collections.Generic;
namespace discovery.POCO
{
    public class MultiLingualObject 
    {
        public string TableName { get; set; }
        public string BranchId { get; set; }
        public int    GenericId { get; set; }
        public string GenericCode { get; set; }
        public List<MultiLingualColumn> MultiLingColumnsCollection = new List<MultiLingualColumn>();
    }
    public class MultiLingualColumn : IEquatable<MultiLingualColumn>
    {
        public string ColumnName { get; set; }
        public string LanguageCode { get; set; }
        public string LanguageText { get; set; }

        public bool Equals(MultiLingualColumn other)
        {
            if (other == null) return false;
            return  string.Equals(ColumnName,other.ColumnName) &&
                    string.Equals(LanguageCode, other.LanguageCode) &&
                    string.Equals(LanguageText, other.LanguageText);
        }
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != GetType()) return false;
            return Equals(obj as MultiLingualColumn);
        }
    }

}

As I am new to C# I have searched for various solutions, including .Contains or Equal (from where I have added override to my Equal above). I also understand I can achieve the comparison by simply using where like this. Yet, as I might have more elements added to the class, I would like to stick to either Equal or Contains, if possible. The code I use to compare and then to insert if doesn't exist is as following

internal void UpdateLocalMultiLing()
        {
            POCO.MultiLingualColumn _equals = new POCO.MultiLingualColumn()
            {
                ColumnName = InvoiceComment.Name.TrimEnd(),
                LanguageCode = inputLanguage,
                LanguageText = InvoiceComment.Value.TrimEnd()
            };

            if (!SupplierMultiLing.MultiLingColumnsCollection.Equals(_equals))
                SupplierMultiLing.MultiLingColumnsCollection.Add(new POCO.MultiLingualColumn
                {
                    ColumnName   = InvoiceComment.Name.Trim(),
                    LanguageCode = inputLanguage,
                    LanguageText = InvoiceComment.Value.Trim()
                }
                );
        }

yet it ignores the condition and adds the same language again. It can be seen from the image attached.enter image description here

Can one advise what shall I fix, please?

KDWolf
  • 398
  • 2
  • 14
  • 1
    https://stackoverflow.com/questions/371328/why-is-it-important-to-override-gethashcode-when-equals-method-is-overridden – Martin Verjans Oct 08 '18 at 11:59
  • Don't forget to accept an answer that solves your problem. https://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work/5235#5235 – Rand Random Oct 08 '18 at 14:47

2 Answers2

4

You're comparing a list against a single object. You need .Contains() instead of .Equals().

Either properly implement IEquatable<T>, or override Equals(object) and GetHashCode(). From What does Collection.Contains() use to check for existing objects?:

either implement IEquatable on your custom class, or override the Equals (and GetHashCode)

This code prints "True":

public class Foo : IEquatable<Foo>
{
    public string Bar { get; set; }

    public bool Equals(Foo other)
    {
        return other.Bar == this.Bar;
    }
}

public static void Main()
{
    var list = new List<Foo>
    {
        new Foo { Bar = "Baz" }
    };

    Console.WriteLine(list.Contains(new Foo { Bar = "Baz" }));
}

But as @Jeppe correctly comments, it's advisable to also provide a proper implementation of GetHashCode() for other collection and comparison types.

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • 1
    FYI - about your comment regarding `GetHashCode()` your linked explicitly says he needs either `IEquatable` or `GetHashCode()` and OP has `IEquatable` in use - so he doesn't need it... to late…. – Rand Random Oct 08 '18 at 12:07
  • @RandRandom It is a very bad idea to not override `GetHashCode` if you implement `IEquatable<>` __and/or__ override `Equals(object)`. It will work well enough for `List<>.Contains` without it. But as soon as you use a `Dictionary<,>` or `HashSet<>`, or do stuff like Linq's `.Distinct()` etc., it will fail. At the very least, include `public override int GetHashCode() => throw new NotSupportedException();` to prevent shooting yourself in the foot. – Jeppe Stig Nielsen Oct 08 '18 at 12:13
  • @JeppeStigNielsen That's perhaps newbie's question, but will overriding GetHashCode have any affect on the performance, please? – KDWolf Oct 08 '18 at 12:32
  • @KDWolf Yes, if you override `GetHashCode()` and you ever use any feature that will take advantage of `GetHashCode()`, like the ones I mentioned in my first comment, then the precise implementation may affect the performance. But the first priority should be correct behavior. If you do not override, you will get incorrect behavior. For example, if you use `list.Distinct().Count()`, and you have two or more "equal" but not "same" instances in the `List<>`, then you will almost certainly get the wrong count if you do not override `GetHashCode()`. – Jeppe Stig Nielsen Oct 08 '18 at 12:41
  • @JeppeStigNielsen - I totally agree with you that its better to override `GetHashCode` - I just wanted to tell CodeCaster that it isn't necessary to implement it to make `.Contains()` work as his answer before his edit claimed to be the case - was curious if you have to implement `GetHashCode` to make `.Distinct()` and so work without the overriding `GetHashCode` so here a little playground https://dotnetfiddle.net/DzpRkt - didn't really get rid of it but you can fake implement it - but as I said, I would also recommend overriding `GetHashCode` was just playing around – Rand Random Oct 08 '18 at 12:48
  • 1
    @RandRandom Yes. Of course, using an explicit `IEqualityComparer<>` just moves the "question" to that class instead. If you do not wish to try to write a good implementation of `GetHashCode()` or `GetHashCode(obj)`, you can either: (A) just throw `NotSupportedException` which means you will be informed once a need for `GetHashCode` arises. Or, (B) just return a constant, like your `-1`, which will give correct behavior but ___poor___ performance. Either is fine. Just never use the non-overridden `GetHashCode` from `System.Object` because you will run into "incomprehensible" problems later. – Jeppe Stig Nielsen Oct 08 '18 at 13:21
0

You should use the Contains() method. Also you should implement IEquatable interface for the MultiLingualColumn or implement IEqualityComparer and pass the second argument to Contains(). I prefer the second option:

public class MultiLingualColumnComparer : IEqualityComparer<MultiLingualColumn>
{
    public bool Equals(MultiLingualColumn x, MultiLingualColumn y)
    {
        //...
    }

    public int GetHashCode(MultiLingualColumn obj)
    {
        //...
    }
}

and then:

if (!SupplierMultiLing.MultiLingColumnsCollection.Contains(_equals, new MultiLingualColumnComparer()))
smolchanovsky
  • 1,775
  • 2
  • 15
  • 29