Struct vs Class using LINQ - c#

I was working on some code when I discovered something I don't fully understand so I created the following test code:
static void Main(string[] args)
{
string myKey = "bla";
var list = new List<KeyValuePair<string, string>>();
var list2 = new List<Test>();
var bla1 = (from a in list where a.Key == myKey select a.Value).FirstOrDefault();
var bla2 = list.FirstOrDefault(x => x.Key == myKey).Value;
var bla3 = (from a in list2 where a.Key == myKey select a.Value).FirstOrDefault();
var bla4 = list2.FirstOrDefault(x => x.Key == myKey).Value;
}
struct Test
{
public string Key { get; set; }
public string Value { get; set; }
public Test(string key, string value)
{
Key = key;
Value = value;
}
}
Now as long as Test is a struct all of the 4 bla-lines work fine. But as soon as I change Test to be a class instead of a struct bla4 fails. How can this be? Isn't bla4 just another (lambda) way of doing bla3 and therefore should behave the same? Why does it make a difference whether the list contains a struct's or classes? And is there a way to have a lambda version of bla4 that doesn't throw an exception and instead returns null like the others?

The default of a class type is null. The default of a struct is an empty struct. That's why FirstOrDefault(..).Value will fail with a NullReferenceException.
You should check that FirstOrDefault() did return something before trying to access the result.
var item = list2.FirstOrDefault(x => x.Key == myKey);
var bla4 = (item == null)?null:item.Value;
In C# 6 and later you can use the ?. operator, to perform the same check:
var item = list2.FirstOrDefault(x => x.Key == myKey)?.Value;
Another option is to use ?? to replace null with a default value.
var item = list2.FirstOrDefault(x => x.Key == myKey) ?? new Test();
var bla4 = item.Value;
It's common to create a special Empty object to use in such cases, eg:
class Test
{
//...
public static readonly Empty =new Test();
}
var item = list2.FirstOrDefault(x => x.Key == myKey) ?? Test.Empty;
var bla4 = item.Value;

A struct can never be null.
Therefore your call to FirstOrDefault() will actually cause a new struct to be constructed, because default(struct) is effectively new(struct).
However if you change it to a class, then default(class) is null.
Thus the result of your call
list2.FirstOrDefault(x => x.Key == myKey)
will actually result in null being returned once it is a class. You should therefore recieve a NullReferenceException when trying to access .Value.
In layman terms what your bla3 acutally does is check if there is a matching item within the collection. If there is, it will select the property Value of that item, otherwise it will return the default of that property's type.
Your bla4 instead looks if it finds an item that matches the Key property. If it does, it selects the first of those items, if it does not, it creates a default of that item's type and returns that. You then try to acces that return value's Value property.

Isn't bla4 just another (lambda) way of doing bla3 and therefore should behave the same?
No. bla3 extracts some strings from the objects contained in a list. You then call FirstOrDefault on that set of strings and obtain a null value, because that set was empty.
bla4 calls FirstOrDefault directly on the set of objects and you then obtain the string property Value from the defaulted object.
When the objects contained in your lists are structs, then the defaulted value will contain a null Value property. That's why they superficially seem similar. But be very clear - the order in which Value is being accessed and FirstOrDefault is being called is different, and FirstOrDefault is operating on different types of things.
Of course, exactly the same can be said for bla2 vs bla1.

Related

IEnumerable.Except() on System.Linq.Enumerable+WhereSelectArrayIterator vs. List<T>

