Sorting based on multiple fields using IComparer [closed] - c#

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
I use the below code to sort List<DataAccessViewModel> list.
Here is the sort order :
PriorityScore
MName
CName
FName
It works as expected.
public int Compare(DataAccessViewModel x, DataAccessViewModel y)
{
if (x == null || y == null)
{
return 0;
}
return x.CompareTo(y);
}
public int CompareTo(DataAccessViewModel mod)
{
int retval = (int)(this.PriorityScore?.CompareTo(mod.PriorityScore));
if(retval != 0)
return retval;
else
{
retval = (this.MName ?? "zzzzzzzzzzzzz").CompareTo(mod.MName ?? "zzzzzzzzzzzzz");
if (retval != 0)
return retval;
else
{
retval = (this.CName ?? "zzzzzzzzzzzzz").CompareTo(this.CName ?? "zzzzzzzzzzzzz");
if (retval != 0)
return retval;
else
retval = (this.FName ?? "zzzzzzzzzzzzz").CompareTo(this.FName ?? "zzzzzzzzzzzzz");
}
}
return retval;
}
But the code looks clunky to me. Is there any better way of doing it or is this it ?

There's a issue in the current code:
public int Compare(DataAccessViewModel x, DataAccessViewModel y)
{
if (x == null || y == null)
{
return 0;
}
...
Since null equals to any value then all values are equal:
a == null, null == b => a == b
It's not the rule you want to implement. I suggest something like this
using System.Linq;
...
// static: we don't want "this"
public static int TheCompare(DataAccessViewModel x, DataAccessViewModel y) {
// Special cases, nulls
if (ReferenceEquals(x, y)) // if references are shared, then equal
return 0;
if (null == x) // let null be smaller than any other value
return -1;
if (null == y)
return 1;
// How we compare strings
static int MyCompare(string left, string right) =>
ReferenceEquals(left, right) ? 0
: null == left ? 1 // null is greater than any other string
: null == right ? -1
: string.Compare(left, right);
// Func<int> for lazy computation
return new Func<int>[] {
() => x.PriorityScore?.CompareTo(y.PriorityScore) ?? -1,
() => MyCompare(x.MName, y.MName),
() => MyCompare(x.CName, y.CName),
() => MyCompare(x.FName, y.FName),
}
.Select(func => func())
.FirstOrDefault(value => value != 0);
}
And then for interface we have
public int CompareTo(DataAccessViewModel other) => TheCompare(this, other);
// I doubt if you want to implement both IComparable<T> and
// IComparer<T> but you can easily do it
public int Compare(DataAccessViewModel x, DataAccessViewModel y) =>
TheCompare(x, y);

You're almost there: just skip the else statements which follow a return:
public int CompareTo(DataAccessViewModel mod)
{
// I worry about this line -- if you're sure that PriorityScore is not null,
// why not use this.PriorityScore.Value to make that clear?
int retval = (int)(this.PriorityScore?.CompareTo(mod.PriorityScore));
if (retval != 0)
return retval;
retval = (this.MName ?? "zzzzzzzzzzzzz").CompareTo(mod.MName ?? "zzzzzzzzzzzzz");
if (retval != 0)
return retval;
retval = (this.CName ?? "zzzzzzzzzzzzz").CompareTo(this.CName ?? "zzzzzzzzzzzzz");
if (retval != 0)
return retval;
return (this.FName ?? "zzzzzzzzzzzzz").CompareTo(this.FName ?? "zzzzzzzzzzzzz");
}
You can also handle the nullables properly by using Comparer<T>.Default. This will sort null as equal to other null values, but less than any other object.
public int CompareTo(DataAccessViewModel mod)
{
int retval = Comparer<int?>.Default.Compare(this.PriorityScore, mod.PriorityScore);
if (retval != 0)
return retval;
retval = Comparer<string>.Default.Compare(this.MName, mod.MName);
if (retval != 0)
return retval;
retval = Comparer<string>.Default.Compare(this.CName, mod.CName);
if (retval != 0)
return retval;
return Comparer<string>.Default.Compare(this.FName, mod.FName);
}

Consider leveraging the default behavior of ValueTuples:
IComparer (this should probably not be the same as your class)
public int Compare(DataAccessViewModel x, DataAccessViewModel y)
{
if (x == null && y == null)
{
return 0;
}
if (x == null) return -1;
if (y == null) return 1;
var thisCompareOrder = (x.PriorityScore, x.MName, x.CName, x.FName);
var thatCompareOrder = (y.PriorityScore, y.MName, y.CName, y.FName);
return thisCompareOrder.CompareTo(thatCompareOrder);
}
If you want your class to have these semantics by default, implement IComparable.
public int CompareTo(DataAccessViewModel mod)
{
if(mod == null) return 1;
var thisCompareOrder = (this.PriorityScore, this.MName, this.CName, this.FName);
var thatCompareOrder = (mod.PriorityScore, mod.MName, mod.CName, mod.FName);
return thisCompareOrder.CompareTo(thatCompareOrder);
}
You might also consider making your original class a record, which has value-based semantics by default.

Related

How should NULL properties be handled in a CompareTo method?

I want to compare 2 instances of a class to see if they are equal.
I created this method that is faulty:
public int CompareTo(AdminEngineTableRowModel other)
{
int result;
if (other != null)
{
result = NXSTDT.CompareTo(other.NXSTDT);
if (result == 0)
result = DALOC.CompareTo(other.DALOC);
if (result == 0)
result = DealerID.CompareTo(other.DealerID);
if (result == 0)
result = Status.CompareTo(other.Status);
if (result == 0)
result = OrderNumber.CompareTo(other.OrderNumber);
if (result == 0)
result = Dealership.CompareTo(other.Dealership);
if (result == 0)
result = Serial.CompareTo(other.Serial);
if (result == 0)
result = Model.CompareTo(other.Model);
if (result == 0)
result = OrderDate.CompareTo(other.OrderDate);
if (result == 0)
result = StartDate.CompareTo(other.StartDate);
if (result == 0)
result = Confirmed.CompareTo(other.Confirmed);
if (result == 0)
result = Deposit.CompareTo(other.Deposit);
if (result == 0)
result = Engine.CompareTo(other.Engine);
if (result == 0)
result = Color.CompareTo(other.Color);
if (result == 0)
result = Name.CompareTo(other.Name);
}
else
{
result = 1;
}
return result;
}
The problem comes when any of the properties, like the first string NXSTDT is NULL:
result = NULL.CompareTo(other.NXSTDT); // This causes an error
I could create a class ctor constructor to initialize all of the properties, or I could edit the CompareTo method to check that each property is not null before I test it:
public int CompareTo(AdminEngineTableRowModel other)
{
int result;
if (other != null)
{
if ((NXSTDT == null) && (other.NXSTDT != null))
result = 1;
if (result == 0)
result = NXSTDT.CompareTo(other.NXSTDT);
if ((DALOC == null) && (other.DALOC != null))
result = 1;
if (result == 0)
result = DALOC.CompareTo(other.DALOC);
// {snip}
}
else
{
result = 1;
}
return result;
}
That just seems like poor programming.
Is there a better way to do this?
One idea would be to write a generic Compare<T> method that works will any object that implements IComparable<T>. Then you can write the null-handling once and not worry about it again. This does assume that the types you're comparing implement that interface.
public static int Compare<T>(T first, T second) where T : IComparable<T>
{
if (ReferenceEquals(first, second)) return 0;
if (first == null) return -1;
return first.CompareTo(second);
}
Then your code would look like:
public int CompareTo(AdminEngineTableRowModel other)
{
if (ReferenceEquals(this, other)) return 0;
if (other == null) return 1;
int result = Compare(NXSTDT, other.NXSTDT);
if (result != 0) return result;
result = Compare(DALOC, other.DALOC);
if (result != 0) return result;
result = Compare(DealerID, other.DealerID);
if (result != 0) return result;
result = Compare(Status, other.Status);
if (result != 0) return result;
result = Compare(OrderNumber, other.OrderNumber);
if (result != 0) return result;
result = Compare(Dealership, other.Dealership);
if (result != 0) return result;
result = Compare(Serial, other.Serial);
if (result != 0) return result;
result = Compare(Model, other.Model);
if (result != 0) return result;
result = Compare(OrderDate, other.OrderDate);
if (result != 0) return result;
result = Compare(StartDate, other.StartDate);
if (result != 0) return result;
result = Compare(Confirmed, other.Confirmed);
if (result != 0) return result;
result = Compare(Deposit, other.Deposit);
if (result != 0) return result;
result = Compare(Engine, other.Engine);
if (result != 0) return result;
result = Compare(Color, other.Color);
if (result != 0) return result;
return Compare(Name, other.Name);
}
If you just want to compare 2 instances for equality, it is simpler to use a boolean Equals() method, like so:
public bool Equals(AdminEngineTableRowModel other)
{
if (other == null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
// If applicable, use <Property>.Equals(other.<Property>)
// For example, to compare strings according to culture
// and case
return (NXSTDT == other.NXSTDT &&
DALOC == other.DALOC &&
DealerId == other.DealerId &&
Status == other.Status &&
OrderNumber == other.OrderNumber &&
....);
}
This way, due to short-circuiting, the method will return false on the first un-equal property value.
If you use at least C# 9 you don't need to actually implement anything, just add record keyword to your class:
public record class YourClass {}
it will automatically generate the Equals, GetHashCode methods among others and overload the == operator so you can simply do:
if (instanceA == instanceB) {}
https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/records

What is the clean way for checking null equality for a class implementing IEqualityComparer<T>? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
This is what I could come up with to check for null equality. It works but it is Shreak looking. The following is valid for this example:
x != null and y == null returns false;
x == null and y != null returns false;
x == null and y == null returns true;
x.ToString() == y.ToString() returns true;
I guess I could code the above but I still feel there is a cleaner way to solve it.
public bool Equals(ConvertibleData x, ConvertibleData y)
{
if (x == null)
{
if (y == null)
{
return true;
}
return false;
}
if (y == null)
{
return false;
}
return x.ToString() == y.ToString(); //Disregard the ToString, this coult be any comparer validation
}
I usually use this pattern where the comparands can be null:
public bool Equals(ConvertibleData? x, ConvertibleData? y)
{
if (ReferenceEquals(x, y))
return true;
if (x is null || y is null)
return false;
return x.ToString() == y.ToString(); //Disregard the ToString, this coult be any comparer validation
}
I like this pattern:
if (x is null && y is null)
return true;
if (x is null || y is null);
return false;
// Check x's properties against y's properties
All your answers are better than code in the question above. I found this example from the official microsoft reference website about IEqualityComparer:
link
public bool Equals(Box b1, Box b2)
{
if (b2 == null && b1 == null)
return true;
else if (b1 == null || b2 == null)
return false;
else if(b1.Height == b2.Height && b1.Length == b2.Length
&& b1.Width == b2.Width)
return true;
else
return false;
}

c# find in text something or something

I need to search in text, but is it possible to have more than one option in richtextbox.Find("something");? For example richtextbox.Find("something" or "somethingelse");
You can implement something like this (an extension method FindAny for RichTextBox):
public static class RichTextBoxExtensions {
public static int FindAny(this RichTextBox source, params String[] toFind) {
if (null == source)
throw new ArgumentNullException("source");
else if (null == toFind)
throw new ArgumentNullException("toFind");
int result = -1;
foreach (var item in toFind) {
if (null == item)
continue;
int v = source.Find(item);
if ((v >= 0) && ((result < 0) || (v < result)))
result = v;
}
return result;
}
}
....
int result = richtextbox.FindAny("something", "somethingelse");

How can i implement == and check for null in c# [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicates:
What is “Best Practice” For Comparing Two Instances of a Reference Type?
How do I check for nulls in an '==' operator overload without infinite recursion?
I have a class called "Criterion" and i want to implement == operator,but I'm struggling with the following problem :
When i implement the == operator, I'm checking if one or both of my instances are null, but when i do it, it causes a recursive call for == and then i get "StackOverflow" (he he) exception.
Technically i can implement the Equals operator and not override the ==, but the code will be much more readable if i implement the == operator.
Here is my code :
public static bool operator == (Criterion c1, Criterion c2)
{
if (null == c1)
{
if (null == c2)
return true;
return false;
}
if (null == c2)
return false;
if ((c1.mId == c2.mId) && (c1.mName == c2.mName))
return true;
return false;
}
Try ReferenceEquals:
public static bool operator ==(Criterion c1, Criterion c2) {
var nullC1 = ReferenceEquals(null, c1);
var nullC2 = ReferenceEquals(null, c2);
if (nullC1 && nullC2)
return true;
if (!nullC1 && !nullC2)
if (c1.mId == c2.mId && c1.mName == c2.mName)
return true;
return false;
}
I'd do it like this:
bool isC1Null = Object.Equals(c1, null)
bool isC2Null = Object.Equals(c2, null)
if (isC1Null ^ isC2Null)
{
return false
}
if (isC1Null && isC2Null)
{
return true
}
//your code

Library with Equals and GetHashCode helper methods for .NET

Google Guava provides nice helpers to implement equals and hashCode like the following example demonstrates:
public int hashCode() {
return Objects.hashCode(lastName, firstName, gender);
}
Is there a similar library for Microsoft .NET?
I don't see why you'd need one. If you want to create a hash-code based on the default GetHashCode for 3 different items, then just use:
Tuple.Create(lastName, firstName, gender).GetHashCode()
That'll boil down to the equivalent of:
int h1 = lastName.GetHashCode();
int h2 = firstName.GetHashCode();
int h3 = gender.GetHashCode();
return (((h1 << 5) + h1) ^ (((h2 << 5) + h2) ^ h3));
Which is pretty reasonable for such a general-purpose combination.
Likewise:
Tuple.Create(lastName, firstName, gender).Equals(Tuple.Create(lastName2, firstName2, gender2))
Would boil down to the equivalent of calling:
return ((lastName == null && lastName2 == null) || (lastName != null && lastName.Equals(lastName2)))
&& ((firstName == null && firstName2 == null) || (firstName != null && firstName.Equals(lastName2)))
&& ((gender == null && gender2 == null) || (gender != null && gender.Equals(lastName2)));
Again, about as good as you could expect.
AFAIK none. However, writing your own shouldn't be too complex (nb using a variation of the Bernstein hash):
public static class Objects
{
public static bool Equals<T>(T item1, T item2, Func<T, IEnumerable<object>> selector)
{
if (object.ReferenceEquals(item1, item2) return true;
if (item1 == null || item2 == null) return false;
using (var iterator1 = selector(item1).GetEnumerator())
using (var iterator2 = selector(item2).GetEnumerator())
{
var moved1 = iterator1.MoveNext();
var moved2 = iterator2.MoveNext();
if (moved1 != moved2) return false;
if (moved1 && moved2)
{
if (!Equals(iterator1.Current, iterator2.Current)) return false;
}
}
return true;
}
public static bool Equals(object item1, object item2)
{
return object.Equals(item1, item2);
}
public static int GetHashCode(params object[] objects)
{
unchecked
{
int hash = 17;
foreach (var item in objects)
{
hash = hash * 31 + item.GetHashCode();
}
return hash;
}
}
}

Categories

Resources