C# LINQ update item List<string> - c#

I have problem with updating a single item under List<string> that matches a different string using LINQ. Let's say that I have a list of names and I want to check if name "John" already exists in my list. If yes, then replace "John" with "Anna".
Here is what I do:
var sItem = myList.First(n=> n == "John"); //I am 100% sure that John exists, that\s why I use .First
sItem = "Anna";
This is how it should work, but when I check my List (myList) after the process, the original item is still there (I can still see John, instead of Anna). I also tried to implement INotifyChanged on the List, but still no result.
What am I doing wrong?

If you need to update, use FindIndex:
int index = myList.FindIndex(n => n == "John");
myList[index] = "Anna";

You are assigning the result of linq query to a string variable. That is not the element of list but a variable that is also referencing the element of that list. Changing the value of variable sItem will define a new string that will be referenced by the sItem and the item in the list remains unchanged.
You can use FindIndex to get the index of element in the array and use it to refer to list element.
int index = myList.FindIndex(n => n == "John");
myList[index] = "Anna";
Searches for an element that matches the conditions defined by the
specified predicate, and returns the zero-based index of the first
occurrence within the entire List.
Edit
When one string variable is assigned to other. They both would be referencing the same string but when you assign a different string to second variable for instance then they both referencing different strings. See the following example from answer of Eric Lippert.
a----------------------Hello
Then you say that "b = a", which means attach another piece of string to the same thing that a is attached to:
a----------------------Hello
/
b---------------------
Then you say "now attach b to Hi"
a----------------------Hello
b----------------------Hi

int index = strList.FindIndex(n => n == "John");
if (index != -1)
{
strList[index] = "Anna";
}
This will ensure that if "John" does not exist in the list, the program does not crash.

It should work for you
List<string> list = new List<string>();
list.Add("Gandarez");
list.Add("Carlos");
var search = list.FirstOrDefault(l => l == "Carlos");
if (search != null)
{
var index = list.IndexOf("Carlos");
list.RemoveAt(index);
list.Insert(index, "Test");
}

int sItem = myList.FindIndex(x => x == "John");
myList[sItem] = "Anna";

The problem you are seeing is that System.String, while actually a reference type, acts like a value type. So, when you assign a new value to sItem you are replacing it, not changing it.
If you were using a true reference type, what you tried could have worked:
List<Person> myList = ....;
var sItem = myList.First(p=> p.Name == "John");
sItem.Name = "Anna";
(Assigning -- sItem = new Person("Anna"); -- would still fail the same way,)

Related

Select lambda usage

Sorry for perhaps noob question) but I am just trying to learn something new.
I have an array that holds object with many fields - how to
check with select if the for example first field of this object is equal for some string ? (this field is also a string so no type ops needed)
Consider this scenario:
// Some data object
public class Data {
public string Name { get; set; }
public int Value { get; set; }
public Data(string name, int value)
{
this.Name = name;
this.Value = value;
}
}
// your array
Data[] array = new Data[]
{
new Data("John Smith", 123),
new Data("Jane Smith", 456),
new Data("Jess Smith", 789),
new Data("Josh Smith", 012)
}
array.Any(o => o.Name.Contains("Smith"));
// Returns true if any object's Name property contains "Smith"; otherwise, false.
array.Where(o => o.Name.StartsWith("J"));
// Returns an IEnumerable<Data> with all items in the original collection where Name starts with "J"
array.First(o => o.Name.EndsWith("Smith"));
// Returns the first Data item where the name ends with "Smith"
array.SingleOrDefault(o => o.Name == "John Smith");
// Returns the single element where the name is "John Smith".
// If the number of elements where the name is "John Smith"
// is greater than 1, this will throw an exception.
// If no elements are found, this` would return null.
// (SingleOrDefault is intended for selecting unique elements).
array.Select(o => new { FullName = o.Name, Age = o.Value });
// Projects your Data[] into an IEnumerable<{FullName, Value}>
// where {FullName, Value} is an anonymous type, Name projects to FullName and Value projects to Age.
I'm not 100% if I understood your questing but I will try to answer it anyway:
If you want to just get the first object with the desired field you can use FirstOrDefault:
var element = myArray.FirstOrDefault(o => o.FirstField == "someString");
If the element was not found it will return null.
If you just want to check if some object in your array matches your string you can check this with any
bool found = myArray.Any(o => o.FirstField == "someString");
Hope this helps
If you just want to find first element in array with certain value in a field/Property, you can use LINQ FirstOrDefault:
var element = array.FirstOrDefault(e => e.MyField == "value");
This will return first element that satisfies a condition or null(or other default value for your type) if no such value was found.
Select() is used as a projection (i.e. data transformation), not as a filter. If you want to filter a set of objects you should be looking at .Where(), Single(), First() and others. If you want to verify whether a property holds for Any or All elements in the collection, you can use those as well.
You can use Where clause to filter the list
var list = someList.Where(x => x.Name == "someName").FirstOrDefault();
var list = someList.Where(x => x.Name == "someName").ToList();
Use FirstOrDefault to select only one object and use ToList to select multiple objects that match a criteria defined by you.
And to make sure when comparing strings either compare all UppperCase or LowerCase letters.
var list = someList.Where(x => x.Name.ToUpper().Equals("SOMENAME")).FirstOrDefault();