Maybe I am missing the details here but I would expect that IEnumerable.Except() would work on Enumerables not concretely cast to a collection.
Let me explain with a simple example: I have a list of files on a directory and I want to exclude the files that start with a certain string.
var allfiles = Directory.GetFiles(#"C:\test\").Select(f => new FileInfo(f));
Getting both the files matching and those not matching would be a matter of identifying one of the two collections and then .Except()-ing on the full list, right?
var matching = allfiles.Where(f => f.Name.StartsWith("TOKEN"));
and
var notmatching = allfiles.Except(matching, new FileComparer());
Where FileComparer() is some class that compares the full path of the two files.
Well, unless I cast both of the three collections to a List, the last notmatching variable still gives me the full list of files after I .Except() on the matching collection. To be clear:
var allfiles = Directory.GetFiles(#"C:\test\").Select(f => new FileInfo(f));
var matching = allfiles.Where(f => f.Name.StartsWith("TOKEN"));
var notmatching = allfiles.Except(matching, new FileComparer());
does not exclude, while
var allfiles = Directory.GetFiles(#"C:\test\").Select(f => new FileInfo(f)).ToList();
var matching = allfiles.Where(f => f.Name.StartsWith("TOKEN")).ToList();
var notmatching = allfiles.Except(matching, new FileComparer()).ToList();
actually does what is says on the tin.
What am I missing here? I can't understand why LINQ doesn't manipulate the collection not currently cast to a list.
For instance, the FileComparer does not even get called in the first case.
internal class FileComparer : IEqualityComparer<FileInfo>
{
public bool Equals(FileInfo x, FileInfo y)
{
return x == null ? y == null : (x.Name.Equals(y.Name, StringComparison.OrdinalIgnoreCase) && x.Length == y.Length);
}
public int GetHashCode(FileInfo obj)
{
return obj.GetHashCode();
}
}
The difference between the two is that without ToList, the deferred allfiles query is executed twice, producing different instances of FileInfo that will not pass reference equality.
Your FileComparer implements GetHashCode incorrectly, as it simply returns the reference-based hash code of the FileInfo objects (which does not itself override GetHashCode).
Implementations are required to ensure that if the Equals(T, T) method returns true for two objects x and y, then the value returned by the GetHashCode(T) method for x must equal the value returned for y.
The solution is to implement GetHashCode based on the same definition of equality as Equals:
public int GetHashCode(FileInfo obj)
{
return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Name);
}

LINQy way to check if any objects in a more than one collection have the same property value

I have three collections of the same model, only changing their names.
want to check the three lists so that:
1) If an element in either list is already in another burst an error
2) When I try to validate the Any does not accept null value.
How do they accept that the string is null?
but I can not.
where I am going wrong?
item.Requeridos > ICollection<MeetingUser>
item.Informados > ICollection<MeetingUser>
item.Opcionais > ICollection<MeetingUser>
public static ValidationResult validaUsuariosNaLista(Reuniao item)
{
var requeridos = item.Requeridos.Select(x => string.IsNullOrWhiteSpace(x.Login)).Any();
var informados = item.Informados.Select(x => string.IsNullOrWhiteSpace(x.Login)).Any();
var opcionais = item.Opcionais .Select(x => string.IsNullOrWhiteSpace(x.Login)).Any();
if (item.Requeridos.Count() + item.Informados.Count() + item.Opcionais.Count() > item.Requeridos.Concat(item.Informados).Concat(item.Opcionais).Distinct().Count())
{
return new ValidationResult(Resources.Validations.sameUserInOtherList);
}
return ValidationResult.Success;
}
where I am going wrong?
In the way that Distinct works. According to MSDN
The Distinct(IEnumerable) method returns an
unordered sequence that contains no duplicate values. It uses the
default equality comparer, Default, to compare values.
The default equality comparer, Default, is used to compare values of
the types that implement the IEquatable generic interface. To
compare a custom data type, you must implement this interface and
provide your own GetHashCode and Equals methods for the type.
In your case you have a custom data type, MeetingUser
What can you do?
If you avoid the use of Distinct, you can try something like the below:
// Initially you concat the three list to one list
var combinedLists = (item.Requeridos.Concat(item.Informados)).Concat(item.Opcionais);
// Then you group them by the Login. If there is any group with more that 1
// element then you have the same login more that one time.
var result = combinedLists.GroupBy(x=>x.Login)
.Any(gr=>gr.Count()>1);
Otherwise, if you stick with the Distinct, you have to implement the IEquatable interface.
Final answer:
if the object can also be null use item.Opcionais as example
Tks #Christos so much.
Yours answers were very good
public class ReuniaoValidation
{
public static ValidationResult validaUsuariosNaLista(Reuniao item)
{
var requeridos = item.Requeridos.Select(x => string.IsNullOrWhiteSpace(x.Login)).Any();
var informados = item.Informados.Select(x => string.IsNullOrWhiteSpace(x.Login)).Any();
var opcionais = item.Opcionais ?? Enumerable.Empty<MeetingUser>();
var listasCombinadas = (item.Requeridos.Concat(item.Informados)).Concat(item.Opcionais ?? Enumerable.Empty<MeetingUser>());
if (listasCombinadas.GroupBy(x => x.Login).Any(gr => gr.Count() > 1))
{
item.AddOdm = false;
return new ValidationResult(Resources.Validations.ValidaUsuarioMesmaLista);
}
return ValidationResult.Success;
}

Object reference not set to an instance while not being null

I'm getting some unexpected behavior in my process. I'm doing the following.
IEnumerable<Thing> things = ...;
IEnumerable<Thing> subset = things.Where(a => a.SomeFlag);
String info = "null: " + (subset == null);
The above works and info tells me that the object isn't null. So I wish to check the number of the elements in subset by this.
IEnumerable<Thing> things = ...;
IEnumerable<Thing> subset = things.Where(a => a.SomeFlag);
String info = "null: " + (subset == null);
String count = subset.Count();
Now I get an exception giving me the error message:
Object reference not set to an instance of an object.
What do I miss?!
It's possible that one of the Thing's in subset is null. You can try this:
IEnumerable<Thing> subset = things.Where(a => a != null && a.SomeFlag);
Note that due to the way Linq's lazy evaluation, you won't get any exception you call .Where because all it's doing at that point is setting up a condition for filtering the elements of things. Only later when you call .Count is it actually evaluating the results.
Update: With the new null-condition operator in C# 6 (also called the safe navigation or 'Elvis' operator), we can do the same thing a bit more succinctly:
IEnumerable<Thing> subset = things.Where(a => a?.SomeFlag);
An IEnumerable<Thing> implies deferred execution.
In your first fragment subset and things are never enumerated.
In the second fragment, it is the call to Count() that enumerates the lists and only then it comes to light that one of the a is null in a => a.SomeFlag.
You can see what really happens here with a bit simplified example.
Test class:
public class Test
{
public int Value { get; set; }
}
And LINQ query on IEnumerable<Test>:
IEnumerable<Test> source = new List<Test>() {
new Test { Value = 10 },
null,
new Test { Value = 20 }
};
IEnumerable<Test> filteredSource = source.Where(x => x.Value > 10);
// return false
Console.WriteLine(filteredSource == null);
// throws NullReferenceException
Console.WriteLine(filteredSource.Count());
Why does it happens? Because filteredSource == null does not cause collection enumeration, so Where predicate is not being fired on any source collection element.
However, when you call Count() on filteredSource the predicate is being called on every item from source collection, and when it comes to an item which is null: null.Value > 10 throws the exception.
How to make it work? Extend the predicate with x != null check:
IEnumerable<Test> filteredSource = source.Where(x => x != null && x.Value > 10);
Ok, so suppose you had the following items in things:
Thing A SomeFlag = true
Thing B SomeFlag = false
null
Thing C SomeFlag = true
First you count all the items in things. So you iterate over 4 objects, find 4 objects, and know that the result is 4. Easy.
Now you want to count all of the items in subset which means you need to figure out which items are in subset in the first place. So you go about counting them:
Thing A .... A.SomeFlag is true, so count it
Thing B .... B.SomeFlag is not true, so don't count it
null .... null.SomeFlag NULLREFERENCEEXCEPTION
And that's where your error comes from.
Note that even if all of the elements in things are not null, you can still get a NullReferenceException if the .SomeFlag get accessor has a side effect that could cause a NullReferenceException.

Remove items from an Object [] that are contained in a custom class array

I have an array of custom items and then I have an array of objects object[], returned by a function. This object array is indeed of the same Clients type but it is returned as array of object type (I cannot edit this aspect).
I tried with an explicit cast, but I get an exception at runtime saying: cannot convert object[] to clients[]. My goal, once I have both arrays, would be to remove from one list the items that are present in the other list too and make an union, in order to have a unique list.
var client = getClients().ToArray(); //Is an array of Clients[]
var normalSearch = getDuplicates().ToArray(); //Is an array of object[]
//This what I try to achieve,but being object, I cannot invoke "c.ContactId" (Line 4)
var uni = (from c in normalSearch
where !(from d in dupes
select d.ContactId)
.Contains(c.ContactId)
select c).ToArray();
I know that in Linq Union() could exclude automatically duplicate if a primitive type is used, otherwise an extension has to be developed with custom types. But I have not access to the rest of the code, therefore no possibility to change the logic elsewhere.
getDuplicates().Cast<Client>();
var uni = client.Union(normalSearch.Cast<Client>())
.DistinctBy(c => c.ContractId);
The DistinctBy is in MoreLinq.
If you can't use MoreLinq, you can simply do
var uni = client.Union(normalSearch.Cast<Client>())
.GroupBy(c => c.ContractId)
.Select(g => g.First());
Which is basically their implementation of it.
You can cast the duplicates to clients using linq, and then create a comparer class. I copy and pasted this from the msdn documentation, it may need to be tweaked
var normalSearch = getDuplicates().ToArray().Cast<Clients>();
var client = getClients().ToArray();
var unique = client.Union(normalSearch, new ClientsComparer());
public class ClientsComparer : IEqualityComparer<Clients>
{
public bool Equals(Clients x, Clients y)
{
//Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
//Check whether the products' properties are equal.
return x.ContactId == y.ContactId;
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(Client client)
{
//Check whether the object is null
if (Object.ReferenceEquals(product, null)) return 0;
//Calculate the hash code for the product.
return client.ContactId.GetHashCode();
}
}

Is a Collection Null

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.

Categories

Resources