c# list select unexpected (for me) behaviour - c#

1. var test = new List<foo>() { new foo { prop1 ="1prop1", prop2 = "1prop2" }, new foo { prop1 = "2prop1", prop2 = "2prop2" } };
2. var test2 = test.Select(x => x.prop1 = "changed");
3. var test3 = test2.First();
Please, explain this behaviour to me.
Why foo.prop1 values change after line 3?

This is to do with deferred execution. Most linq methods defer execution until the resulting enumerable is actually enumerated. So when you run the Select statement it just creates an Enumerable that is ready to run the appropriate selector.
When you call First on the enumerable it runs the transform on the first item, thus changing its value.
This all assumes that you intended to write x.prop1 = "changed" and not x.prop1 == "changed". The former is the assignment operator which sets the value of x.prop1 and returns the set value. The latter is the equality operator and will return a boolean based on whether they are equal or not.

= is assignment, which means it actually changes values.
You want to use == instead, to check for equality.
Try:
var test2 = test.Select(x => x.prop1=="changed");

You are making an assignment = when you probably want to make an equality comparison ==.
var test2 = test.Select(x => x.prop1 == "changed");

Related

Why does using LINQ change properties when used in if

when I use someList.Where(t => t.isTrue = true) nothing happens. But when I use code as given below,
if(someList.Where(t => t.isTrue = true).Count() > 0)
return;
All items inside the list are set to true. Why this is happening?
Edit : I am not trying to assign or compare anything. I am curious about why this happens when used with if.
This happens because you use an assignment instead (=) of equality compare (==).
Also it only happens when you use Count because LINQ only evaluates the lambda expression when it has to get a value.
var q = someList.Where(t => t.isTrue = true); // Nothing will happen
q.ToList() // would happen here
if(q.Count() > 0 ) { .. } // Also here
To compare and not assign the value you should use:
var q = someList.Where(t => t.isTrue == true);
var q = someList.Where(t => t.isTrue); // Or simpler
The reason the compiler allows this is because assignment is an expression that has a value. For example :
int a = 10;
int b;
int c = (b = a) ; // (a=b) is of type int even though it also assigns a value, and b and c will have a value of 10
In your case, the assignment of a bool has type bool, which happens to be a valid return value for a lambda passed to Where
All items inside the list are set to true when you use = and then evaluates the expression by using Count().
As the isTrue is a boolean this would be enough to count the values which is true
if(someList.Where(t => t.isTrue).Count() > 0)
return;
As an alternative to checking if the count is higher than 0 you can use the Any method which already does just that
if(someList.Where(t => t.isTrue).Any()) // Any returns true if there are any elements in the collection
return;
You can further simplify this with an overload of Any that takes the condition as a parameter, skiping the additional Where
if(someList.Any(t => t.isTrue)) // This overload takes a function that returns a boolean like Where does and returns true if there is at least 1 element that matches the condition
return;

Strange syntax in ef

var items = context.Items.Where(x => x.IsActive=true).ToList();
Why is correct syntax and working query?
This is a very subtle bug in the code. The Where Func needs to return a bool to be valid, but you are setting the value, rather than comparing it, so there's nothing to return, yes?
General Explanation
The code compiles because when you assign a value in c#, e.g. x = 1 that expression is evaluated, and therefore returned, as the value which was assigned (1).
People sometimes use this to lazily instantiate a readonly property, e.g.
private Foo myFoo;
public Foo FooInstance
{
// set myFoo to the existing instance or a new instance
// and return the result of the "myFoo ?? new Foo()" expression
get { return myFoo = myFoo ?? new Foo(); }
}
or assign the same value to multiple variables:
// set z to 1
// set y to result of "z = 1"
// set x to result of "y = z = 1"
x = y = z = 1;
Your Scenario
So what you are doing for each entry in the list is set IsActive to true and return that same true from the function. So then you end up with a new List containing all entries, and all of them have been changed to Active.
If you were using a property in the Where which is not a bool, such as an int, you would get a compilation error:
Cannot implicitly convert type 'int' to 'bool'.
See this as an example (https://dotnetfiddle.net/9S9NAV)
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var foos = new List<Foo>()
{
new Foo(true, 1),
new Foo(false, 2)
};
// works
var actives = foos.Where(x => x.IsActive=true).ToList();
// returns 2, not 1!
Console.WriteLine(actives.Count);
// compile error
var ages = foos.Where(x => x.Age = 1).ToList();
}
}
public class Foo {
public Foo(bool active, int age)
{
this.IsActive = active;
this.Age = age;
}
public bool IsActive { get; set; }
public int Age { get; set; }
}
Your example is working as your are assigning the value and not comparing value which is syntactically correct and hence it compiles and executes correctly. Note that = is used for assignment and == is used for comparison.
When you say:
var items = context.Items.Where(x => x.IsActive=true).ToList();
In this you are not comparing rather assigning the true value to IsActive which is fine for the compiler and hence you find it working.
Why it is working?
I think works because 'x.IsActive = true' will always evaluate to true. So it's not syntactically incorrect.
In other words the code is saying:
Give me all Items
Set IsActive to true for each Item
Return all Items Where(x => x.IsActive)
So as all items are set to true it will return everything.
use == for checking condition
var items = context.Items.Where(x => x.IsActive==true).ToList();
Or
var items = context.Items.Where(x => x.IsActive).ToList();

