1

So i have an interface named IResource that consist of those 5 properties as readonly but since I am adding those to a Dictionary<IResource, int> i need a way to compare two IResource so i don't have duplicates in the Dictionary. Is there a way for me to add a default Equals(object obj) to every IResource? I already added an Equals Override to the Wood class and it solved the problem but i would have to add a Equals(object obj) in every class that implements the IResource.

public class Wood : IResource
{
    public string Package => "Core";

    public string Family => "Wood";

    public string Name => "Wood";

    public bool IsFractal => false;

    public ResourceType Type => ResourceType.Natural;
}

PS:I have an override of the Add(IResource key, uint value) method to the dictionary to check if the IResource already exists.

    public new void Add(IResource key, uint value)
    {
        if (base.ContainsKey(key))
            base[key] += value;
        else
            base.Add(key, value);
    }

Right now when i add a IResource interface to the dictionary, it always adds a new entry.

  • If you want to prevent duplicate entries in your Dictionary, then `.ContainsKey(key)` is a good way to do it: https://stackoverflow.com/a/30571457/3135317. Q: So what exactly is your question? – FoggyDay Nov 10 '20 at 00:17
  • @FoggyDay even when using the -ContainsKey(key) i get duplicates. I used cointainskey in my custom dictionary. BUT what i noticed is that if the classes that implement the interface have a Equals override they do not get duplicated. I was wondering if theres a way to add a Equals for every class that implements the interface. – Pedro Costa Nov 10 '20 at 00:20
  • Possibly you could change your interface to an abstract base class and implement equals there – Jerry Nov 10 '20 at 00:22
  • 1
    Note that answers so far are extremely restrictive and essentially kill value of the interface by requiring to derive from particular class. Most likely what you should be looking for is passing a custom comparer to you dictionary - https://stackoverflow.com/questions/11562996/comparing-object-used-as-key-in-dictionary – Alexei Levenkov Nov 10 '20 at 01:39

3 Answers3

2

You can move your comparison to a base class and override Equals and GetHashCode there. Just add any members you want to use in the comparison to the abstract class and include them in the equity comparison.

For example:

public enum ResourceType { Natural }

public interface IResource
{
    public string Name { get; }
    public ResourceType ResourceType { get; }
}

public abstract class Resource
{
    public abstract string Name { get; }
    public abstract ResourceType ResourceType { get; }
    // other properties that you want to use for your resource comparision

    public override bool Equals(object obj)
        => obj is Resource r && Name == r.Name && ResourceType == r.ResourceType;

    public override int GetHashCode() => (Name, ResourceType).GetHashCode();
}

public class Wood : Resource, IResource
{
    public override string Name => "Wood";
    public override ResourceType ResourceType => ResourceType.Natural;
    // ... other properties
}
Jerry
  • 1,477
  • 7
  • 14
  • Great, this works, i was confused about how to make the abstract class, i was using protected instead of abstract. Thank You! – Pedro Costa Nov 10 '20 at 00:45
1

You can create an abstract class that implements IResource. Use that class and override Equals and GetHashCode.

public abstract class Resource : IResource
{
    //make all your interface properties abstract
    public abstract string Package { get; }
    public abstract string Family { get; }
    public abstract string Name { get; }
    public abstract bool IsFractal { get; }
    public abstract ResourceType Type { get; }

    public override bool Equals(object obj)
    {
        if (!(obj is Resource resource)) return false;
        return ReferenceEquals(this, resource) ||
               Package == resource.Package &&
               Family == resource.Family &&
               Name == resource.Family &&
               IsFractal == resource.IsFractal &&
               Type == resource.Type;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Package, Family, Name, IsFractal, Type);
    }
}

Then make all your resources implement the abstract class Resource

public class Wood : Resource
{
    public override string Package => "Core";

    public override string Family => "Wood";

    public override string Name => "Wood";

    public override bool IsFractal => false;

    public override ResourceType Type => ResourceType.Natural;
}

public class Rock : Resource
{
    public override string Package => "Core";

    public override string Family => "Rock";

    public override string Name => "Rock";

    public override bool IsFractal => false;

    public override ResourceType Type => ResourceType.Natural;
}

This will give you the behavior you expect.

JohanP
  • 5,252
  • 2
  • 24
  • 34
1

While it's certainly possible to create an abstract base class - as others have pointed out - it's really not a great idea. You're creating a dependency that any class that implements IResource must also implement equality as you've defined it for IResource. And that might be fine or it might make it hard to maintain and lead to bugs.

The framework is designed to handle this situation by allowing you to customize how the dictionary does comparisons. It does this by using IEqualityComparer.

Here's an example for your IResource interface:

public class ResourceComparer : IEqualityComparer<IResource>
{
    public bool Equals([AllowNull] IResource x, [AllowNull] IResource y)
    {
        if (null == x && null == y)
            return true;

        if (null == x || null == y)
            return false;

        return x.Package.Equals(y.Package) &&
            x.Family.Equals(y.Family) &&
            x.Name.Equals(y.Name) &&
            x.IsFractal.Equals(y.IsFractal) &&
            x.Type.Equals(y.Type);
    }

    public int GetHashCode([DisallowNull] IResource obj)
    {
        HashCode hash = new HashCode();
        hash.Add(obj.Package);
        hash.Add(obj.Family);
        hash.Add(obj.Name);
        hash.Add(obj.IsFractal);
        hash.Add(obj.Type);
        return hash.ToHashCode();
    }
}

Once you've got that then you can create your dictionary with that comparer. I've used your Wood class and created one other called Metal. Neither has to share a base class or override Equals and GetHashCode.

    static void Main(string[] _)
    {
        var resourceMap = new Dictionary<IResource,uint>(new ResourceComparer());

        var resources = new IResource[] { new Wood(), new Metal(), 
                                         new Wood(), new Wood() };

        foreach (var r in resources)
        {
            if (resourceMap.TryGetValue(r, out var count))
                resourceMap[r] = count + 1;
            else
                resourceMap.Add(r, 1);
        }

        Console.WriteLine(resourceMap[new Wood()]);
        Console.WriteLine(resourceMap[new Metal()]);
    }

Here's the simple POCO style metal class:

public class Metal : IResource
{
    public string Package => "Core";

    public string Family => "Metal";

    public string Name => "Metal";

    public bool IsFractal => false;

    public ResourceType Type => ResourceType.ManMade;
}
MikeJ
  • 1,299
  • 7
  • 10