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();
Related
I need to sort an existing list of object based on another list of objects (with matching keys), but both lists may or may not contain the same keys and number of items. Example:
struct Item
{
public string _id;
public Item(string id) { _id = id; }
}
List<Item> totalItems = new List<Item>()
{
new Item("foo"), new Item("bar"),
new Item("baz"), new Item("monkey"),
new Item("cat"), new Item("apple")
};
List<string> preferredSortOrder = new List<string>()
{
"monkey",
"cat",
"zebra",
"baz"
};
So the output would be:
monkey
cat
baz
apple
bar
foo
This is because the sort order from preferredSortOrder takes precedence, so monkey, cat, and baz are first in the list because they exist in totalItems. If something in preferredSortOrder list doesn't exist in totalItems, it should be ignored. All remaining items in totalItems should then be ordered alphabetically (as you can see above).
I haven't coded in linq for years and everything I've tried so far hasn't worked.
Edit: Based on Calimero's answer, here is the solution:
totalItems = totalItems
.OrderBy(item => item._id)
.OrderBy(item => {
var i = preferredSortOrder.IndexOf(item._id);
return i == -1 ? totalItems.Count : i;
}).ToList();
Use List.IndexOf method of your reference list as your keySelector function parameter of Enumerable.OrderBy with Linq.
totalItems.OrderBy(item => preferredSortOrder.IndexOf(item._id)).ToList();
EDIT : This would put the elements that are not in the reference list first (because IndexOf will return -1). But you can easily return a value that is greater than any possible index for this case
totalItems.OrderBy(item =>
{
var index = preferredSortOrder.IndexOf(item._id);
return index == -1 ? totalItems.Count : index
}).ToList();
You can make an extension method out of this if you have repeated usage.
EDIT 2 : I didnt pay attention that your _id is private (you didnt specify accessibility and it is default accessibility here). You probably want to have a property that uses it as a backing field, or make it public
Add a second string field to struct Item, call it 'sortOrder'.
Create a class or struct for 'preferredSortOrder', add a second string field ('sortOrder') that indicates the sort order. Use numbers as they will sort ahead of alpha.
Do a first pass through 'totalItems', do a lookup on 'preferredSortOrder', copy the sortOrder. If the item is not found, copy its string (eg "apple").
Sort totalItems on sortOrder. Presto!
I have a
List<object> list = new List<object>();
while (myReader.Read())
{
string arrKablan = myReader["arrK_Title"].ToString();
string arrTotal = myReader["arrTotal"].ToString();
string _title = myReader["MF_Title"].ToString();
string _path = myReader["MF_Path"].ToString();
int _level = Convert.ToInt32(myReader["MF_Level"].ToString());
list.Add(new { title = _title, path = _path, kablanim = arrKablan, total = arrTotal, level = _level });
}
I need to select just items where level == 1
i tried
list = list.where(item => item.level == 1);
but i get an error
'object' does not contain a definition for 'level' and no extension method 'level' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)
i know that the compiler can get the type so he can know what it is "level".
how can i achieve this kind of select, without to define a class ?
You have two ways of fixing this:
Use a List<dynamic> instead of a List<object>. This will disable type checks. Drawback: This will disable type checks. :-)
Let the compiler infer the correct type of your list. To do this, have your data layer return a DataTable instead of a DataReader and then use LINQ to create the list:
var myList = (from drow in myDataTable.AsEnumerable()
select new {
kablanim = drow["arrK_Title"].ToString(),
total = drow["arrTotal"].ToString(),
...
}).ToList();
I can't see why you don't just make a concrete class:
public class Foo
{
public string Title { get; set; }
public string Path { get; set; }
// etc, etc
}
Then
List<Foo> list = new List<Foo>();
while (myReader.Read())
{
string arrKablan = myReader["arrK_Title"].ToString();
string arrTotal = myReader["arrTotal"].ToString();
string _title = myReader["MF_Title"].ToString();
string _path = myReader["MF_Path"].ToString();
int _level = Convert.ToInt32(myReader["MF_Level"].ToString());
list.Add(new Foo { Title = _title, Path = _path, /* etc, etc */ });
}
then you call becomes
list = list.Where(item => item.Level == 1).ToList();
(Note the additional ToList call required to make the list assignment valid)
Just for completeness, you can also do this. Create a function to get a value from any object using reflection:
private T GetValue<T>(object obj, string property)
{
return (T)obj.GetType()
.GetProperties()
.Single(p => p.Name == property)
.GetValue(obj);
}
And call it like this:
var filteredList = list.Where(item => GetValue<int>(item, "level") == 1);
You can get value of a property on anonymous class like this:
var anon = new { Level = "level", Time = DateTime.Now };
Type type = anon.GetType();
var props = type.GetProperties();
foreach (var propertyInfo in props)
{
if (propertyInfo.Name == "Level")
{
var x =propertyInfo.GetValue(anon);
}
}
I'm not sure if it is the best way to achieve that, but it is certainly possible.
You are adding object of anonymous class to the list. You can refer to this anonymous class field only inside the method you've defined it in and you should probably avoid adding it to the list, because there is now other way other then reflection or dynamic to access field of theese objects.
For example, you can access one of the elements like this:
var list = new List();
list.Add(new { field1 = "a", field2 = 2 });
list.Add(new { field1 = "b", field2 = 3 });
list.Add(new { field1 = "c", field2 = 4 });
dynamic o = list[1];
Console.WriteLine(o.field1);
Console.WriteLine(o.field2);
But you should be aware, that dynamic feature has a big overhead on every member access.
If you really want to use lambdas, you can rewrite
list = list.where(item => item.level == 1);
like this
var filtered = list.Where(item =>
{
dynamic ditem = item;
return ditem.Level == 1;
});
but this is a bad approach.
The other approach is to use reflection and rewrite this lambda like this
var filtered = list.Where(item =>
{
var field = item.GetType().GetField("level");
return (int)field.GetValue(item) == 1;
});
This is better than using dynamic because it has a smaller overhead, but can still be very costly.
Also it would probably be better to cache FieldInfo object outside of loop if your anonymous objects have same type. It can be done like this
var field = list.First().GetType().GetField("level");
var filtered = list.Where(item => (int)field.GetValue(item) == 1);
For performance reasons, Linq depends on metadata being available at compile time. By explicitly declaring List<object> you have typed the elements of this list as object which does not have a member level.
If you want to use Linq like this you have two options.
Declare a class with a level member and use it to type the collection
Declare an interface with a level member and use it to cast in the lambda expression
Option 1 is the preferred approach. Normally Linq is used with a database and the classes are generated by Visual Studio directly from the database. This is why nobody complains about the need for classes to supply metadata.
The following line creates anonymous class.
new { title = _title, path = _path, kablanim = arrKablan, total = arrTotal, level = _level });
You can't cast then your objects to anything meaningfull.
Objects don't have those properties.
You have to create a class by your own and use it.
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,)
I've got an list where myclass is defined with a few properties
List<MyClass> myClassList = new List<MyClass>();
myClassList.Add(new MyClass() { Id = 1, Name = "My first test", Key = "First" });
myClassList.Add(new MyClass() { Id = 2, Name = "My second test", Key = "Second" });
I then have a path i.e. c:\my folders\company name\My First Test, which I break into a string array using regex i.e. myArrayPath
I want to find if any elements of myClassList based on a given property i.e. Name can be found in any of the elements from myArrayPath and ideally I'd like to return the Key but if I return the object matching one of the element from myArrayPath, this will be just as good.
Is there a way to achieve this using linq and/or lambda expressions.
Thanks.
This can be quite easily done with LINQ:
var keyResult = myClassList.Where(x=>myArrayPath.Contains(x.Name))
.Select(x=>x.Key);
if you need it in the form of a list of an array, just add .ToList() or .ToArray() at the end of it.
I have a list of objects. These objects have three variables, ID, Name, & value. There can be a lot of objects in this list, and I need to find one based on the ID or Name, and change the value.
Example
class objec
{
public string Name;
public int UID;
public string value;
}
List<objec> TextPool = new List<objec>();
How would I find the one entry in TextPool that had the Name of 'test' and change its value to 'Value'.
The real program has many more search options, and values that need changing, so I couldn't just use a Dictionary (though Name and UID or unique identifiers).
Any help would be great
You could use LINQ to find it, then change the element directly:
var item = TextPool.FirstOrDefault(o => o.Name == "test");
if (item != null)
item.value = "Value";
If you wanted to change all elements that match, you could, potentially, even do:
TextPool.Where(o => o.Name == "test").ToList().ForEach(o => o.value = "Value");
However, I personally would rather split it up, as I feel the second option is less maintainable (doing operations which cause side effects directly on the query result "smells" to me)...
var find = TextPool.FirstOrDefault(x => x.Name == "test");
if (find != null)
{
find.Name = "Value";
}
Sounds like a job for LINQ!
var matchedObject =
from t in TextPool
where t.UName == "test"
select t;
This is assuming your search is defined in code. If your code is driven by the UI, you may simply need to do a linear iteration. To search all possible attributes, without indexing, it isn't going to get any faster.
[ Edit: Was beaten to the punch, but leaving this up as an example of a different syntax, plus a link ]
List<objec> TextPool = new List<objec>();
objec found = TextPool.FirstOrDefault(item => item.Name == "test");
if (found != null) found.value = "Value";
If you are going to perform many lookups, you could cache the results in multiple Dictionary<> instances (or Lookup<> instance if keys are not unique).