How to implement a simple generic comparison in C# - c#

I am just begining to explore the basic of using generics and would have thought i could implement this simple pattern to solve a typical problem in my daily use. I have spent days searching for a simple example. I can find examples for looking for .Equals, but not much past that. I want to be able to instantiate with things like:
Spec<double> voltageSpec;
Spec<int> cyclesSpec;
Spec<myClass> fishInTheOceanSpec;
then be able to :
bool isGood = voltageSpec.inSpec(5.0);
bool isGood cyclesSpec.inSpec(2);
bool isGood fishInTheOceanSpec.( new myClass(20));
My attempt is shown below.
/// <summary>
/// Generic object to hold a specification i.e min and max limits.
/// Implements a method to determin if a value is between limits.
/// </summary>
public class Spec<T> : IComparer<T>
{
public Spec()
{
Min = default(T);
Max = default(T);
}
public T Min { get; set; }
public T Max { get; set; }
public bool inSpec(T Value)
{
if ((Comparer<T>.Default.Compare(Value, this.Max) <= 0) &
(Comparer<T>.Default.Compare(Value, this.Min) >= 0))
return true;
else
return false;
}
public int Compare(T x, T y)
{
if (x == y) return 0;
if (x < y) return -1;
if (x > y) return 1;
}
public Spec<T> Copy()
{
return (Spec<T>)this.MemberwiseClone();
}
}

I would refactor as follows - make T be responsible to provide the comparison - this works already for primitive types, your custom classes just have to implement IComparable<T>:
public class Spec<T> where T : IComparable<T>
{
public Spec()
{
Min = default(T);
Max = default(T);
}
public T Min { get; set; }
public T Max { get; set; }
public bool inSpec(T Value)
{
if(Value.CompareTo(this.Max) <=0 &&
Value.CompareTo(this.Min) >=0)
return true;
else
return false;
}
public Spec<T> Copy()
{
return (Spec<T>)this.MemberwiseClone();
}
}

Declare your class like this
public class Spec<T> : IComparer<T>
where T : IComparable<T>
{
....
}
Then you can apply CompareTo to T
public int Compare(T x, T y)
{
return x.CompareTo(y);
}
Basic types like int, double or string do implement IComparable<T>.

Related

IComparer C# + generics