Searching List<T>

I have a List<string> containing file paths. How can I check if there is a particular file in my list e.g. file.txt? Preferably a method returning not a bool value but list element or element's index.
I've read the List<T> documentation but it only confused me as I'm a beginning programmer and my English isn't very good.
Use Where to get a list of values:
var found = fileList.
Where((f)=>System.IO.Path.GetFileName(f)
.Equals(SEARCH_VALUE,
StringComparison.InvariantCultureIgnoreCase));
or use FirstOrDefault for single element or null in case it's not found
If your list contains the full path (like c:\windows\system.ini") I would use System.IO.Path.GetFileName and also keep in mind to search case intenstive
var result = from f in files
where Path.GetFileName(f).Equals("file.txt",
StringComparison.InvariantCultureIgnoreCase)
select f;
bool found = result.Any();
The IndexOf method is what you need, if you want to find the path that exactly watches what you are looking for.
However, if you what to find paths in your list that end with a certain file name, you can do,
var matches = paths.Select((path, i) => new { Path = path, Index = i })
.Where(item => Path.GetFileName(item.Path).Equals(
"file.txt",
StringComparison.OrdinalIgnoreCase));
However, note that matches will be a sequence of 0 or more matches. So, you can do,
if (matches.Any())
{
// I found something.
foreach (var match in matches)
{
var matchIndex = match.Index;
var matchPath = match.Path;
}
}
else
{
// Oops, no matches.
}
or, if you only want the first.
var firstMatchPath = matches.First().Path;
would do.
If you just want the first value if there is one then you can do this.
var value = mylist.FirstOrDefault(x=>x.EndsWith("file.txt"));
or if you want to do something with each matching string.
foreach (string value in mylist.Where(x=>x.EndsWith("file.txt")) )
{
// Do whatever you intend with value.
}
or if you want a list of the indices of the values, then you could try this.
var indexValues = new List<int>();
foreach (string value in mylist.Where(x=>x.EndsWith("file.txt")) )
{
indexValues.Add(mylist.IndexOf(value));
}
Use LINQ (assuming you havethe paths as strings):
var found = from f in fileList where f.equals("file.txt") select f;
Considering that you have path and file name is located at the end of path:
//List of elements
List<string> foundElements = myInitialList.Where(s => s.EndsWith("file.txt")).ToList();
//List of Indexes (base on lift of elements here above)
List<int> indexList = new List<int>();
foundElements.ForEach(f => indexList.Add(myInitialList.IndexOf(f)));
It's not clear from your question, but it seems that the list will contain file paths, but what you are looking for is a filename.
The following code will give you the index of the first occurrence of a file called "file.txt" in the list of path names, or -1 if it isn't in the list.
Note how this uses Path.GetFileName(). This is so that it will match "c:\dir1\dir2\file.txt" and not "c:\dir1\dir2\wrongfile.txt".
int index = filenames.FindIndex(filename => Path.GetFileName(filename).Equals("file.txt", StringComparison.OrdinalIgnoreCase));
However, if you are searching for an entire path then you can do it like this:
int index = filenames.FindIndex(filename => filename.Equals("file.txt", StringComparison.OrdinalIgnoreCase));
Note how we do a comparison of the entire filename rather than using "EndsWith".
If the filenames are already all lowercase and you are comparing entire paths then you can do a simpler search:
int index = filenames.IndexOf("file.txt");
Or if they are all uppercase you'd have to do:
int index = filenames.IndexOf("FILE.TXT");

Remove Items in Entity Collection

How to remove items in Entity Collection?
ex:
I have two entities that is related to each other
Employee and Reference
**Table: Employee**
EmployeeId(PK)
**Table: Reference**
ReferenceId(PK)
EmployeeId(FK)
Name
first I Initialize this:
Employee empCol = new Employee();
Reference refs = new Reference();
and then I save some data in the Entity Collection
refs.Name = "Sample";
empCol.References.Add(refs);
refs.Name = "Sample2";
empCol.References.Add(refs);
I want to remove the second element in the collection, how can I do that?
[0]= {Name = "Sample"}
[1]= {Name = "Sample2"}
I try this kind of code but it is not working, this code does not removing the second element in my Entity Collection, but it is not producing errors:
empCol.References.ToList().RemoveAt(1);
Dont assume how index is maintained inside by collection. Find the object first, and then remove it from collection
var ref = empCol.References.FirstOrDefault( r=> r.Name == "Sample");
if (ref != null)
empCol.References.Remove(ref);
If you want to remove by index, Find that index first.
I try to remove the object in the collection according to it's index in the collection and it work
var refs = empCols.References.ElementAt(1);
empCols.References.Remove(refs);
You can use all the List methods to remove items if you convert your EntityCollection into a List using toList(), for example
List<RotateArticle> articles = RotateArticle.GetDataByCategoryId(sm,currentArticle.MainCategory.Key).ToList();
and then
articles.Remove(articles[i]);
or
articles.RemoveRange(2, articles.Count - 1);

Don't understand why the ForEach command isn't working

I am wanting to trim any white space off a collection of strings. I used the following code but it doesn't seem to work. Could anyone explain why?
result.ForEach(f => f = f.Trim());
This won't work because you are assigning a new string reference to a local variable. This is probably what you are looking for:
result = result.Select(f => f.Trim()).ToList();
You are re-assigning the argument variable inside the scope of the lambda. It's a collapsed form of:
foreach(string value in myList)
{
Lambda(value);
}
void Lambda(string input)
{
input = input.Trim();
}
The simplest way would probably be to use a projection:
myList = myList.Select(str => str.Trim()).ToList();
foreach doesn't give you write access to the underlying collection, it only iterates through it, which means your change isn't stored back into the collection.
You can do two things:
Produce a new collection
var newResult = result.Select(f => f.Trim()).ToList();
Use a normal for-loop and change the original collection
for (int index = 0; index < result.Count; index++)
result[index] = result[index].Trim();

what does this .net line of code means

I recently shifted from JAVA development environment to .net development environment. I am developing web application using .net MVC framework. Would someone help me to find the meaning of following code segment. It seams like iterating thought list, but I could not find specific definition of this code sample:
SmartTextBoxModel smartTextBoxModel = new SmartTextBoxModel();
List<string> nameList = new List<string>() { "AA", "AB", "AC", "BB", "B" };
var filteredStringList =
from n in nameList
where n.IndexOf(name, 0, StringComparison.OrdinalIgnoreCase) != -1
select n;
The SmartTextBoxModel class has following code (it basically contains list object and getters and setters).
public class SmartTextBoxModel
{
public SmartTextBoxModel()
{
this.NameList = new List<SelectListItem>();
}
public List<SelectListItem> NameList { get;private set; }
public string Name { get; set; }
}
My Question is what does this line mean:
var filteredStringList =
from n in nameList
where n.IndexOf(name, 0, StringComparison.OrdinalIgnoreCase) != -1
select n;
That line is selecting all instances n in nameList where the string n contains the string name. So your result will be any of the strings in nameList that have the string name in it.
Also, it is important to break it up into the two parts. First, this is a Linq query. You could do this to find all the items in nameList that equal name exactly:
var filteredStringList = from n in nameList where n == name select n;
Your where statement "n.IndexOf(name, 0, StringComparison.OrdinalIgnoreCase) != -1" just changes the simpler query, "n == name" to filter in a slightly different way. First, the n.IndexOf(name) method gets the first starting index where the string name occurs in n. Any value >= 0 means that name exists in the string. -1 is returned if the string doesnt exist. The other arguments are the index where to start the search, in your case 0, and the string comparison, in your case StringComparison.OrdinalIgnoreCase. StringComparison.OrdinalIgnoreCase tells the string comparison to treat A and a as the same and so on.
Edit: #Jason had a good point to consider also. While strictly speaking the query is not actually doing the iteration, it is actually creating a linq expression. The expression will execute only after you call something like filteredStringList.ToList() or a similar call. For all intents and purposes, the result is the same, but it is important to know when the query will actually execute. See this post for more details: http://blogs.msdn.com/b/charlie/archive/2007/12/09/deferred-execution.aspx?wa=wsignin1.0
var filteredStringList =
from n in nameList
where n.IndexOf(name, 0, StringComparison.OrdinalIgnoreCase) != -1
select n;
This is LINQ (specifically the query syntax form), and exactly what is happening is a little complicated and subtle.
The rough idea is that this block of code creates an iterator. When this iterator is iterated over, it will filter nameList by only selecting the elements of nameList that satisfy the predicate p(n) = n.IndexOf(name, 0, StringComparison.OrdinalIgnoreCase) != -1. That is, it will only select the elements of nameList that contain name (ignoring case).
It is very important that you understand that filteredStringList is not a list (thus, it is horribly named). It does not contain the results of the filtering. It only creates an object that captures the rules for building the filtered subsequence of nameList when it is iterated over.
"get all entries of nameList which contain name"
It's saying if a variable called name exists in nameList then select it into filteredStringList.
Your LINQ can be interpreted as
for (int i = 0; i < nameList.Count; i++)
{
if (nameList[i].IndexOf(name, 0, StringComparison.OrdinalIgnoreCase) != -1)
{
TempList.Add(nameList[i]);
}
}
here TempList is List<String> TempList = new List<string>();
In LAMBDA Expression you can write this as
var filteredStringList = nameList.Where(X => X.IndexOf(name, 0, StringComparison.OrdinalIgnoreCase) != -1);

Categories

Resources