Reputation: 49
I have an interface named IResource
that consist of 5 properties as readonly. Since I am adding those to a Dictionary<IResource, int>
, I need a way to compare two IResource
values 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 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.
Upvotes: 0
Views: 88
Reputation: 1379
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;
}
Upvotes: 1
Reputation: 5472
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.
Upvotes: 1
Reputation: 1527
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
}
Upvotes: 2