How do lambda parameters work? - c#

Let's say we have a class Student
class Student
{
public string Name { get; set; }
public string Gender { get; set; }
public List<string> Subjects { get; set; }
public static List<Student> GetAllStudetns()
{
List<Student> listStudents = new List<Student>
{
new Student
{
Name = "Tom",
Gender = "Male",
Subjects = new List<string> { "ASP.NET", "C#" }
},
new Student
{
Name = "Mike",
Gender = "Male",
Subjects = new List<string> { "ADO.NET", "C#", "AJAX" }
}
};
return listStudents;
}
}
And we want to print out each student with subjects like this:
Tom - ASP.NET
Tom - C#
Mike - ADO.NET
Mike - C#
etc
so the answer I found is
var result = Student.GetAllStudents().SelectMany(s => s.Subjects, (student, subject) => new { StudentName = student.Name, SubjectName = subject});
//then use foreach loop to retrieve...
I can understand the second use of =>, which is just projection to a anonymous type. But I don't understand the first part
s => s.Subjects, (student, subject)
From my understanding, the left side of => is intput parameter which is Student instance s in this case, but the right side of => should be return type related to the Student instance s,for example, s.Name if we want to get the student's name, so what does (student, subject) mean?

SelectMany is overloaded. See https://msdn.microsoft.com/en-us/library/bb534631(v=vs.110).aspx
s => s.Subjects is your collection selector - the transform that will be applied to each element of the input
and (student, subject) => new { StudentName = student.Name, SubjectName = subject} is your result selector - the transform function to apply to each element of the intermediate sequence

