How should NULL properties be handled in a CompareTo method? - c#

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

Related

Sorting based on multiple fields using IComparer [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
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.

Multithread, Linq to Sql, ConcurrentDictonary Fails Remove

I have a ConcurrentDictionary of Attributes for products. These attributes have the product ID and values for the names of the attribute and any options the attribute has. I have this ConcurrentDictionary because I have threads that are created to handle each attribute in the dictionary by attribute name.
if (knownAttribute.AttributeType.Value.Equals("Product Specification"))
{
Console.WriteLine("Started a thread for: " + knownAttribute.AttributeTypeId + ", " + knownAttribute.Value);
while (true)
{
/* if (AS400SpecificationAttributes.IsEmpty && knownSpecificationBag.IsEmpty && gatherRowsTasks.All(x => x.IsCompleted))
break;*/
AS400SpecificationAttribute AS400SpecificationAttributeWork = null;
AS400SpecificationAttributeWork = knownSpecificationBag.Keys.FirstOrDefault(x => x.AttributeName == knownAttribute.Value);
if (AS400SpecificationAttributeWork != null)
{
var product = ctx.Products.FirstOrDefault(x => x.ProductNumber == AS400SpecificationAttributeWork.ProductNumber);
if (product == null)
continue;
var productAttribute = new ProductAttribute();
productAttribute.Attribute = knownAttribute;
if (AS400SpecificationAttributeWork.AttributeValue != null)
{
var knownAttributeOption = ctx.AttributeOptions.FirstOrDefault(x => x.Attribute.Equals(knownAttribute) && x.Value.Equals(AS400SpecificationAttributeWork.AttributeValue));
if (knownAttributeOption == null)
{
knownAttributeOption = new AttributeOption();
knownAttributeOption.Value = AS400SpecificationAttributeWork.AttributeValue;
knownAttributeOption.Attribute = knownAttribute;
ctx.AttributeOptions.InsertOnSubmit(knownAttributeOption);
ctx.SubmitChanges();
}
productAttribute.AttributeOption = knownAttributeOption;
productAttribute.AttributeOptionId = knownAttributeOption.Id;
}
product.ProductAttributes.Add(productAttribute);
ctx.SubmitChanges();
string tmpstr = null;
if (!knownSpecificationBag.TryRemove(AS400SpecificationAttributeWork, out tmpstr))
Thread.Sleep(50);
}
else
{
if (tryCounter < 5)
{
tryCounter++;
Thread.Sleep(1000);
Console.WriteLine("Thread waiting for work: Product Specification:" + knownAttribute.Value);
continue;
}
else
{
int outVal;
threadTracker.TryRemove("Product Specification:" + knownAttribute.Value, out outVal);
Console.WriteLine("Closing Thread: Product Specification:" + knownAttribute.Value);
break;
}
}
Thread.Sleep(50);
}
It seems like the following Attribute element refuses to be removed.
I don't understand why. If i put it in a while(!dic.tryRemove(ele)) it will forever be stuck and never move from there.
There may be an error somewhere within the thread but I have no idea why.
This statement
if (!knownSpecificationBag.TryRemove(AS400SpecificationAttributeWork, out tmpstr))
will always return true or false. It won't block. That's the behavior of ConcurrentDictionary. It will return false if the key is not in the dictionary.
If you're looping while that method returns false and it's stuck, that means that the item isn't in the dictionary when the loop begins. Either it either was never in the dictionary or that another thread already removed it.
Is your intention to loop until the item is not in the dictionary?
You could try this:
if (!knownSpecificationBag.TryRemove(AS400SpecificationAttributeWork, out tmpstr)
&& !knownSpecificationBag.ContainsKey(AS400SpecificationAttributeWork))
Implement proper equals and gethashcode when using TryRemove
public override int GetHashCode()
{
return new { this.name, this.value, this.group, this.productNumber }.GetHashCode();
}
public bool Equals(AS400SpecificationAttribute other)
{
if (other == null)
return false;
return (this.ProductNumber.Equals(other.productNumber) && ((this.group != null && this.group.Equals(other.AttributeGroup)) || (this.group == null && other.AttributeGroup == null)) && ((this.name!= null && this.name.Equals(other.AttributeName)) || (this.name == null && other.AttributeName == null)) && ((this.value != null && this.value.ToUpper().Equals(other.AttributeValue.ToUpper())) || (this.value == null && other.AttributeValue == null)));
}

Why is this dynamic list comparison failing?

I have a search that returns a result that is dynamic. So I am trying to just show a label if there are no results found. The problem i am having is i dont know how to count the result because it is dynamic and is not equal to a type.
The error message is :
Operator '!=' Cannot be applied ot operands of type
System.Collections.Generic.List and int
if (Page.IsValid)
{
string keyword = txtSearch.Text.Trim();
List<dynamic> results = SearchItems(keyword);
List<dynamic> Cresults = SearchContacts(keyword);
if(results != 0 || Cresults !=0)
{
//bind and return
LVI.DataSource = results;
LVI.DataBind();
// System.Threading.Thread.Sleep(500);
//Contact Bind return
LVC.DataSource = Cresults;
LVC.DataBind();
// System.Threading.Thread.Sleep(250);
lvAdmin.DataSource = results;
lvAdmin.DataBind();
LVCAdmin.DataSource = Cresults;
LVCAdmin.DataBind();
}
else{
NoResults.Visible = true;
}
You cannot just do:
if(results != 0 || Cresults !=0)
{
}
That way your comparing the actual List to 0, which obviously fails.
Just do:
if(results.Count != 0 || Cresults.Count !=0)
{
}
Or:
if(results.Any() || Cresults.Any())
{
}
Use the Count property of the List class:
if (results.Count != 0 || Cresults.Count != 0)
{
//rest of code
}
Docs: http://msdn.microsoft.com/en-us/library/a7f69ad7.aspx
you can try using count
if(results.Count > 0 || Cresults.Count > 0)

if then else vs the ternary operator ( ? :) in c#

this.value1 and c.value1 can both be either null or non-null. So a total of 4 combinations to test. value2 can also be null or non-null.
Can the if-then-else's below be replaced by something shorter like use the ternary operator ( if then else using the ? : operators) - and would that be a bad practice for this specific case because we are testing 4 combinations for value1 and value2?
public override bool Equals(object obj)
{
bool value1_check = false;
bool value2_check = false;
var c = obj as ObjectType;
if (this.value1 != null)
value1_check = this.value1.Equals(c.value1);
else if ((this.value1 == null) && (c.value1 == null))
value1_check = true;
else if ((this.value1 == null) && (c.value1 != null))
value1_check = c.value1.Equals(this.value1);
if (this.value2 != null)
value2_check = this.value2.Equals(c.value2);
else if ((this.value2 == null) && (c.value2 == null))
value2_check = true;
else if ((this.value2 == null) && (c.value2 != null))
value2_check = c.value2.Equals(this.value2);
return (value1_check && value2_check);
}
You can call Object.Equals(), which already does all that.
return Equals(this.Value1, c.Value1)
&& Equals(this.Value2, c.Value2);
Actually, you might want the ?? Operator.
var lhs= this.value1 ?? c.value1 ?? null;
var rhs = c.value1 ?? this.value1 ?? null;
var value1Check = lhs == rhs
Should do the same thing as yours, but almost 100% less readable!
If your still wondering about the ternary option.
value1_check= this.value1!=null? this.value1.Equals(c.value1):(c.value1!=null?c.value.Equals(this.value):value1_check=true);

Sorting List of Strings

I'm having a List<String> l_lstTemp and which contains
"A1"
"A1_1"
"A1_2"
"1A"
"B2_1"
"B1_2"
"B1_1_2"
"A10"
"B11"
"A"
"Z"
I need to sort the items based on the character and numeric value.
So the sorted list will be like
"1A"
"A"
"A1"
"A1_1"
"A1_2"
"A10"
"B1_1_2"
"B1_2"
"B2_1"
"B11"
"Z"
Here is my code:
l_lstTemp.Sort(delegate(String One, String Two)
{
Match l_mOne = Regex.Match(One, #"(\D*)(\d*)");
Match l_mTwo = Regex.Match(Two, #"(\D*)(\d*)");
int Result;
if (l_mOne.Success || l_mTwo.Success)
{
String l_strX, l_strY;
l_strX = l_mOne.Groups[1].Value;
l_strY = l_mTwo.Groups[1].Value;
Result = l_strX.CompareTo(l_strY);
if (Result != 0)
return Result;
l_strX = l_mOne.Groups[2].Value;
l_strY = l_mTwo.Groups[2].Value;
if (l_strX == String.Empty || l_strY == String.Empty)
{
Result = l_strX.CompareTo(l_strY);
if (Result != 0)
return Result;
}
else
{
long X = long.Parse(l_strX);
long Y = long.Parse(l_strY);
Result = X.CompareTo(Y);
if (Result != 0)
return Result;
}
}
return 0 ;
}
);
But its not working (sorting) properly.
How do I modify my code to sort the list properly?
Please post me a way to do this.
Thanks in advance.
I did some modifications to your code. The thing was that when both Group 1 and Group 2 are equals, you still need to check what remains.
Important: I did the modifications inside your code, so this could be a little tricky. I really suggest you refactoring your code now that you know it works:
l.Sort(delegate(String One, String Two)
{
while (One != "" && Two != "")
{
if (One == Two)
return 0;
//Add one more group to capture what remains of the expression
Match l_mOne = Regex.Match(One, #"_*(\D*)(\d*)(.*)$");
Match l_mTwo = Regex.Match(Two, #"_*(\D*)(\d*)(.*)$");
int Result;
if (l_mOne.Success || l_mTwo.Success)
{
String l_strX, l_strY;
l_strX = l_mOne.Groups[1].Value;
l_strY = l_mTwo.Groups[1].Value;
Result = l_strX.CompareTo(l_strY);
if (Result != 0)
return Result;
l_strX = l_mOne.Groups[2].Value;
l_strY = l_mTwo.Groups[2].Value;
if (l_strX == String.Empty || l_strY == String.Empty)
{
Result = l_strX.CompareTo(l_strY);
if (Result != 0)
return Result;
}
else
{
long X = long.Parse(l_strX);
long Y = long.Parse(l_strY);
Result = X.CompareTo(Y);
if (Result != 0)
return Result;
One = l_mOne.Groups[3].Value; //Store in 'One' the remaining part of the regex
Two = l_mTwo.Groups[3].Value; //The same in Two
continue; //The result will be the result of the comparison of those two new values.
}
}
}
return One.CompareTo(Two);
});
Edit:
I also added _* to remove all the _ characters from the begining of the strings. I assumed here that the strings will only contain _ after the numbers and not something like B1B or B1$.
The thing here is that you don't really explain how the comparison should be made, and I had to assume those things from your original data and the sorted data, otherwise what would happen if you want to sort A1A and A1_? What should it return?
Here's how I would implement such a comparer. Much easier to follow IMHO.
var re = new Regex(#"^(\d+)?([A-Z]+)(\d+)?(?:_(\d+)(?:_(\d+))?)?$");
Func<Group, int, int> intOrDefault = (g, d) => g.Success ? Convert.ToInt32(g.Value) : d;
list.Sort((x, y) =>
{
var xm = re.Match(x);
var ym = re.Match(y);
int cmp;
// compare the first group
// compare the leading numbers (if any)
cmp = intOrDefault(xm.Groups[1], int.MaxValue).CompareTo(intOrDefault(ym.Groups[1], int.MaxValue));
if (cmp != 0)
return cmp;
// compare letters
cmp = xm.Groups[2].Value.CompareTo(ym.Groups[2].Value);
if (cmp != 0)
return cmp;
// compare the trailing numbers (if any)
cmp = intOrDefault(xm.Groups[3], 0).CompareTo(intOrDefault(ym.Groups[3], 0));
if (cmp != 0)
return cmp;
// compare the next group
cmp = intOrDefault(xm.Groups[4], 0).CompareTo(intOrDefault(ym.Groups[4], 0));
if (cmp != 0)
return cmp;
// compare the last group
cmp = intOrDefault(xm.Groups[5], 0).CompareTo(intOrDefault(ym.Groups[5], 0));
return cmp;
});
For this example, just calling sort l_lstTemp.Sort() would get you the result you are looking for

Categories

Resources