Linq's FirstOrDefault to act as other type?

I have this code :
var res1 = dtData.AsEnumerable()
.Where(...)
.Select(f => new { val = f["PremiumAfterUWDiscount"].ToDecimalOrZero(),
idpolicy = f["IdPolicy"].ToString() })
.FirstOrDefault();
however , since this returns an anonymous type , its default value is null .
I want it to act as FirstOrDefault for int type.
so if there is no record , it will return 0 ( default behavior as int).
is it possible ?
p.s. ( of course i can check it in a condition but still , i prefer the linq way).
Return an anonymous type that signifies "nothing" and either use the null coalescing operator:
var res1 = dtData.AsEnumerable()
.Where(...)
.Select(f => new { val = f["PremiumAfterUWDiscount"].ToDecimalOrZero(),
idpolicy = f["IdPolicy"].ToString() })
.FirstOrDefault() ?? new { val = 0, idpolicy = "" };
Or the DefaultIfEmpty extension method:
var res1 = dtData.AsEnumerable()
.Where(...)
.Select(f => new { val = f["PremiumAfterUWDiscount"].ToDecimalOrZero(),
idpolicy = f["IdPolicy"].ToString() })
.DefaultIfEmpty(new { val = 0, idpolicy = "" })
.FirstOrDefault();
You would only be able to return an int in place of an anonymous type if you in fact return an object and cast later on (as per #recursive's answer), but this to me seems counter-productive.
FirstOrDefault does not offer a way to specify what "default" is.
You can't have an expression evaluate to 2 different data types on two execution paths.
Also, even if that's possible, var is different to dynamic so the variable type won't be inferred by the compiler.
Doing this doesn't make any sense, and I would encourage you to think about why you want to do this, and find some cleaner, more direct way to accomplish it.
With that said, here's a small tweak to #IronicMuffin's approach that will actually work.
object res1 = dtData.AsEnumerable()
.Where(...)
.Select(f => new {
val = f["PremiumAfterUWDiscount"].ToDecimalOrZero(),
idpolicy = f["IdPolicy"].ToString()
})
.FirstOrDefault() as object ?? 0;
I don't think this is very useful though.

How to handle nulls in LINQ when using Min or Max?

I have the following Linq query:
result.Partials.Where(o => o.IsPositive).Min(o => o.Result)
I get an exception when result.Partials.Where(o => o.IsPositive) does not contains elements. Is there an elegant way to handle this other than splitting the operation in two and checking for null? I have a class full of operations like this one.
EDIT: The question is related with LINQ to Objects.
This is the Exception I'm getting (translated it says: The sequence is empty):
A short summary of the calculation of a Min
- No mediation (Exception!)
var min = result.Partials.Where(o => o.IsPositive).Min(o => o.Result);
This is your case: if there are no matching elements, then the Min call will raise an exception (InvalidOperationException).
- With DefaultIfEmpty() -- still troublesome
var min = result.Partials.Where(o => o.IsPositive)
.Select(o => o.Result)
.DefaultIfEmpty()
.Min();
DefaultIfEmpty will create an enumeration over the 0 element, when there are no elements in the list. How do you know that 0 is the Min or if 0 stands for a list with no elements?
- Nullable values; A better solution
var min = result.Partials.Where(o => o.IsPositive)
.Min(o => (decimal?)o.Result);
Here Min is either null (because that's equal to default(decimal?)) or the actual Min found.
So a consumer of this result will know that:
When result is null then the list had no elements
When the result is a decimal value then the list had some elements and the Min of those elements is that returned value.
However, when this doesn't matter, then min.GetValueOrDefault(0) can be called.
You can use the DefaultIfEmpty method to ensure the collection has at least 1 item:
result.Partials.Where(o => o.IsPositive).Select(o => o.Result).DefaultIfEmpty().Min();
You can't use Min (or Max) if the sequence is empty. If that shouldn't be happening, you have a different issue with how you define result. Otherwise, you should check if the sequence is empty and handle appropriately, eg:
var query = result.Partials.Where(o => o.IsPositve);
min = query.Any() ? query.Min(o => o.Result) : 0; // insert a different "default" value of your choice...
Yet another way to express it in LINQ is to use Aggregate:
var min = result.Partials
.Where(o => o.IsPositive)
.Select(o => o.Result)
.Aggregate(0, Math.Min); // Or any other value which should be returned for empty list
Since LINQ lacks methods like MinOrDefault() and MaxOrDefault(), you can create them by yourself:
public static class LinqExtensions
{
public static TProp MinOrDefault<TItem, TProp>(this IEnumerable<TItem> This, Func<TItem, TProp> selector)
{
if (This.Count() > 0)
{
return This.Min(selector);
}
else
{
return default(TProp);
}
}
}
Therefore, if the collection has values, the Min() is calculated, otherwise you get the property type's default value.
An example of use:
public class Model
{
public int Result { get; set; }
}
// ...
public void SomeMethod()
{
Console.WriteLine("Hello World");
var filledList = new List<Model>
{
new Model { Result = 10 },
new Model { Result = 9 },
};
var emptyList = new List<Model>();
var minFromFilledList = filledList.MinOrDefault(o => o.Result)); // 9
var minFromEmptyList = emptyList.MinOrDefault(o => o.Result)); // 0
}
NOTE 1: you don't need to check if the This parameter is null: the invoked Count() already checks that, and it throws the same Exception that you would throw.
NOTE 2: This solution is good only in situations where the Count() method is cheap to call. All .NET collections are fine since they are all very efficient; it could be a problem for particular customized/non-standard collections.

