I have a .Find statement like so:
MyObject obj = existingObjs.Find(x => x.listA == currentObj.listA &&
x.listB == currentObj.listB);
Unfortunately "obj" is always null because my "currentObj" has a listB = null but "x.listB" is always an empty list. I want to treat null and empty list as the same thing, how would I put that into my .Find statement?
I tried something like so but, it is returning a bool instead of an obj so it won't compile.
MyObject obj = existingObjs.Find(x => x.listA == currentObj.listA &&
(x.listB == null ? null : currentObj.listB));
Some points to note:
you should rather not use == for comparing complicated objects like lists, since == tests for reference equality, not the structure (e.g. whether elements on the list are the same). For comparing lists you could use Enumerable.SequenceEqual
mixing null and empty list in the same variable is a bad practice that will hit you in many places just like here, but for handling null vs empty there is nullish coalescing operator x.listB ?? Enumerable.Empty() that will always return enumerable object that you can use for comparisons
all of this should be put into bool Equals(MyObject other) method
for example
using System;
using System.Linq;
class MyObject<T> : IEquatable<MyObject<T>> {
public bool Equals(MyObject<T> other) {
if (other == null)
return false;
return ListsEqual(listA, other.listA) &&
ListsEqual(listB, other.listB);
}
bool ListsEqual(IList<T> l1, IList<T> l2) {
return Enumerable.SequenceEqual(ListOrEmpty(l1), ListOrEmpty(l2));
}
IEnumerable<T> ListOrEmpty(IList<T> li) {
return li ?? Enumerable.Empty<T>();
}
IList<T> listA;
IList<T> listB;
}
You could use the conditional operator:
currentObj.listB == null ?
!Any(x.listB) :
Enumerable.SequenceEqual(currentObj.listB, x.listB)
You could use null-coalescing operator to make sure array in currentObject will be empty arrays when they are null and then compare them:
currentObject.listA ??= Array.Empty<ListAType>();
currentObject.listB ??= Array.Empty<ListBType>();
MyObject obj = existingObjs.Find(x => Enumerable.SequenceEqual(x.listA, currentObj.listA) &&
Enumerable.SequenceEqual(x.listB, currentObj.listB));
Related
I am comparing two excel sheets using C#.
I have assigned the sheets to datatables and I hope to compare the values in each cell. I have converted the datatables to lists and I'm iterating through them comparing them. How do I avoid a NullReferenceException if one of the values contains a null?
I am hitting a NullReferenceException at this point in the code:
if (lst1[i].ToString() != lst2[j].ToString())
Is there a way to guard against nulls here or do I need to handle null values earlier in the code?
Thanks
One good way to do this is to ask if the value is null before call .ToString().
So, would be like:
if (lst1[i] != null && lst2[j] != null && lst1[i].ToString() != lst2[j].ToString())
if (lst1[i]?.ToString() != lst2[j]?.ToString())
More info on the ?. operator
Just test for null before dereferencing lst1[i] and lst2[j]
if(lst1[i] is not null and lst2[j] is not null)
{
if (lst1[i].ToString() != lst2[j].ToString())
{
...
}
}
Assuming
if both items are null then they are equal;
if both items are not null and their string representation are equal then they are equal;
in any other case they are not equal;
you can make comparison of your lists more readable With following LINQ query.
var query = list1
.Zip(list2, (a, b) => new { a, b })
.Any(m => !((m.a == null && m.b == null) || (m.a != null && m.b != null && m.a.ToString() == m.b.ToString())));
if (query)
{
Console.WriteLine("Lists are not equal.");
}
else
{
Console.WriteLine("Lists are equal.");
}
Since you're doing ToString() it looks like both lists are typed as objects. Since they're coming from Excel they're presumably all either strings or simple value types.
In that case you can do
if(lst1.SequenceEquals(lst2))
That compares each item in both lists and ensure that they're equal. It accounts for nulls. You wouldn't need to compare each item one at a time. This compares the entire list. I'd write unit tests to make sure it doesn't throw any curves.
If for some reason you wanted to do a text-based comparison ("2" == 2) then you could create a comparer like this:
public class ObjectTextComparer : IEqualityComparer<object>
{
public bool Equals(object x, object y)
{
return x?.ToString() == y?.ToString();
}
public int GetHashCode(object obj)
{
return obj?.GetHashCode() ?? 0;
}
}
and then this would return true:
var x = new object[] { "x", 2, null, "y" };
var y = new object[] { "x", "2", null, "y" };
var comparer = new ObjectTextComparer();
var listsAreEqual = x.SequenceEqual(y, comparer);
That second option is a can of worms, though, if the values in one list are stored in Excel as text and the other ones as numbers or dates.
For example if one list contains a date and the other list contains the same date stored as text, the comparer is going to convert the first one to a string, but it might not be formatted the same way as the other one stored as text, and the two strings won't match.
so I've got two lists of objects, objects have multiple fields, but I'd like to distinct them basing on only two of them.
To give you the picture, object KeyAndValue consists of fields Key and Tag, so:
list1 = { obj1(key=1,tag=A), obj2(key=2,tag=A) }
list2 = { obj3(key=1,tag=A), obj4(key=2,tag=B) }
I'm currently using:
list1.Where(x => !list2.Any(y => y.key == x.key)).ToList();
The correct result is: obj1, obj2 and obj4 as obj3 has the same key and tag as obj1
What I'm trying to accomplish is to speed this process up as with a lot of objects it takes much too long. I found that custom IEqualityComparer could help here, so I've written my own basing on MS Specification
It looks like this:
class KeyComparer : IEqualityComparer<KeyAndValue>
{
public bool Equals(KeyAndValue x, KeyAndValue y)
{
if (Object.ReferenceEquals(x, y))
return true;
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
return x.key == y.key && x.tag == y.tag;
}
public int GetHashCode(KeyAndValue keyAndValue)
{
if (Object.ReferenceEquals(keyAndValue, null))
return 0;
int hashKeyAndValueKey = keyAndValue.key == null ? 0 : keyAndValue.key.GetHashCode();
int hashKeyAndValueTag = keyAndValue.tag == null ? 0 : keyAndValue.tag.GetHashCode();
return hashKeyAndValueKey ^ hashKeyAndValueTag;
}
}
And I use it like this:
list1.Except(list2, new KeyComparer()).ToList();
Unfortunately it does only remove duplicates from list2. It seems that it does not even touch list1 and I do not know if it's the fault of my custom comparer, the way I use it or maybe I should use another method. I've been looking through other questions but could not find a working answer (or at least one that I'd actually know how to implement properly).
I think you don't need Except. You want to have distinct values of both?
var distinctValues = list1.Union(list2).Distinct();
You need to either implement GetHashCode/Equals in KeyAndValue or use a comparer to compare the objects by key and value.
(Old stuff bellow)
Not sure whether I understand the question correctly. Could it be that you didn't recognize that Except create a new IEnumerable and ToList a new List?
try:
var list1AdditionalItems = list1.Except(list2, new KeyComparer()).ToList();
and probably also:
var list2AdditionalItems = list2.Except(list1, new KeyComparer()).ToList();
Another observation. in this code:
list1.Where(x => !list2.Any(y => y.key == x.key)).ToList();
You only check the key. If you want this behaviour, you should write the comparer accordingly:
class KeyComparer : IEqualityComparer<KeyAndValue>
{
public bool Equals(KeyAndValue x, KeyAndValue y)
{
if (ReferenceEquals(x, y))
return true;
if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
return false;
return x.key == y.key;
}
public int GetHashCode(KeyAndValue keyAndValue)
{
if (ReferenceEquals(keyAndValue, null))
return 0;
return keyAndValue.key == null ? 0 : keyAndValue.key.GetHashCode();
}
}
Last but not least: When you need performance, consider using a dictionary instead.
As Stefan Steinegger (whom I thank grately for the effort and time spent) mentioned in the first comment that neither of my methods return obj4 I found an issue and decided to implement totally different approach. Now my KeyAndValue class has also an int Hash field, when a constructor is called Hash field is filled with key.GetHashCode() ^ tag.GetHashCode(). It simplified the comparison as now I'm first combining list1 with list2 and then sending it through: CombinedList.GroupBy(x => x.Hash).Select(y => y.First()).ToList(); Results seem to be correct :)
object bread(food foo)
{
return foo.ingredient ?? "cheese";
}
If foo exits, but ingredient is null, I get "cheese".
My question, including an assumption:
If foo itself is null will "chesse" be returned or will it throw an ArgutmentNullException?
My GUESS is that the NullCoalescingOperator is implemented more or less like this:
object nco(object lhs, object rhs)
{
if(lhs != null)
return lhs;
else
return rhs;
}
Therefore passing foo.ingredient already causes the exception(since you cannot check a field in an object that you don't have) and thus it is thrown.
Would make sense.
Is this idea correct / how is the nco implemented and why?
You will get a NullReferenceException if foo is null.
So you've got to treat this case, with a ternary (if else) rather than a coalesce.
return (foo == null || foo.ingredient == null)
? "cheese"
: foo.ingredient;
Yes, you idea is absolutely correct because:
foo.ingredient ?? "cheese";
Equals to:
foo.ingredient != null ? foo.ingredient : "cheese";
What you might like, in VS2014 CTP they already have new operator ?., which will do what you need:
foo?.ingredient ?? "cheese";
Of course it'll throw an exception. You cannot access a property of the object, that's not there. If you want secure yourself from an error, you could write:
return (foo == null) ? "cheese" : (foo.ingredient ?? "cheese");
I have an object model MyObject that contains a nullable byte as one of its properties.
How do I sort a list of MyObjects on this key so that the list is ordered by ascending based on the values of this properties, with the objects that have the null appearing last.
Thanks for your suggestions.
Linqs OrderBy comes with an overload which accepts an IComparer. This way you can sort the objects all the way you want.
Quick example:
public class NullByteComparer : IComparer<byte?>
{
public int Compare(byte? a, byte? b)
{
if (a == b)
return 0;
if (a == null)
return 1;
if (b == null)
return -1;
return return a < b ? -1 : 1;
}
}
Use it
yourObjects.OrderBy(x => x.NullByteProperty, new NullByteComparer());
Create a custom implementation of the IComparer<byte?> interface.
Use ?? operator to force null values to the back, like this:
var res = myList.OrderBy(v => (uint?)v.NullableByteProp ?? uint.MaxValue);
You need to cast to uint?, because otherwise your nulls will sort together with your 0xFFs.
You can use the GetValueOrDefault() function to provide a value when null equal to Byte.MaxValue
var orderedValues = values.OrderBy(v => v.GetValueOrDefault(Byte.MaxValue));
var customers = new List<BECustomer>();
customers = GetCustomers();
But I don't understand why customers[0] == null even if
customers.Any()==true or
customers.Count == 1 or
customers==null is false
How do I check for null customers?
This is a difference.
You list is not null since you instantiated it with new List() and then assign getCustomers(). Maybe this is returning null.
But an element in the list can be null. For example:
customers[0] = null
Then you have set the first element in the array to null.
So to summarize:
if (customers == null)
Checks if the customerzs variable points to null
if (customers[0] == null)
Checks if the first element in the array is null
Try the following:
customers.Any(c => c == null)
You can check that with customers.Contains(null). See more here.
If you want to check if any item of a collection is null, you can use this extension method.
public static bool AnyNull<T>(this IEnumerable<T> items)
where T : class
{
return items.Any(item => item == null);
}
Usage:
var customers = GetCustomers();
bool anyCustomerNull = customers.AnyNull();
new List<BECustomer().Add(null) will have the same effect. Non-null, non-empty list whch contains null element./
Because there is one customer, that is set to null.