It is because Subjects is defined as a List<string> inside the class and you have to print it as separately, So it needs to iterate over the collection of the sub-list. Before explaining the requested part(s => s.Subjects, (student, subject)) of the query, you should take a look into the definition of the .SelectMany
public static IEnumerable<TResult> SelectMany<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TResult>> selector
)
As per the signature of the method, the second parameter(Func) is accepting the source object(here it is of type Student) and an IEnumerable collection objects, here that is of type string, since that is a List<string>.
Which means student in (student, subject) is of type Student and subject will be an item in s.Subjects, So the .SelectMany will further iterates through the sub collection here.
If you want to print like this(Tom - ASP.NET,C#) means you can do GroupBy in your case You don't want to go for that so performs iteration over the sub-list using that code;
Additional information: If the result is based on grouping then your code will be like this:
var result = Student.GetAllStudents()
.GroupBy(s => s.Name)
.Select(x=> new { StudentName = x.Key, SubjectName = String.Join(",",
x.SelectMany(y=>y.Subjects)
.ToList()});

Related

Sort list based on name of object in list?

Apologies if this is a duplicate question, which I think it may be although I cannot find the answer I am looking for.
In my list, I need to sort by the names of the objects in the list. As an example, if in a list of cars I have the objects Volvo, Ford, BMW, Audi, Ferrari and Lamborghini in that order, then I would need it to order the objects Audi then BMW etc.
I have tried doing .OrderBy(x => nameof(x)) but this didn't work. Does anyone have any solutions
Edit: I need to order by the following (other elements hidden because of sensitive data):
So on Data there is an object called "Aaa" for example which needs to be the first element in this list.
Instead of relying on the variables names within a collection you should use a Name-property for every instances within that list:
class Car
{
public string Name { get; set; }
// ...
}
Now within your consuming code you can easily use this property:
var myList = new List<Car> {
new Car { Name = "BMW" },
new Car { Name = "Audi" }
}:
var result = myList.OrderBy(x => x.Name);
As Abion47 and me already mentioned the names of variables have no meaning to the assembly, just within your sour-code. When you de-compile your assembly you see that the names are replaced by a random dummy-name. So relying on those names is hardly a good idea.
Below is one of the way using IComparer interface.
//Implementing IComparer
public class Cars
{
string Name { get; set; }
}
public class SortByName : IComparer<Cars>
{
public int Compare(Cars first, Cars next)
{
return first.Name.CompareTo(next.Name);
}
}
//Main Method
static void Main(string[] args)
{
Cars one = new Cars { Name = "Ford" };
Cars two = new Cars { Name = "Audi" };
Cars three = new Cars { Name = "Lamborghini" };
Cars four = new Cars { Name = "Ferrari" };
List<Cars> _lstCars = new List<Cars>();
_lstCars.Add(one);
_lstCars.Add(two);
_lstCars.Add(three);
_lstCars.Add(four);
SortByName sortbyname = new SortByName();
_lstCars.Sort(sortbyname);
Console.WriteLine("Result After sorting by name");
foreach (var item in _lstCars)
{
Console.WriteLine(item.Name);
Console.WriteLine();
}
Console.Read();
}

Can I initialize an object in one statement using lambda for each syntax?

If I have these two classes:
public class StudyClass
{
public string className { get; set; }
public List<Student> students { get; set; }
}
public class Student
{
public string studentName { get; set; }
}
Then I can initialize the StudyClass object like that:
var classObject = GetClassData(); // returns a big object with many properties that I don't need
var studyClass= new StudyClass() {
className = classObject.className
}
foreach(var student in classObject.students)
{
studyClass.students.add(new Student() {
studentName = student.Name
});
}
Is it possible to do it in a more simple way, by doing something like:
var classObject = GetClassData(); // returns a big object with many properties that I don't need
var studyClass= new StudyClass() {
className = classObject.className,
students = classObject.students.ForEach...// i am stuck here
}
If it's possible, is there any performance benefit or drawback by doing that ?
Yes, you can do this using the LINQ Select method followed by returning the results as a list using ToList:
var classObject = GetClassData();
var studyClass = new StudyClass {
className = classObject.className
students = classObject.students.Select(s => new Student { studentName = s.Name}).ToList()
};
This will enumerate classObject.students calling the lambda function once for each one, where the expression returns a new Student using the current value (s) to set the studentName property.
is there any performance benefit or drawback by doing that ?
It's unlikely to be any more performant; internally it still has to enumerate classObject.students and has to call the lambda method, while the original method has to make use of List.Add. You need to properly measure the timings to find out if it makes a worthwhile difference in your environment.
You can do it using linq:
var studyClass= new StudyClass() {
className = classObject.className,
students = classObject.students.Select(s => new Student { studentName = s.Name }).ToList();
}
There maybe a little performance improvement. You have to enumerate classObject.students in both variations, but Select and ToList may be faster than calling List.Add for each single student.
You can just project a list using Select() and ToList(), like this:
var studyClass= new StudyClass()
{
className = classObject.className,
students = classObject.students.Select(s=>new Student(){Name=s.Name}).ToList()
}
It looks like you are wanting to clone the students from one list and append them to another list.
You can do that like this:
studyClass.students.AddRange(
classObject.students.Select(student =>
new Student {studentName = student.studentName}));
If you actually want to replace the list with a new one, you can do this instead:
studyClass.students = new List<Student>(
classObject.students.Select(student =>
new Student {studentName = student.studentName}));
This is likely to be marginally more performant than other methods, but the difference will probably be so small as to be negligible.

Plain ArrayList Linq c# 2 syntaxes (need a conversion)

This question is purely academic for me and a spinoff of a question I answered here.
Retrieve object from an arraylist with a specific element value
This guy is using a plain ArrayList... I Know not the best thing to do ... filled with persons
class Person
{
public string Name { get; set; }
public string Gender { get; set; }
public Person(string name, string gender)
{
Name = name;
Gender = gender;
}
}
personArrayList = new ArrayList();
personArrayList.Add(new Person("Koen", "Male"));
personArrayList.Add(new Person("Sheafra", "Female"));
Now he wants to select all females. I solve this like this
var females = from Person P in personArrayList where P.Gender == "Female" select P;
Another guy proposes
var persons = personArrayList.AsQueryable();
var females = persons.Where(p => p.gender.Equals("Female"));
But that does not seem to work because the compiler can never find out the type of p.
Does anyone know what the correct format for my query would be in the second format?
You can use Cast<T> to cast it to a strongly typed enumerable:
var females = personArrayList.Cast<Person>()
.Where(p => p.gender.Equals("Female"));
Cast<T> throws exception if you have anything other than Person in your arraylist. You can use OfType<T> instead of Cast<T> to consider only those objects of type Person.
On a side note, kindly use an enum for gender, not strings.
enum Sex { Male, Female }
class Person
{
public Sex Gender { get; set; }
}
Since the ArrayList has untyped members, you'll have to cast the members to Person:
var females = persons.OfType<Person>().Where(p => p.gender.Equals("Female"));
Cast personArrayList to its element type and you are done.
var persons = personArrayList.Cast<Person>();

How do I Iterate Linq group result set?

I'm getting some data from my database and using linq to calculate sums and counts and group the data.
This is what I have:
var si = _repository.GetAllByDate(date);
var cs = from s in si
group s by s.Name into g
select new { Comm = g.Key, SIList = g.ToList(), Count = g.Count() };
i now need to pass cs to a method in another class so that I can extract Comm, SIList and Count for each item in the group, what type do I pass it as? IEnumerable doesn't work. The actual linq group result type seems to be:
{System.Linq.Enumerable.WhereSelectEnumerableIterator<System.Linq.IGrouping<Model.Domain.MasterData
.MyItem,Model.Domain.SI<>f__AnonymousTyped<Model.Domain.MasterData.MyItem,System.Collections.Generic.List<Model.Domain.SI>,int>>}
Any ideas? I effectively want to pass cs as a variable and iterate through it there.
You'll need to create a type that matches the definition of your anonymous type, if it's going to be used in different scopes.
public class SomeClass {
public Comm Comm { get; set; }
public IList<String> SIList { get; set; }
public Int32 Count { get; set; }
}
var si = _repository.GetAllByDate(date);
var cs = from s in si
group s by s.Name into g
select new SomeClass { Comm = g.Key, SIList = g.ToList(), Count = g.Count() };
EDIT: I supposed we can assume that the list will be of String so I'm editing for that. If that's the wrong type you'll need to change the IList<T> definition accordingly.
The reason that you get such a complicated type is because the query uses lazy execution. You are looking at the type of the expression that returns the result, not the type of the result.
The type of the result is IEnumerable<_hidden_internal_class_name_>, i.e. as you are creating anonymous objects in the query, the result is a stream of objects of a class that the compiler creates internally.
It's pretty useless to pass on that result to another method, as it would need to use reflection to read the properties in the objects. You should create a named class for the objects in the result, so that it's easy to access its properties.
Creating a type is an excellent idea, but why do that when a returned Tuple can be done without creating a new class or struct? If the need is local and or internal and the class won't be reused, try using a Tuple instead.
Select new Tuple<Comm, IEnumerable<string>, Int32>( new Comm(), myStringList.AsEnumerable(), myCount )
class Pet
{
public string Name { get; set; }
public int Age { get; set; }
}
// Uses method-based query syntax.
public static void GroupByEx1()
{
// Create a list of pets.
List<Pet> pets =
new List<Pet>{ new Pet { Name="Barley", Age=8 },
new Pet { Name="Boots", Age=4 },
new Pet { Name="Whiskers", Age=1 },
new Pet { Name="Daisy", Age=4 } };
// Group the pets using Age as the key value
// and selecting only the pet's Name for each value.
IEnumerable<IGrouping<int, string>> query =
pets.GroupBy(pet => pet.Age, pet => pet.Name);
// Iterate over each IGrouping in the collection.
foreach (IGrouping<int, string> petGroup in query)
{
// Print the key value of the IGrouping.
Console.WriteLine(petGroup.Key);
// Iterate over each value in the
// IGrouping and print the value.
foreach (string name in petGroup)
Console.WriteLine(" {0}", name);
}
}
/*
This code produces the following output:
8
Barley
4
Boots
Daisy
1
Whiskers
*/
Pass it as object and in your foreach loop, use var as the iterator.

How can I use linq to return integers in one array that do not match up with an integer property of another array?

I have the following method signature:
internal static int[] GetStudentIDsThatAreNotLinked(PrimaryKeyDataV1[]
existingStudents, IQueryable<Student> linkedStudents)
PrimaryKeyData is a class that has ServerID and LocalID integers as properties.
Student is a class that (among other properties) has an integer one called StudentID.
In English, what I want to do is return an array of integers which are in existingStudents[...].ServerID but not in linkedStudents[...].StudentID
If 'existingStudents' and 'linkedStudents' were both integer arrays, I would use a linq query as below:
return from es in existingStudents where
!linkedStudents.Contains<int>(es) select es;
..which could then be converted to an array of ints.
What I want to do is give Contains an IEqualityOperator that will consider a PrimaryKeyData class to be equal to a Student class if PrimaryKeyData.ServerID == Student.StudentID
So I think I need a lambda expression but I'm very confused on how that would be constructed.
I think I'm going in the right direction but can anyone help me over the final hurdle?
So, my understanding is that you want to get all instances of PrimaryKeyDataV1 whose ServerID property doesn't exist in any of the students.StudentID property of the linkedStudents parameter?
internal static PrimaryKeyDataV1[] GetStudentsThatAreNotLinked(PrimaryKeyDataV1[] existingStudents, IQueryable<Student> linkedStudents)
{
var results = existingStudents.Select(s => s.ServerID)
.Except(linkedStudents.Select(link => link.StudentID))
.ToArray();
return existingStudents.Where(stud => results.Contains(stud.ServerID));
}
Or if you just want an array of IDs...
internal static int[] GetStudentIDsThatAreNotLinked(PrimaryKeyDataV1[] existingStudents, IQueryable<Student> linkedStudents)
{
return existingStudents.Select(s => s.ServerID)
.Except(linkedStudents.Select(link => link.StudentID))
.ToArray();
}
If you only need to return the IDs, you can use something like:
existingStudents.Select(es => es.StudentID)
.Except(linkedStudents.Select(ls => ls.ServerID));
You can write this in query comprehension form, but I think it's less clear:
var result = (from es in existingStudents select es.StudentID);
.Except
(from ls in linkedStudents select ls.ServerID)
If you need to return the result as an array, just use the .ToArray() extension:
existingStudents.Select(es => es.StudentID)
.Except(linkedStudents.Select(ls => ls.ServerID)).ToArray();
You don't need to create your own equality comparer in the case that you only need to return the set difference in IDs.
List<student> ExistingStudents = new List<student>();
List<student> LinkedStudents = new List<student>();
ExistingStudents.Add(new student {id=1, name="joe"});
ExistingStudents.Add(new student { id = 2, name = "beth" });
ExistingStudents.Add(new student { id = 3, name = "sam" });
LinkedStudents.Add(new student { id = 2, name = "beth" });
var students = from stud in ExistingStudents
where !(LinkedStudents.Select(x => x.id).Contains(stud.id))
select stud;
foreach(student s in students)
{
System.Diagnostics.Debug.WriteLine(string.Format("id: {0}, name: {1}", s.id, s.name));
}
simple student class:
public class student
{
public int id;
public string name;
}

Categories