Select single item from a list

Using LINQ what is the best way to select a single item from a list if the item may not exists in the list?
I have come up with two solutions, neither of which I like. I use a where clause to select the list of items (which I know will only be one), I can then check the count and make a Single call on this list if count is one, the other choice is to use a foreach and just break after getting the item.
Neither of these seem like a good approach, is there a better way?
You can use IEnumerable.First() or IEnumerable.FirstOrDefault().
The difference is that First() will throw if no element is found (or if no element matches the conditions, if you use the conditions). FirstOrDefault() will return default(T) (null if it's a reference type).
Use the FirstOrDefault selector.
var list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var firstEven = list.FirstOrDefault(n => n % 2 == 0);
if (firstEven == 0)
Console.WriteLine("no even number");
else
Console.WriteLine("first even number is {0}", firstEven);
Just pass in a predicate to the First or FirstOrDefault method and it'll happily go round' the list and picks the first match for you.
If there isn't a match, FirstOrDefault will returns the default value of whatever datatype the list items is.
Hope this helps :-)
List<string> items = new List<string>();
items.Find(p => p == "blah");
or
items.Find(p => p.Contains("b"));
but this allows you to define what you are looking for via a match predicate...
I guess if you are talking linqToSql then:
example looking for Account...
DataContext dc = new DataContext();
Account item = dc.Accounts.FirstOrDefault(p => p.id == 5);
If you need to make sure that there is only 1 item (throws exception when more than 1)
DataContext dc = new DataContext();
Account item = dc.Accounts.SingleOrDefault(p => p.id == 5);
Just to complete the answer, If you are using the LINQ syntax, you can just wrap it since it returns an IEnumerable:
(from int x in intList
where x > 5
select x * 2).FirstOrDefault()
Maybe I'm missing something here, but usually calling .SingleOrDefault() is the way to go to return either the single element in the list, or a default value (null for reference or nullable types) if the list is empty.
It generates an exception if the list contains more than one element.
Use FirstOrDefault() to cover the case where you could have more than one.
There are two easy ways, depending on if you want to deal with exceptions or get a default value.
You can use the First<T>() or the FirstOrDefault<T>() extension method to get the first result or default(T).
var list = new List<int> { 1, 2, 4 };
var result = list.Where(i => i == 3).First(); // throws InvalidOperationException
var result = list.Where(i => i == 3).FirstOrDefault(); // = 0
SingleOrDefault() is what you need
cheers
just saw this now, if you are working with a list of object you can try this
public class user
{
public string username { get; set; }
public string password { get; set; }
}
List<user> userlist = new List<user>();
userlist.Add(new user { username = "macbruno", password = "1234" });
userlist.Add(new user { username = "james", password = "5678" });
string myusername = "james";
string mypassword = "23432";
user theUser = userlist.Find(
delegate (user thisuser)
{
return thisuser.username== myusername && thisuser.password == mypassword;
}
);
if (theUser != null)
{
Dosomething();
}
else
{
DoSomethingElse();
}

Categories