How I can write make method Compare in RoomComparerByVolume ?
"Define a generic class RoomComparerByVolume<> implementing IComparer interface.
Impose a constraint on the type argument so that it should implement the IShape interface.
This comparer should perform comparison of rooms by room volume."
public interface IShape
{
public double Area()
{
return 0;
}
}
public class Rectangle : IShape
{
public double Length { get; set; }
public double Width { get; set; }
public double Area()
{
return Length * Width;
}
}
public class Trapezoid : IShape
{
public double Length1 { get; set; }
public double Length2 { get; set; }
public double Width { get; set; }
public double Area()
{
return (Length1 + Length2) * Width / 2;
}
}
public class Room<T> where T : IShape, ICloneable, IComparable
{
public double Height { get; set; }
public T Floor;
public double Volume()
{
return Height * Height;
}
public object Clone()
{
return new Room<T> { Height = this.Height, Floor = this.Floor };
}
public int CompareTo(object o)
{
Room<T> r = o as Room<T>;
if (r != null)
return this.Volume().CompareTo(r.Volume());
else
throw new Exception("Unable to compare");
}
}
public class RoomComparerByVolume<T> : IComparer<T> where T : IShape
{
}
Your question is a bit unclear. If compare "by room volume" means in fact compare Area() values
and you want to imeplement a corresponding comparer, you can put something like this
public sealed class ShapeComparerByArea<T> : IComparer<T> where T : IShape
{
public int Compare(T left, T right)
{
if (ReferenceEquals(left, right))
return 0;
if (left == null)
return 1;
if (right == null)
return -1;
return left.Area().CompareTo(right.Area());
}
}
If you want to define comparable Room class (which has Shape Floor) you can put it as
public class Room : IComparable<Room> {
public Room(IShape floor, double height) {
Floor = floor ?? throw new ArgumentNullException(nameof(floor));
Height = height > 0
? height
: throw new ArgumentOutOfRangeException(nameof(height));
}
public IShape Floor { get; }
public double Height { get; }
public double Volume => Floor.Area() * Height;
public int CompareTo(Room other) {
if (ReferenceEquals(this, other))
return 0;
if (other is null)
return 1;
return Volume.CompareTo(other.Volume);
}
}
Finally, RoomComparerByVolume can be
public sealed class RoomComparerByVolume : IComparer<Room> {
public int Compare(Room left, Room right) {
if (ReferenceEquals(left, right))
return 0;
if (left == null)
return 1;
if (right == null)
return -1;
return left.Volume.CompareTo(right.Volume);
}
}
Demo:
Room myRoom = new Room(
new Trapezoid() { Length1 = 5, Length2 = 7, Width = 3},
2.8);
Room myOtherRoom = new Room(
new Rectangle() { Length = 3, Width = 4 },
2.5);
Console.WriteLine(myRoom.CompareTo(myOtherRoom));
RoomComparerByVolume comparer = new RoomComparerByVolume();
Console.WriteLine(comparer.Compare(myRoom, myOtherRoom));
I would assume that the desired declaration should look like
public class RoomComparerByVolume<T> : IComparer<Room<T>> where T : IShape, ICloneable, IComparable{
public int Compare(Room<T> left, Room<T> right) {
...
}
That way you can compare the two rooms. But note that the two rooms need to be the same shape using this method. Also note that you need to use the same generic constraints as you are using for Room<T>.
Also note that in many cases you do not need to create a specific comparer type. Often you can use a delegate to do the actual comparison:
Comparer<Room<Trapezoid>>.Create((l, r) => l.Volume().CompareTo(r.Volume));

How do you write a GetHashCode method for an object made of a string and a collection of int32?

There is a class of Products:
public class ProductWithFeatures
{
public string Name { get; set; }
public ICollection<Feature> Features { get; set; }
}
public class Feature
{
public int Id { get; set; }
public Feature(int Id)
{
this.Id = Id;
}
}
I want to write an IEqualityComparer for this (i already have one for Feature).
The one for Feature is like this:
public class FeatureComparer : IEqualityComparer<Feature>
{
public bool Equals(Feature x, Feature y)
{
return x.Id == y.Id;
}
public int GetHashCode(Feature obj)
{
return obj.Id;
}
}
And what i wrote so far on the other one is this:
public class ProductComparer : IEqualityComparer<LinqHomework.ProductWithFeatures>
{
public bool Equals(ProductWithFeatures x, ProductWithFeatures y)
{
return x.Name == y.Name && LinqHomework.FeatureComparer.Equals(x.Features, y.Features);
}
public int GetHashCode(ProductWithFeatures obj)
{
}
}
I can't find an answer anywhere about this. Does anybody know how to write it?
Two ProductWithFeaturess are equal if they have the same name, and have the same features in the same order.
public class ProductComparer : IEqualityComparer<LinqHomework.ProductWithFeatures>
{
public bool Equals(ProductWithFeatures x, ProductWithFeatures y)
{
return x.Name == y.Name && x.Features.SequenceEqual(y.Features, new LinqHomework.FeatureComparer());
}
public int GetHashCode(ProductWithFeatures obj)
{
int hash = obj.Name.GetHashCode();
var featureComparer = new LinqHomework.FeatureComparer();
foreach (var feature in obj.Features)
{
hash = hash * 23 + featureComparer.GetHashCode(feature);
}
return hash;
}
}
This is a simple approach, which can be improved in a number of ways.
First, let's give our FeatureComparer a Default property, so we don't need to keep creating new instances:
public class FeatureComparer : IEqualityComparer<Feature>
{
public static FeatureComparer Default { get; } = new FeatureComparer();
// ... as before
}
This lets us write:
public class ProductComparer : IEqualityComparer<LinqHomework.ProductWithFeatures>
{
public bool Equals(ProductWithFeatures x, ProductWithFeatures y)
{
return x.Name == y.Name && x.Features.SequenceEqual(y.Features, LinqHomework.FeatureComparer.Default);
}
public int GetHashCode(ProductWithFeatures obj)
{
int hash = obj.Name.GetHashCode();
foreach (var feature in obj.Features)
{
hash = hash * 23 + LinqHomework.FeatureComparer.Default.GetHashCode(feature);
}
return hash;
}
}
We're also not handling the case where our methods are passed null, or the name of a feature is null, so let's deal with those. We can also test whether x and y are the same object in Equals.
We'll also do the integer operations in an unchecked block in case it overflows (and the assembly is compiled with /checked).
Note that we use ReferenceEquals instead of ==, in case you end up implementing the == operator in your types.
public class ProductComparer : IEqualityComparer<LinqHomework.ProductWithFeatures>
{
public bool Equals(ProductWithFeatures x, ProductWithFeatures y)
{
if (ReferenceEquals(x, y))
return true;
if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
return false;
if (x.Name != y.Name)
return false;
if (ReferenceEquals(x.Features, y.Features))
return true;
if (ReferenceEquals(x.Features, null) || ReferenceEquals(y.Features, null))
return false;
if (!x.Features.SequenceEquals(y.Features, LinqHomework.FeatureComparer.Default))
return false;
return true;
}
public int GetHashCode(ProductWithFeatures obj)
{
if (ReferenceEquals(obj, null))
return 0;
unchecked
{
int hash = obj.Name?.GetHashCode() ?? 0;
if (!ReferenceEquals(obj.Features, null))
{
foreach (var feature in obj.Features)
{
hash = hash * 23 + LinqHomework.FeatureComparer.Default.GetHashCode(feature);
}
return hash;
}
}
}
}
It's really up to you. I personally would go for something like
public int GetHashCode( ProductWithFeatures obj )
{
string toHash = obj.Name;
foreach( var feature in obj.Features )
toHash += feature.GetHashCode();
return toHash.GetHashCode();
}
It's not the nicest code ever, but it does what it's supposed to do.

LINQ Distinct not using the IEqualityComparer? [duplicate]

This question already has answers here:
IEqualityComparer not working as intended
(3 answers)
Closed 7 years ago.
I have the following model:
public class Word {
public string Original { get; set; }
public string Normalized { get; set; }
public string Root { get; set; }
public string Subroot { get; set; }
public Regex SubrootRegex { get; set; }
}
I have created the following three IEqualityComparer<Word>:
public class NormalizedWordComparer : IEqualityComparer<Word> {
public bool Equals(Word x, Word y) {
return x.Normalized == y.Normalized;
}
public int GetHashCode(Word obj) {
return obj.GetHashCode();
}
}
public class RootWordComparer : IEqualityComparer<Word> {
public bool Equals(Word x, Word y) {
return x.Root == y.Root;
}
public int GetHashCode(Word obj) {
return obj.GetHashCode();
}
}
public class SubrootWordComparer : IEqualityComparer<Word> {
public bool Equals(Word x, Word y) {
return x.Subroot == y.Subroot;
}
public int GetHashCode(Word obj) {
return obj.GetHashCode();
}
}
In another class, I am trying to do the following:
_normalizedWords = ConfigurationFacade.Words.Select(w => {
Word word = new Word() { Original = w };
word.Normalized = Normalize(word, _filters);
word.Root = GetRoot(word.Normalized, ConfigurationFacade.WordRootPercentage);
word.Subroot = GetRoot(word.Root, ConfigurationFacade.WordSubrootPercentage);
word.SubrootRegex = null; //Complicated regex here
return word;
}).Distinct(new NormalizedWordComparer());
_wordRoots = _normalizedWords.Distinct(new RootWordComparer());
_wordSubroots = _wordRoots.Distinct(new SubrootWordComparer());
However, _normalizedWords, _wordRoots and _wordSubroots all end up with the same amount of elements, as if the Distinct() method didn't work or the comparer is being ignored.
I checked the elements with the debugger, and there are a lot that have the same Root value, so there should only be one of them in _wordRoots, but that's not the case, they are not removed or filtered.
Why is my Distinct() not working?
Why is my Distinct() not working?
Because Distinct first checks the hash code (since it's a quick check to see if two object could be equal) and then calls Equals. Since your GetHashCode implementations are all the same (and do not correspond to your Equals methods), Distinct is not working as you would expect.
Change your GetHashCode methods to correspond with Equals:
public class NormalizedWordComparer : IEqualityComparer<Word> {
public bool Equals(Word x, Word y) {
return x.Normalized == y.Normalized;
}
public int GetHashCode(Word obj) {
return obj.Normalized.GetHashCode();
}
}
public class RootWordComparer: IEqualityComparer<Word> {
public bool Equals(Word x, Word y) {
return x.Root == y.Root;
}
public int GetHashCode(Word obj) {
return obj.Root.GetHashCode();
}
}
public class SubrootWordComparer : IEqualityComparer<Word> {
public bool Equals(Word x, Word y) {
return x.Subroot == y.Subroot;
}
public int GetHashCode(Word obj) {
return obj.Subroot.GetHashCode();
}
}

Generic IComparer for sorting different objects in different properties

I'm trying to sort an array of objects with IComparer.
I wrote the code but it works only with the particular object. e.g.:
for this class
public class Cars
{
public string Name { get; set; }
public string Manufacturer { get; set; }
public int Year { get; set; }
public Cars(string name, string manufacturer, int year)
{
Name = name;
Manufacturer = manufacturer;
Year = year;
}
}
My code looks like:
class MySort
{
public class SortByYears : IComparer
{
int IComparer.Compare(Object x, Object y)
{
Cars X = (Cars)x, Y = (Cars)y;
return (X.Year.CompareTo(Y.Year));
}
}
public class SortByName : IComparer
{
int IComparer.Compare(Object x, object y)
{
Cars X = (Cars)x, Y = (Cars)y;
return (X.Name.CompareTo(Y.Name));
}
}
public class SortByManyfacturer : IComparer
{
int IComparer.Compare(object x, object y)
{
Cars X = (Cars)x, Y = (Cars)y;
return (X.Manufacturer.CompareTo(Y.Manufacturer));
}
}
}
But if I add another class with different properties it will be useless.
So is there any chance to modify this code so that it worked for objects with different properties?
class SortComparer<T> : IComparer<T>
{
private PropertyDescriptor PropDesc = null;
private ListSortDirection Direction =
ListSortDirection.Ascending;
public SortComparer(object item,string property,ListSortDirection direction)
{
PropDesc = TypeDescriptor.GetProperties(item)[property];
Direction = direction;
}
int IComparer<T>.Compare(T x, T y)
{
object xValue = PropDesc.GetValue(x);
object yValue = PropDesc.GetValue(y);
return CompareValues(xValue, yValue, Direction);
}
private int CompareValues(object xValue, object yValue,ListSortDirection direction)
{
int retValue = 0;
if (xValue is IComparable) // Can ask the x value
{
retValue = ((IComparable)xValue).CompareTo(yValue);
}
else if (yValue is IComparable) //Can ask the y value
{
retValue = ((IComparable)yValue).CompareTo(xValue);
}
// not comparable, compare String representations
else if (!xValue.Equals(yValue))
{
retValue = xValue.ToString().CompareTo(yValue.ToString());
}
if (direction == ListSortDirection.Ascending)
{
return retValue;
}
else
{
return retValue * -1;
}
}
}
Calling code:
Assuming a list named lst:
lst.Sort(new SortComparer<Cars>(lst[0],"YourPropertyName",ListSortDirection.Ascending));
You may leverage the Create method of Comparer<T> which takes a Comparison delegate and returns Comparer<T>.
var carnameComparer = Comparer<Cars>.Create((x, y) => x.Year.CompareTo(y.Year));
var carManufacturerComparer = Comparer<Cars>.Create((x, y) => x.Manufacturer.CompareTo(y.Manufacturer));
and for another type
var carsComparer = Comparer<SomeType>.Create((x, y) => x.SomeProperty.CompareTo(y.SomeProperty));
If you're in prior to .Net4.5 you can use the following CreateComparer method.
private static IComparer<T> CreateComparer<T>(Comparison<T> comparison)
{
return new ComparisonComparer<T>(comparison);
}
public class ComparisonComparer<T> : IComparer<T>
{
private Comparison<T> comparison;
public ComparisonComparer(Comparison<T> comparison)
{
if (comparison == null)
{
throw new ArgumentNullException("comparison");
}
this.comparison = comparison;
}
public int Compare(T x, T y)
{
return comparison(x, y);
}
}
Use an interface and use generic IComparer Interface instead of IComparer
public interface IObjectWithNameProperty
{
string Name {get; set;}
}
public class MyNameComparer : IComparer<IObjectWithNameProperty>
{
public int Compare(IObjectWithNameProperty x, IObjectWithNameProperty y)
{
...
}
}
public class Car: IObjectWithNameProperty
{
public string Name {get;set;}
...
}
public class Dog: IObjectWithNameProperty
{
public string Name {get;set;}
...
}
Here is another take based on terrybozzio's.
public class PropertyComparer<T> : IComparer<T> where T : new()
{
private PropertyDescriptor PropDesc = null;
private ListSortDirection Direction = ListSortDirection.Ascending;
public PropertyComparer(string property, ListSortDirection direction)
{
T item = new T();
PropDesc = TypeDescriptor.GetProperties(item)[property];
Direction = direction;
Type interfaceType = PropDesc.PropertyType.GetInterface("IComparable");
if (interfaceType == null && PropDesc.PropertyType.IsValueType)
{
Type underlyingType = Nullable.GetUnderlyingType(PropDesc.PropertyType);
if (underlyingType != null)
{
interfaceType = underlyingType.GetInterface("IComparable");
}
}
if (interfaceType == null)
{
throw new NotSupportedException("Cannot sort by " + PropDesc.Name +
". This" + PropDesc.PropertyType.ToString() +
" does not implement IComparable");
}
}
int IComparer<T>.Compare(T x, T y)
{
object xValue = PropDesc.GetValue(x);
object yValue = PropDesc.GetValue(y);
IComparable comparer = (IComparable)xValue;
if (Direction == ListSortDirection.Ascending)
{
return comparer.CompareTo(yValue);
}
else
{
return -1 * comparer.CompareTo(yValue);
}
}
}
The cleanest way is to define an interface that both object implement, then use that in the comparison. Otherwise you're going to have a mess of case statements depending on the
possible combination of objects:
public class SortByYears : IComparer
{
int IComparer.Compare(Object x, Object y)
{
if(x is Cars)
{
Cars X = (Cars)x
if(y is Cars)
{
Y = (OtherCars)y;
return (X.Year.CompareTo(Y.Year));
if(y is OtherCars)
{
Y = (OtherCars)y;
return (X.Year.CompareTo(Y.Year));
}
}
if(x is OtherCars)
{
... repeat upper block
}
}
}
Another approach would be using the generic IComparer interface and lambda expressions.
class CarComparer<T> : IComparer<Car> where T : IComparable<T>
{
private readonly Func<Car, T> _sortExpression;
public CarComparer(Func<Car, T> sortExpression)
{
_sortExpression = sortExpression;
}
public int Compare(Car x, Car y)
{
return _sortExpression(x).CompareTo(_sortExpression(y));
}
}
This class compares the Car's property passed in parameter in the constructor.
// Sort the cars by name
var nameCarComparer = new CarComparer<string>(car => car.Name);
Array.Sort(myArray, nameCarComparer);

How do I implement equality for an abstract base class?

I'm following the MSDN guidance for value equality, and I found a case that the documentation didn't cover, equality for a base class.
A little background:
I'm working on a Mahjong game (4-player, not matching), and I'm working on defining the tiles. Tiles can be broken into two groups: suits, which have a number associated with them (and can be put together in sequences, like 2-3-4) and honor tiles, which have no number.
Here's what I have so far:
public enum MahjongSuitType
{
Bamboo = 1,
Character,
Dot
}
public enum MahjongSuitNumber
{
One = 1,
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine
}
public enum MahjongHonorType
{
GreenDragon = 1,
RedDragon,
WhiteDragon,
EastWind,
SouthWind,
WestWind,
NorthWind
}
public abstract class MahjongTile
{
}
public class MahjongSuitTile : MahjongTile, IEquatable<MahjongTile>
{
public MahjongSuitType SuitType { get; private set; }
public MahjongSuitNumber SuitNumber { get; private set; }
public bool IsRedBonus { get; private set; } //this has no bearing on equality
public MahjongSuitTile(MahjongSuitType suitType,
MahjongSuitNumber suitNumber,
bool isRedBonus = false)
{
this.SuitType = suitType;
this.SuitNumber = suitNumber;
this.IsRedBonus = isRedBonus;
}
public override bool Equals(object obj)
{
return this.Equals(obj as MahjongTile);
}
public bool Equals(MahjongTile other)
{
if (Object.ReferenceEquals(other, null))
return false;
if (Object.ReferenceEquals(other, this))
return true;
MahjongSuitTile otherSuitTile = other as MahjongSuitTile;
if (Object.ReferenceEquals(otherSuitTile, null))
return false;
return (this.SuitType == otherSuitTile.SuitType) &&
(this.SuitNumber == otherSuitTile.SuitNumber);
}
public override int GetHashCode()
{
return this.SuitType.GetHashCode() ^ this.SuitNumber.GetHashCode();
}
}
public class MahjongHonorTile : MahjongTile, IEquatable<MahjongTile>
{
public MahjongHonorType HonorType { get; private set; }
public MahjongHonorTile(MahjongHonorType honorType)
{
this.HonorType = HonorType;
}
public override bool Equals(object obj)
{
return this.Equals(obj as MahjongTile);
}
public bool Equals(MahjongTile other)
{
if (Object.ReferenceEquals(other, null))
return false;
if (Object.ReferenceEquals(other, this))
return true;
MahjongHonorTile otherHonorTile = other as MahjongHonorTile;
if (Object.ReferenceEquals(otherHonorTile, null))
return false;
return this.HonorType == otherHonorTile.HonorType;
}
public override int GetHashCode()
{
return this.HonorType.GetHashCode();
}
}
For the majority of the code, I'd like to refer to the tiles via the base class, something like:
List<MahjongTile> hand = new List<MahjongTile>() { ... };
HashSet<MahjongTile> dragonTiles = new HashSet()
{
new MahjongHonorTile(MahjongHonorType.GreenDragon),
new MahjongHonorTile(MahjongHonorType.RedDragon),
new MahjongHonorTile(MahjongHonorType.WhiteDragon)
}
IEnumerable<MahjongTile> dragonTilesInHand = hand.Where(t => dragonTiles.Contains(t));
My Question: how should I define equality in the MahjongTile base class?
Since Object.Equals is virtual, the subclasses implementation override the method. No further code is needed.

Categories

Resources